linux/drivers/net/wireless/ath/carl9170/fw.c
Christian Lamparter 5c89569194 carl9170: support firmware-based rx filter
The hardware rx-filter was essentially disabled, because
of a serve, yet unidentifiable problem with iwlagn.
Due to these circumstances the driver and mac80211 were
left with the job of filtering.

This is very unfortunate and has proven to be expensive
in terms of latency, memory and load.

Therefore the new 1.8.8.3 firmware introduces a flexible
filtering infrastructure which allows the driver to
offload some of the checks (FCS & PLCP crc check,
RA match, control frame filter, etc...) whenever possible.

Note:
This patch also includes all changes to the
shared headers files since the inclusion.

Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2010-10-05 13:35:21 -04:00

403 lines
10 KiB
C

/*
* Atheros CARL9170 driver
*
* firmware parser
*
* Copyright 2009, 2010, Christian Lamparter <chunkeey@googlemail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, see
* http://www.gnu.org/licenses/.
*/
#include <linux/kernel.h>
#include <linux/firmware.h>
#include <linux/crc32.h>
#include "carl9170.h"
#include "fwcmd.h"
#include "version.h"
#define MAKE_STR(symbol) #symbol
#define TO_STR(symbol) MAKE_STR(symbol)
#define CARL9170FW_API_VER_STR TO_STR(CARL9170FW_API_MAX_VER)
MODULE_VERSION(CARL9170FW_API_VER_STR ":" CARL9170FW_VERSION_GIT);
static const u8 otus_magic[4] = { OTUS_MAGIC };
static const void *carl9170_fw_find_desc(struct ar9170 *ar, const u8 descid[4],
const unsigned int len, const u8 compatible_revision)
{
const struct carl9170fw_desc_head *iter;
carl9170fw_for_each_hdr(iter, ar->fw.desc) {
if (carl9170fw_desc_cmp(iter, descid, len,
compatible_revision))
return (void *)iter;
}
/* needed to find the LAST desc */
if (carl9170fw_desc_cmp(iter, descid, len,
compatible_revision))
return (void *)iter;
return NULL;
}
static int carl9170_fw_verify_descs(struct ar9170 *ar,
const struct carl9170fw_desc_head *head, unsigned int max_len)
{
const struct carl9170fw_desc_head *pos;
unsigned long pos_addr, end_addr;
unsigned int pos_length;
if (max_len < sizeof(*pos))
return -ENODATA;
max_len = min_t(unsigned int, CARL9170FW_DESC_MAX_LENGTH, max_len);
pos = head;
pos_addr = (unsigned long) pos;
end_addr = pos_addr + max_len;
while (pos_addr < end_addr) {
if (pos_addr + sizeof(*head) > end_addr)
return -E2BIG;
pos_length = le16_to_cpu(pos->length);
if (pos_length < sizeof(*head))
return -EBADMSG;
if (pos_length > max_len)
return -EOVERFLOW;
if (pos_addr + pos_length > end_addr)
return -EMSGSIZE;
if (carl9170fw_desc_cmp(pos, LAST_MAGIC,
CARL9170FW_LAST_DESC_SIZE,
CARL9170FW_LAST_DESC_CUR_VER))
return 0;
pos_addr += pos_length;
pos = (void *)pos_addr;
max_len -= pos_length;
}
return -EINVAL;
}
static void carl9170_fw_info(struct ar9170 *ar)
{
const struct carl9170fw_motd_desc *motd_desc;
unsigned int str_ver_len;
u32 fw_date;
dev_info(&ar->udev->dev, "driver API: %s 2%03d-%02d-%02d [%d-%d]\n",
CARL9170FW_VERSION_GIT, CARL9170FW_VERSION_YEAR,
CARL9170FW_VERSION_MONTH, CARL9170FW_VERSION_DAY,
CARL9170FW_API_MIN_VER, CARL9170FW_API_MAX_VER);
motd_desc = carl9170_fw_find_desc(ar, MOTD_MAGIC,
sizeof(*motd_desc), CARL9170FW_MOTD_DESC_CUR_VER);
if (motd_desc) {
str_ver_len = strnlen(motd_desc->release,
CARL9170FW_MOTD_RELEASE_LEN);
fw_date = le32_to_cpu(motd_desc->fw_year_month_day);
dev_info(&ar->udev->dev, "firmware API: %.*s 2%03d-%02d-%02d\n",
str_ver_len, motd_desc->release,
CARL9170FW_GET_YEAR(fw_date),
CARL9170FW_GET_MONTH(fw_date),
CARL9170FW_GET_DAY(fw_date));
strlcpy(ar->hw->wiphy->fw_version, motd_desc->release,
sizeof(ar->hw->wiphy->fw_version));
}
}
static bool valid_dma_addr(const u32 address)
{
if (address >= AR9170_SRAM_OFFSET &&
address < (AR9170_SRAM_OFFSET + AR9170_SRAM_SIZE))
return true;
return false;
}
static bool valid_cpu_addr(const u32 address)
{
if (valid_dma_addr(address) || (address >= AR9170_PRAM_OFFSET &&
address < (AR9170_PRAM_OFFSET + AR9170_PRAM_SIZE)))
return true;
return false;
}
static int carl9170_fw(struct ar9170 *ar, const __u8 *data, size_t len)
{
const struct carl9170fw_otus_desc *otus_desc;
const struct carl9170fw_chk_desc *chk_desc;
const struct carl9170fw_last_desc *last_desc;
last_desc = carl9170_fw_find_desc(ar, LAST_MAGIC,
sizeof(*last_desc), CARL9170FW_LAST_DESC_CUR_VER);
if (!last_desc)
return -EINVAL;
otus_desc = carl9170_fw_find_desc(ar, OTUS_MAGIC,
sizeof(*otus_desc), CARL9170FW_OTUS_DESC_CUR_VER);
if (!otus_desc) {
dev_err(&ar->udev->dev, "failed to find compatible firmware "
"descriptor.\n");
return -ENODATA;
}
chk_desc = carl9170_fw_find_desc(ar, CHK_MAGIC,
sizeof(*chk_desc), CARL9170FW_CHK_DESC_CUR_VER);
if (chk_desc) {
unsigned long fin, diff;
unsigned int dsc_len;
u32 crc32;
dsc_len = min_t(unsigned int, len,
(unsigned long)chk_desc - (unsigned long)otus_desc);
fin = (unsigned long) last_desc + sizeof(*last_desc);
diff = fin - (unsigned long) otus_desc;
if (diff < len)
len -= diff;
if (len < 256)
return -EIO;
crc32 = crc32_le(~0, data, len);
if (cpu_to_le32(crc32) != chk_desc->fw_crc32) {
dev_err(&ar->udev->dev, "fw checksum test failed.\n");
return -ENOEXEC;
}
crc32 = crc32_le(crc32, (void *)otus_desc, dsc_len);
if (cpu_to_le32(crc32) != chk_desc->hdr_crc32) {
dev_err(&ar->udev->dev, "descriptor check failed.\n");
return -EINVAL;
}
} else {
dev_warn(&ar->udev->dev, "Unprotected firmware image.\n");
}
#define SUPP(feat) \
(carl9170fw_supports(otus_desc->feature_set, feat))
if (!SUPP(CARL9170FW_DUMMY_FEATURE)) {
dev_err(&ar->udev->dev, "invalid firmware descriptor "
"format detected.\n");
return -EINVAL;
}
ar->fw.api_version = otus_desc->api_ver;
if (ar->fw.api_version < CARL9170FW_API_MIN_VER ||
ar->fw.api_version > CARL9170FW_API_MAX_VER) {
dev_err(&ar->udev->dev, "unsupported firmware api version.\n");
return -EINVAL;
}
if (!SUPP(CARL9170FW_COMMAND_PHY) || SUPP(CARL9170FW_UNUSABLE) ||
!SUPP(CARL9170FW_HANDLE_BACK_REQ)) {
dev_err(&ar->udev->dev, "firmware does support "
"mandatory features.\n");
return -ECANCELED;
}
if (ilog2(le32_to_cpu(otus_desc->feature_set)) >=
__CARL9170FW_FEATURE_NUM) {
dev_warn(&ar->udev->dev, "driver does not support all "
"firmware features.\n");
}
if (!SUPP(CARL9170FW_COMMAND_CAM)) {
dev_info(&ar->udev->dev, "crypto offloading is disabled "
"by firmware.\n");
ar->disable_offload = true;
}
if (SUPP(CARL9170FW_PSM))
ar->hw->flags |= IEEE80211_HW_SUPPORTS_PS;
if (!SUPP(CARL9170FW_USB_INIT_FIRMWARE)) {
dev_err(&ar->udev->dev, "firmware does not provide "
"mandatory interfaces.\n");
return -EINVAL;
}
if (SUPP(CARL9170FW_MINIBOOT))
ar->fw.offset = le16_to_cpu(otus_desc->miniboot_size);
else
ar->fw.offset = 0;
if (SUPP(CARL9170FW_USB_DOWN_STREAM)) {
ar->hw->extra_tx_headroom += sizeof(struct ar9170_stream);
ar->fw.tx_stream = true;
}
if (SUPP(CARL9170FW_USB_UP_STREAM))
ar->fw.rx_stream = true;
if (SUPP(CARL9170FW_RX_FILTER)) {
ar->fw.rx_filter = true;
ar->rx_filter_caps = FIF_FCSFAIL | FIF_PLCPFAIL |
FIF_CONTROL | FIF_PSPOLL | FIF_OTHER_BSS |
FIF_PROMISC_IN_BSS;
}
ar->fw.vif_num = otus_desc->vif_num;
ar->fw.cmd_bufs = otus_desc->cmd_bufs;
ar->fw.address = le32_to_cpu(otus_desc->fw_address);
ar->fw.rx_size = le16_to_cpu(otus_desc->rx_max_frame_len);
ar->fw.mem_blocks = min_t(unsigned int, otus_desc->tx_descs, 0xfe);
atomic_set(&ar->mem_free_blocks, ar->fw.mem_blocks);
ar->fw.mem_block_size = le16_to_cpu(otus_desc->tx_frag_len);
if (ar->fw.vif_num >= AR9170_MAX_VIRTUAL_MAC || !ar->fw.vif_num ||
ar->fw.mem_blocks < 16 || !ar->fw.cmd_bufs ||
ar->fw.mem_block_size < 64 || ar->fw.mem_block_size > 512 ||
ar->fw.rx_size > 32768 || ar->fw.rx_size < 4096 ||
!valid_cpu_addr(ar->fw.address)) {
dev_err(&ar->udev->dev, "firmware shows obvious signs of "
"malicious tampering.\n");
return -EINVAL;
}
ar->fw.beacon_addr = le32_to_cpu(otus_desc->bcn_addr);
ar->fw.beacon_max_len = le16_to_cpu(otus_desc->bcn_len);
if (valid_dma_addr(ar->fw.beacon_addr) && ar->fw.beacon_max_len >=
AR9170_MAC_BCN_LENGTH_MAX) {
ar->hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC);
if (SUPP(CARL9170FW_WLANTX_CAB)) {
ar->hw->wiphy->interface_modes |=
BIT(NL80211_IFTYPE_AP);
}
}
#undef SUPPORTED
return 0;
}
static struct carl9170fw_desc_head *
carl9170_find_fw_desc(struct ar9170 *ar, const __u8 *fw_data, const size_t len)
{
int scan = 0, found = 0;
if (!carl9170fw_size_check(len)) {
dev_err(&ar->udev->dev, "firmware size is out of bound.\n");
return NULL;
}
while (scan < len - sizeof(struct carl9170fw_desc_head)) {
if (fw_data[scan++] == otus_magic[found])
found++;
else
found = 0;
if (scan >= len)
break;
if (found == sizeof(otus_magic))
break;
}
if (found != sizeof(otus_magic))
return NULL;
return (void *)&fw_data[scan - found];
}
int carl9170_fw_fix_eeprom(struct ar9170 *ar)
{
const struct carl9170fw_fix_desc *fix_desc = NULL;
unsigned int i, n, off;
u32 *data = (void *)&ar->eeprom;
fix_desc = carl9170_fw_find_desc(ar, FIX_MAGIC,
sizeof(*fix_desc), CARL9170FW_FIX_DESC_CUR_VER);
if (!fix_desc)
return 0;
n = (le16_to_cpu(fix_desc->head.length) - sizeof(*fix_desc)) /
sizeof(struct carl9170fw_fix_entry);
for (i = 0; i < n; i++) {
off = le32_to_cpu(fix_desc->data[i].address) -
AR9170_EEPROM_START;
if (off >= sizeof(struct ar9170_eeprom) || (off & 3)) {
dev_err(&ar->udev->dev, "Skip invalid entry %d\n", i);
continue;
}
data[off / sizeof(*data)] &=
le32_to_cpu(fix_desc->data[i].mask);
data[off / sizeof(*data)] |=
le32_to_cpu(fix_desc->data[i].value);
}
return 0;
}
int carl9170_parse_firmware(struct ar9170 *ar)
{
const struct carl9170fw_desc_head *fw_desc = NULL;
const struct firmware *fw = ar->fw.fw;
unsigned long header_offset = 0;
int err;
if (WARN_ON(!fw))
return -EINVAL;
fw_desc = carl9170_find_fw_desc(ar, fw->data, fw->size);
if (!fw_desc) {
dev_err(&ar->udev->dev, "unsupported firmware.\n");
return -ENODATA;
}
header_offset = (unsigned long)fw_desc - (unsigned long)fw->data;
err = carl9170_fw_verify_descs(ar, fw_desc, fw->size - header_offset);
if (err) {
dev_err(&ar->udev->dev, "damaged firmware (%d).\n", err);
return err;
}
ar->fw.desc = fw_desc;
carl9170_fw_info(ar);
err = carl9170_fw(ar, fw->data, fw->size);
if (err) {
dev_err(&ar->udev->dev, "failed to parse firmware (%d).\n",
err);
return err;
}
return 0;
}