diff options
Diffstat (limited to 'drivers/net/wireless/orinoco.c')
-rw-r--r-- | drivers/net/wireless/orinoco.c | 1949 |
1 files changed, 1772 insertions, 177 deletions
diff --git a/drivers/net/wireless/orinoco.c b/drivers/net/wireless/orinoco.c index 36c004e1560..ec0451cbb8e 100644 --- a/drivers/net/wireless/orinoco.c +++ b/drivers/net/wireless/orinoco.c @@ -79,15 +79,21 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> +#include <linux/delay.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/ethtool.h> +#include <linux/firmware.h> #include <linux/if_arp.h> #include <linux/wireless.h> #include <net/iw_handler.h> #include <net/ieee80211.h> +#include <linux/scatterlist.h> +#include <linux/crypto.h> + #include "hermes_rid.h" +#include "hermes_dld.h" #include "orinoco.h" /********************************************************************/ @@ -241,6 +247,74 @@ static int __orinoco_program_rids(struct net_device *dev); static void __orinoco_set_multicast_list(struct net_device *dev); /********************************************************************/ +/* Michael MIC crypto setup */ +/********************************************************************/ +#define MICHAEL_MIC_LEN 8 +static int orinoco_mic_init(struct orinoco_private *priv) +{ + priv->tx_tfm_mic = crypto_alloc_hash("michael_mic", 0, 0); + if (IS_ERR(priv->tx_tfm_mic)) { + printk(KERN_DEBUG "orinoco_mic_init: could not allocate " + "crypto API michael_mic\n"); + priv->tx_tfm_mic = NULL; + return -ENOMEM; + } + + priv->rx_tfm_mic = crypto_alloc_hash("michael_mic", 0, 0); + if (IS_ERR(priv->rx_tfm_mic)) { + printk(KERN_DEBUG "orinoco_mic_init: could not allocate " + "crypto API michael_mic\n"); + priv->rx_tfm_mic = NULL; + return -ENOMEM; + } + + return 0; +} + +static void orinoco_mic_free(struct orinoco_private *priv) +{ + if (priv->tx_tfm_mic) + crypto_free_hash(priv->tx_tfm_mic); + if (priv->rx_tfm_mic) + crypto_free_hash(priv->rx_tfm_mic); +} + +static int michael_mic(struct crypto_hash *tfm_michael, u8 *key, + u8 *da, u8 *sa, u8 priority, + u8 *data, size_t data_len, u8 *mic) +{ + struct hash_desc desc; + struct scatterlist sg[2]; + u8 hdr[ETH_HLEN + 2]; /* size of header + padding */ + + if (tfm_michael == NULL) { + printk(KERN_WARNING "michael_mic: tfm_michael == NULL\n"); + return -1; + } + + /* Copy header into buffer. We need the padding on the end zeroed */ + memcpy(&hdr[0], da, ETH_ALEN); + memcpy(&hdr[ETH_ALEN], sa, ETH_ALEN); + hdr[ETH_ALEN*2] = priority; + hdr[ETH_ALEN*2+1] = 0; + hdr[ETH_ALEN*2+2] = 0; + hdr[ETH_ALEN*2+3] = 0; + + /* Use scatter gather to MIC header and data in one go */ + sg_init_table(sg, 2); + sg_set_buf(&sg[0], hdr, sizeof(hdr)); + sg_set_buf(&sg[1], data, data_len); + + if (crypto_hash_setkey(tfm_michael, key, MIC_KEYLEN)) + return -1; + + desc.tfm = tfm_michael; + desc.flags = 0; + return crypto_hash_digest(&desc, sg, data_len + sizeof(hdr), + mic); +} + +/********************************************************************/ /* Internal helper functions */ /********************************************************************/ @@ -273,12 +347,19 @@ static inline void set_port_type(struct orinoco_private *priv) #define ORINOCO_MAX_BSS_COUNT 64 static int orinoco_bss_data_allocate(struct orinoco_private *priv) { - if (priv->bss_data) + if (priv->bss_xbss_data) return 0; - priv->bss_data = - kzalloc(ORINOCO_MAX_BSS_COUNT * sizeof(bss_element), GFP_KERNEL); - if (!priv->bss_data) { + if (priv->has_ext_scan) + priv->bss_xbss_data = kzalloc(ORINOCO_MAX_BSS_COUNT * + sizeof(struct xbss_element), + GFP_KERNEL); + else + priv->bss_xbss_data = kzalloc(ORINOCO_MAX_BSS_COUNT * + sizeof(struct bss_element), + GFP_KERNEL); + + if (!priv->bss_xbss_data) { printk(KERN_WARNING "Out of memory allocating beacons"); return -ENOMEM; } @@ -287,18 +368,319 @@ static int orinoco_bss_data_allocate(struct orinoco_private *priv) static void orinoco_bss_data_free(struct orinoco_private *priv) { - kfree(priv->bss_data); - priv->bss_data = NULL; + kfree(priv->bss_xbss_data); + priv->bss_xbss_data = NULL; } +#define PRIV_BSS ((struct bss_element *)priv->bss_xbss_data) +#define PRIV_XBSS ((struct xbss_element *)priv->bss_xbss_data) static void orinoco_bss_data_init(struct orinoco_private *priv) { int i; INIT_LIST_HEAD(&priv->bss_free_list); INIT_LIST_HEAD(&priv->bss_list); - for (i = 0; i < ORINOCO_MAX_BSS_COUNT; i++) - list_add_tail(&priv->bss_data[i].list, &priv->bss_free_list); + if (priv->has_ext_scan) + for (i = 0; i < ORINOCO_MAX_BSS_COUNT; i++) + list_add_tail(&(PRIV_XBSS[i].list), + &priv->bss_free_list); + else + for (i = 0; i < ORINOCO_MAX_BSS_COUNT; i++) + list_add_tail(&(PRIV_BSS[i].list), + &priv->bss_free_list); + +} + +static inline u8 *orinoco_get_ie(u8 *data, size_t len, + enum ieee80211_mfie eid) +{ + u8 *p = data; + while ((p + 2) < (data + len)) { + if (p[0] == eid) + return p; + p += p[1] + 2; + } + return NULL; +} + +#define WPA_OUI_TYPE "\x00\x50\xF2\x01" +#define WPA_SELECTOR_LEN 4 +static inline u8 *orinoco_get_wpa_ie(u8 *data, size_t len) +{ + u8 *p = data; + while ((p + 2 + WPA_SELECTOR_LEN) < (data + len)) { + if ((p[0] == MFIE_TYPE_GENERIC) && + (memcmp(&p[2], WPA_OUI_TYPE, WPA_SELECTOR_LEN) == 0)) + return p; + p += p[1] + 2; + } + return NULL; +} + + +/********************************************************************/ +/* Download functionality */ +/********************************************************************/ + +struct fw_info { + char *pri_fw; + char *sta_fw; + char *ap_fw; + u32 pda_addr; + u16 pda_size; +}; + +const static struct fw_info orinoco_fw[] = { + { "", "agere_sta_fw.bin", "agere_ap_fw.bin", 0x00390000, 1000 }, + { "", "prism_sta_fw.bin", "prism_ap_fw.bin", 0, 1024 }, + { "symbol_sp24t_prim_fw", "symbol_sp24t_sec_fw", "", 0x00003100, 0x100 } +}; + +/* Structure used to access fields in FW + * Make sure LE decoding macros are used + */ +struct orinoco_fw_header { + char hdr_vers[6]; /* ASCII string for header version */ + __le16 headersize; /* Total length of header */ + __le32 entry_point; /* NIC entry point */ + __le32 blocks; /* Number of blocks to program */ + __le32 block_offset; /* Offset of block data from eof header */ + __le32 pdr_offset; /* Offset to PDR data from eof header */ + __le32 pri_offset; /* Offset to primary plug data */ + __le32 compat_offset; /* Offset to compatibility data*/ + char signature[0]; /* FW signature length headersize-20 */ +} __attribute__ ((packed)); + +/* Download either STA or AP firmware into the card. */ +static int +orinoco_dl_firmware(struct orinoco_private *priv, + const struct fw_info *fw, + int ap) +{ + /* Plug Data Area (PDA) */ + __le16 pda[512] = { 0 }; + + hermes_t *hw = &priv->hw; + const struct firmware *fw_entry; + const struct orinoco_fw_header *hdr; + const unsigned char *first_block; + const unsigned char *end; + const char *firmware; + struct net_device *dev = priv->ndev; + int err; + + if (ap) + firmware = fw->ap_fw; + else + firmware = fw->sta_fw; + + printk(KERN_DEBUG "%s: Attempting to download firmware %s\n", + dev->name, firmware); + + /* Read current plug data */ + err = hermes_read_pda(hw, pda, fw->pda_addr, + min_t(u16, fw->pda_size, sizeof(pda)), 0); + printk(KERN_DEBUG "%s: Read PDA returned %d\n", dev->name, err); + if (err) + return err; + + err = request_firmware(&fw_entry, firmware, priv->dev); + if (err) { + printk(KERN_ERR "%s: Cannot find firmware %s\n", + dev->name, firmware); + return -ENOENT; + } + + hdr = (const struct orinoco_fw_header *) fw_entry->data; + + /* Enable aux port to allow programming */ + err = hermesi_program_init(hw, le32_to_cpu(hdr->entry_point)); + printk(KERN_DEBUG "%s: Program init returned %d\n", dev->name, err); + if (err != 0) + goto abort; + + /* Program data */ + first_block = (fw_entry->data + + le16_to_cpu(hdr->headersize) + + le32_to_cpu(hdr->block_offset)); + end = fw_entry->data + fw_entry->size; + + err = hermes_program(hw, first_block, end); + printk(KERN_DEBUG "%s: Program returned %d\n", dev->name, err); + if (err != 0) + goto abort; + + /* Update production data */ + first_block = (fw_entry->data + + le16_to_cpu(hdr->headersize) + + le32_to_cpu(hdr->pdr_offset)); + + err = hermes_apply_pda_with_defaults(hw, first_block, pda); + printk(KERN_DEBUG "%s: Apply PDA returned %d\n", dev->name, err); + if (err) + goto abort; + + /* Tell card we've finished */ + err = hermesi_program_end(hw); + printk(KERN_DEBUG "%s: Program end returned %d\n", dev->name, err); + if (err != 0) + goto abort; + + /* Check if we're running */ + printk(KERN_DEBUG "%s: hermes_present returned %d\n", + dev->name, hermes_present(hw)); + +abort: + release_firmware(fw_entry); + return err; +} + +/* End markers */ +#define TEXT_END 0x1A /* End of text header */ + +/* + * Process a firmware image - stop the card, load the firmware, reset + * the card and make sure it responds. For the secondary firmware take + * care of the PDA - read it and then write it on top of the firmware. + */ +static int +symbol_dl_image(struct orinoco_private *priv, const struct fw_info *fw, + const unsigned char *image, const unsigned char *end, + int secondary) +{ + hermes_t *hw = &priv->hw; + int ret; + const unsigned char *ptr; + const unsigned char *first_block; + + /* Plug Data Area (PDA) */ + __le16 pda[256]; + + /* Binary block begins after the 0x1A marker */ + ptr = image; + while (*ptr++ != TEXT_END); + first_block = ptr; + + /* Read the PDA from EEPROM */ + if (secondary) { + ret = hermes_read_pda(hw, pda, fw->pda_addr, sizeof(pda), 1); + if (ret) + return ret; + } + + /* Stop the firmware, so that it can be safely rewritten */ + if (priv->stop_fw) { + ret = priv->stop_fw(priv, 1); + if (ret) + return ret; + } + + /* Program the adapter with new firmware */ + ret = hermes_program(hw, first_block, end); + if (ret) + return ret; + + /* Write the PDA to the adapter */ + if (secondary) { + size_t len = hermes_blocks_length(first_block); + ptr = first_block + len; + ret = hermes_apply_pda(hw, ptr, pda); + if (ret) + return ret; + } + + /* Run the firmware */ + if (priv->stop_fw) { + ret = priv->stop_fw(priv, 0); + if (ret) + return ret; + } + + /* Reset hermes chip and make sure it responds */ + ret = hermes_init(hw); + + /* hermes_reset() should return 0 with the secondary firmware */ + if (secondary && ret != 0) + return -ENODEV; + + /* And this should work with any firmware */ + if (!hermes_present(hw)) + return -ENODEV; + + return 0; +} + + +/* + * Download the firmware into the card, this also does a PCMCIA soft + * reset on the card, to make sure it's in a sane state. + */ +static int +symbol_dl_firmware(struct orinoco_private *priv, + const struct fw_info *fw) +{ + struct net_device *dev = priv->ndev; + int ret; + const struct firmware *fw_entry; + + if (request_firmware(&fw_entry, fw->pri_fw, + priv->dev) != 0) { + printk(KERN_ERR "%s: Cannot find firmware: %s\n", + dev->name, fw->pri_fw); + return -ENOENT; + } + + /* Load primary firmware */ + ret = symbol_dl_image(priv, fw, fw_entry->data, + fw_entry->data + fw_entry->size, 0); + release_firmware(fw_entry); + if (ret) { + printk(KERN_ERR "%s: Primary firmware download failed\n", + dev->name); + return ret; + } + + if (request_firmware(&fw_entry, fw->sta_fw, + priv->dev) != 0) { + printk(KERN_ERR "%s: Cannot find firmware: %s\n", + dev->name, fw->sta_fw); + return -ENOENT; + } + + /* Load secondary firmware */ + ret = symbol_dl_image(priv, fw, fw_entry->data, + fw_entry->data + fw_entry->size, 1); + release_firmware(fw_entry); + if (ret) { + printk(KERN_ERR "%s: Secondary firmware download failed\n", + dev->name); + } + + return ret; +} + +static int orinoco_download(struct orinoco_private *priv) +{ + int err = 0; + /* Reload firmware */ + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + /* case FIRMWARE_TYPE_INTERSIL: */ + err = orinoco_dl_firmware(priv, + &orinoco_fw[priv->firmware_type], 0); + break; + + case FIRMWARE_TYPE_SYMBOL: + err = symbol_dl_firmware(priv, + &orinoco_fw[priv->firmware_type]); + break; + case FIRMWARE_TYPE_INTERSIL: + break; + } + /* TODO: if we fail we probably need to reinitialise + * the driver */ + + return err; } /********************************************************************/ @@ -453,8 +835,7 @@ static int orinoco_xmit(struct sk_buff *skb, struct net_device *dev) int err = 0; u16 txfid = priv->txfid; struct ethhdr *eh; - int data_off; - struct hermes_tx_descriptor desc; + int tx_control; unsigned long flags; if (! netif_running(dev)) { @@ -486,23 +867,54 @@ static int orinoco_xmit(struct sk_buff *skb, struct net_device *dev) if (skb->len < ETH_HLEN) goto drop; - eh = (struct ethhdr *)skb->data; + tx_control = HERMES_TXCTRL_TX_OK | HERMES_TXCTRL_TX_EX; - memset(&desc, 0, sizeof(desc)); - desc.tx_control = cpu_to_le16(HERMES_TXCTRL_TX_OK | HERMES_TXCTRL_TX_EX); - err = hermes_bap_pwrite(hw, USER_BAP, &desc, sizeof(desc), txfid, 0); - if (err) { - if (net_ratelimit()) - printk(KERN_ERR "%s: Error %d writing Tx descriptor " - "to BAP\n", dev->name, err); - goto busy; + if (priv->encode_alg == IW_ENCODE_ALG_TKIP) + tx_control |= (priv->tx_key << HERMES_MIC_KEY_ID_SHIFT) | + HERMES_TXCTRL_MIC; + + if (priv->has_alt_txcntl) { + /* WPA enabled firmwares have tx_cntl at the end of + * the 802.11 header. So write zeroed descriptor and + * 802.11 header at the same time + */ + char desc[HERMES_802_3_OFFSET]; + __le16 *txcntl = (__le16 *) &desc[HERMES_TXCNTL2_OFFSET]; + + memset(&desc, 0, sizeof(desc)); + + *txcntl = cpu_to_le16(tx_control); + err = hermes_bap_pwrite(hw, USER_BAP, &desc, sizeof(desc), + txfid, 0); + if (err) { + if (net_ratelimit()) + printk(KERN_ERR "%s: Error %d writing Tx " + "descriptor to BAP\n", dev->name, err); + goto busy; + } + } else { + struct hermes_tx_descriptor desc; + + memset(&desc, 0, sizeof(desc)); + + desc.tx_control = cpu_to_le16(tx_control); + err = hermes_bap_pwrite(hw, USER_BAP, &desc, sizeof(desc), + txfid, 0); + if (err) { + if (net_ratelimit()) + printk(KERN_ERR "%s: Error %d writing Tx " + "descriptor to BAP\n", dev->name, err); + goto busy; + } + + /* Clear the 802.11 header and data length fields - some + * firmwares (e.g. Lucent/Agere 8.xx) appear to get confused + * if this isn't done. */ + hermes_clear_words(hw, HERMES_DATA0, + HERMES_802_3_OFFSET - HERMES_802_11_OFFSET); } - /* Clear the 802.11 header and data length fields - some - * firmwares (e.g. Lucent/Agere 8.xx) appear to get confused - * if this isn't done. */ - hermes_clear_words(hw, HERMES_DATA0, - HERMES_802_3_OFFSET - HERMES_802_11_OFFSET); + eh = (struct ethhdr *)skb->data; /* Encapsulate Ethernet-II frames */ if (ntohs(eh->h_proto) > ETH_DATA_LEN) { /* Ethernet-II frame */ @@ -513,33 +925,65 @@ static int orinoco_xmit(struct sk_buff *skb, struct net_device *dev) /* Strip destination and source from the data */ skb_pull(skb, 2 * ETH_ALEN); - data_off = HERMES_802_2_OFFSET + sizeof(encaps_hdr); /* And move them to a separate header */ memcpy(&hdr.eth, eh, 2 * ETH_ALEN); hdr.eth.h_proto = htons(sizeof(encaps_hdr) + skb->len); memcpy(hdr.encap, encaps_hdr, sizeof(encaps_hdr)); - err = hermes_bap_pwrite(hw, USER_BAP, &hdr, sizeof(hdr), - txfid, HERMES_802_3_OFFSET); - if (err) { - if (net_ratelimit()) - printk(KERN_ERR "%s: Error %d writing packet " - "header to BAP\n", dev->name, err); - goto busy; + /* Insert the SNAP header */ + if (skb_headroom(skb) < sizeof(hdr)) { + printk(KERN_ERR + "%s: Not enough headroom for 802.2 headers %d\n", + dev->name, skb_headroom(skb)); + goto drop; } - } else { /* IEEE 802.3 frame */ - data_off = HERMES_802_3_OFFSET; + eh = (struct ethhdr *) skb_push(skb, sizeof(hdr)); + memcpy(eh, &hdr, sizeof(hdr)); } err = hermes_bap_pwrite(hw, USER_BAP, skb->data, skb->len, - txfid, data_off); + txfid, HERMES_802_3_OFFSET); if (err) { printk(KERN_ERR "%s: Error %d writing packet to BAP\n", dev->name, err); goto busy; } + /* Calculate Michael MIC */ + if (priv->encode_alg == IW_ENCODE_ALG_TKIP) { + u8 mic_buf[MICHAEL_MIC_LEN + 1]; + u8 *mic; + size_t offset; + size_t len; + + if (skb->len % 2) { + /* MIC start is on an odd boundary */ + mic_buf[0] = skb->data[skb->len - 1]; + mic = &mic_buf[1]; + offset = skb->len - 1; + len = MICHAEL_MIC_LEN + 1; + } else { + mic = &mic_buf[0]; + offset = skb->len; + len = MICHAEL_MIC_LEN; + } + + michael_mic(priv->tx_tfm_mic, + priv->tkip_key[priv->tx_key].tx_mic, + eh->h_dest, eh->h_source, 0 /* priority */, + skb->data + ETH_HLEN, skb->len - ETH_HLEN, mic); + + /* Write the MIC */ + err = hermes_bap_pwrite(hw, USER_BAP, &mic_buf[0], len, + txfid, HERMES_802_3_OFFSET + offset); + if (err) { + printk(KERN_ERR "%s: Error %d writing MIC to BAP\n", + dev->name, err); + goto busy; + } + } + /* Finally, we actually initiate the send */ netif_stop_queue(dev); @@ -554,7 +998,7 @@ static int orinoco_xmit(struct sk_buff *skb, struct net_device *dev) } dev->trans_start = jiffies; - stats->tx_bytes += data_off + skb->len; + stats->tx_bytes += HERMES_802_3_OFFSET + skb->len; goto ok; drop: @@ -834,21 +1278,48 @@ static void orinoco_rx_monitor(struct net_device *dev, u16 rxfid, stats->rx_dropped++; } +/* Get tsc from the firmware */ +static int orinoco_hw_get_tkip_iv(struct orinoco_private *priv, int key, + u8 *tsc) +{ + hermes_t *hw = &priv->hw; + int err = 0; + u8 tsc_arr[4][IW_ENCODE_SEQ_MAX_SIZE]; + + if ((key < 0) || (key > 4)) + return -EINVAL; + + err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CURRENT_TKIP_IV, + sizeof(tsc_arr), NULL, &tsc_arr); + if (!err) + memcpy(tsc, &tsc_arr[key][0], sizeof(tsc_arr[0])); + + return err; +} + static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) { struct orinoco_private *priv = netdev_priv(dev); struct net_device_stats *stats = &priv->stats; struct iw_statistics *wstats = &priv->wstats; struct sk_buff *skb = NULL; - u16 rxfid, status, fc; + u16 rxfid, status; int length; - struct hermes_rx_descriptor desc; - struct ethhdr *hdr; + struct hermes_rx_descriptor *desc; + struct orinoco_rx_data *rx_data; int err; + desc = kmalloc(sizeof(*desc), GFP_ATOMIC); + if (!desc) { + printk(KERN_WARNING + "%s: Can't allocate space for RX descriptor\n", + dev->name); + goto update_stats; + } + rxfid = hermes_read_regn(hw, RXFID); - err = hermes_bap_pread(hw, IRQ_BAP, &desc, sizeof(desc), + err = hermes_bap_pread(hw, IRQ_BAP, desc, sizeof(*desc), rxfid, 0); if (err) { printk(KERN_ERR "%s: error %d reading Rx descriptor. " @@ -856,7 +1327,7 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) goto update_stats; } - status = le16_to_cpu(desc.status); + status = le16_to_cpu(desc->status); if (status & HERMES_RXSTAT_BADCRC) { DEBUG(1, "%s: Bad CRC on Rx. Frame dropped.\n", @@ -867,8 +1338,8 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) /* Handle frames in monitor mode */ if (priv->iw_mode == IW_MODE_MONITOR) { - orinoco_rx_monitor(dev, rxfid, &desc); - return; + orinoco_rx_monitor(dev, rxfid, desc); + goto out; } if (status & HERMES_RXSTAT_UNDECRYPTABLE) { @@ -878,15 +1349,14 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) goto update_stats; } - length = le16_to_cpu(desc.data_len); - fc = le16_to_cpu(desc.frame_ctl); + length = le16_to_cpu(desc->data_len); /* Sanity checks */ if (length < 3) { /* No for even an 802.2 LLC header */ /* At least on Symbol firmware with PCF we get quite a lot of these legitimately - Poll frames with no data. */ - return; + goto out; } if (length > IEEE80211_DATA_LEN) { printk(KERN_WARNING "%s: Oversized frame received (%d bytes)\n", @@ -895,6 +1365,11 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) goto update_stats; } + /* Payload size does not include Michael MIC. Increase payload + * size to read it together with the data. */ + if (status & HERMES_RXSTAT_MIC) + length += MICHAEL_MIC_LEN; + /* We need space for the packet data itself, plus an ethernet header, plus 2 bytes so we can align the IP header on a 32bit boundary, plus 1 byte so we can read in odd length @@ -921,6 +1396,100 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) goto drop; } + /* Add desc and skb to rx queue */ + rx_data = kzalloc(sizeof(*rx_data), GFP_ATOMIC); + if (!rx_data) { + printk(KERN_WARNING "%s: Can't allocate RX packet\n", + dev->name); + goto drop; + } + rx_data->desc = desc; + rx_data->skb = skb; + list_add_tail(&rx_data->list, &priv->rx_list); + tasklet_schedule(&priv->rx_tasklet); + + return; + +drop: + dev_kfree_skb_irq(skb); +update_stats: + stats->rx_errors++; + stats->rx_dropped++; +out: + kfree(desc); +} + +static void orinoco_rx(struct net_device *dev, + struct hermes_rx_descriptor *desc, + struct sk_buff *skb) +{ + struct orinoco_private *priv = netdev_priv(dev); + struct net_device_stats *stats = &priv->stats; + u16 status, fc; + int length; + struct ethhdr *hdr; + + status = le16_to_cpu(desc->status); + length = le16_to_cpu(desc->data_len); + fc = le16_to_cpu(desc->frame_ctl); + + /* Calculate and check MIC */ + if (status & HERMES_RXSTAT_MIC) { + int key_id = ((status & HERMES_RXSTAT_MIC_KEY_ID) >> + HERMES_MIC_KEY_ID_SHIFT); + u8 mic[MICHAEL_MIC_LEN]; + u8 *rxmic; + u8 *src = (fc & IEEE80211_FCTL_FROMDS) ? + desc->addr3 : desc->addr2; + + /* Extract Michael MIC from payload */ + rxmic = skb->data + skb->len - MICHAEL_MIC_LEN; + + skb_trim(skb, skb->len - MICHAEL_MIC_LEN); + length -= MICHAEL_MIC_LEN; + + michael_mic(priv->rx_tfm_mic, + priv->tkip_key[key_id].rx_mic, + desc->addr1, + src, + 0, /* priority or QoS? */ + skb->data, + skb->len, + &mic[0]); + + if (memcmp(mic, rxmic, + MICHAEL_MIC_LEN)) { + union iwreq_data wrqu; + struct iw_michaelmicfailure wxmic; + DECLARE_MAC_BUF(mac); + + printk(KERN_WARNING "%s: " + "Invalid Michael MIC in data frame from %s, " + "using key %i\n", + dev->name, print_mac(mac, src), key_id); + + /* TODO: update stats */ + + /* Notify userspace */ + memset(&wxmic, 0, sizeof(wxmic)); + wxmic.flags = key_id & IW_MICFAILURE_KEY_ID; + wxmic.flags |= (desc->addr1[0] & 1) ? + IW_MICFAILURE_GROUP : IW_MICFAILURE_PAIRWISE; + wxmic.src_addr.sa_family = ARPHRD_ETHER; + memcpy(wxmic.src_addr.sa_data, src, ETH_ALEN); + + (void) orinoco_hw_get_tkip_iv(priv, key_id, + &wxmic.tsc[0]); + + memset(&wrqu, 0, sizeof(wrqu)); + wrqu.data.length = sizeof(wxmic); + wireless_send_event(dev, IWEVMICHAELMICFAILURE, &wrqu, + (char *) &wxmic); + + goto drop; + } + } + /* Handle decapsulation * In most cases, the firmware tell us about SNAP frames. * For some reason, the SNAP frames sent by LinkSys APs @@ -939,11 +1508,11 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) hdr = (struct ethhdr *)skb_push(skb, ETH_HLEN); hdr->h_proto = htons(length); } - memcpy(hdr->h_dest, desc.addr1, ETH_ALEN); + memcpy(hdr->h_dest, desc->addr1, ETH_ALEN); if (fc & IEEE80211_FCTL_FROMDS) - memcpy(hdr->h_source, desc.addr3, ETH_ALEN); + memcpy(hdr->h_source, desc->addr3, ETH_ALEN); else - memcpy(hdr->h_source, desc.addr2, ETH_ALEN); + memcpy(hdr->h_source, desc->addr2, ETH_ALEN); dev->last_rx = jiffies; skb->protocol = eth_type_trans(skb, dev); @@ -952,7 +1521,7 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) skb->pkt_type = PACKET_OTHERHOST; /* Process the wireless stats if needed */ - orinoco_stat_gather(dev, skb, &desc); + orinoco_stat_gather(dev, skb, desc); /* Pass the packet to the networking stack */ netif_rx(skb); @@ -961,13 +1530,33 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) return; - drop: - dev_kfree_skb_irq(skb); - update_stats: + drop: + dev_kfree_skb(skb); stats->rx_errors++; stats->rx_dropped++; } +static void orinoco_rx_isr_tasklet(unsigned long data) +{ + struct net_device *dev = (struct net_device *) data; + struct orinoco_private *priv = netdev_priv(dev); + struct orinoco_rx_data *rx_data, *temp; + struct hermes_rx_descriptor *desc; + struct sk_buff *skb; + + /* extract desc and skb from queue */ + list_for_each_entry_safe(rx_data, temp, &priv->rx_list, list) { + desc = rx_data->desc; + skb = rx_data->skb; + list_del(&rx_data->list); + kfree(rx_data); + + orinoco_rx(dev, desc, skb); + + kfree(desc); + } +} + /********************************************************************/ /* Rx path (info frames) */ /********************************************************************/ @@ -1087,52 +1676,172 @@ static void orinoco_join_ap(struct work_struct *work) } /* Send new BSSID to userspace */ -static void orinoco_send_wevents(struct work_struct *work) +static void orinoco_send_bssid_wevent(struct orinoco_private *priv) { - struct orinoco_private *priv = - container_of(work, struct orinoco_private, wevent_work); struct net_device *dev = priv->ndev; struct hermes *hw = &priv->hw; union iwreq_data wrqu; int err; - unsigned long flags; - - if (orinoco_lock(priv, &flags) != 0) - return; err = hermes_read_ltv(hw, IRQ_BAP, HERMES_RID_CURRENTBSSID, ETH_ALEN, NULL, wrqu.ap_addr.sa_data); if (err != 0) - goto out; + return; wrqu.ap_addr.sa_family = ARPHRD_ETHER; /* Send event to user space */ wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); +} - out: - orinoco_unlock(priv, &flags); +static void orinoco_send_assocreqie_wevent(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + union iwreq_data wrqu; + int err; + u8 buf[88]; + u8 *ie; + + if (!priv->has_wpa) + return; + + err = hermes_read_ltv(hw, IRQ_BAP, HERMES_RID_CURRENT_ASSOC_REQ_INFO, + sizeof(buf), NULL, &buf); + if (err != 0) + return; + + ie = orinoco_get_wpa_ie(buf, sizeof(buf)); + if (ie) { + int rem = sizeof(buf) - (ie - &buf[0]); + wrqu.data.length = ie[1] + 2; + if (wrqu.data.length > rem) + wrqu.data.length = rem; + + if (wrqu.data.length) + /* Send event to user space */ + wireless_send_event(dev, IWEVASSOCREQIE, &wrqu, ie); + } } +static void orinoco_send_assocrespie_wevent(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + union iwreq_data wrqu; + int err; + u8 buf[88]; /* TODO: verify max size or IW_GENERIC_IE_MAX */ + u8 *ie; + + if (!priv->has_wpa) + return; + + err = hermes_read_ltv(hw, IRQ_BAP, HERMES_RID_CURRENT_ASSOC_RESP_INFO, + sizeof(buf), NULL, &buf); + if (err != 0) + return; + + ie = orinoco_get_wpa_ie(buf, sizeof(buf)); + if (ie) { + int rem = sizeof(buf) - (ie - &buf[0]); + wrqu.data.length = ie[1] + 2; + if (wrqu.data.length > rem) + wrqu.data.length = rem; + + if (wrqu.data.length) + /* Send event to user space */ + wireless_send_event(dev, IWEVASSOCRESPIE, &wrqu, ie); + } +} + +static void orinoco_send_wevents(struct work_struct *work) +{ + struct orinoco_private *priv = + container_of(work, struct orinoco_private, wevent_work); + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return; + + orinoco_send_assocreqie_wevent(priv); + orinoco_send_assocrespie_wevent(priv); + orinoco_send_bssid_wevent(priv); + + orinoco_unlock(priv, &flags); +} static inline void orinoco_clear_scan_results(struct orinoco_private *priv, unsigned long scan_age) { - bss_element *bss; - bss_element *tmp_bss; - - /* Blow away current list of scan results */ - list_for_each_entry_safe(bss, tmp_bss, &priv->bss_list, list) { - if (!scan_age || - time_after(jiffies, bss->last_scanned + scan_age)) { - list_move_tail(&bss->list, &priv->bss_free_list); - /* Don't blow away ->list, just BSS data */ - memset(bss, 0, sizeof(bss->bss)); - bss->last_scanned = 0; + if (priv->has_ext_scan) { + struct xbss_element *bss; + struct xbss_element *tmp_bss; + + /* Blow away current list of scan results */ + list_for_each_entry_safe(bss, tmp_bss, &priv->bss_list, list) { + if (!scan_age || + time_after(jiffies, bss->last_scanned + scan_age)) { + list_move_tail(&bss->list, + &priv->bss_free_list); + /* Don't blow away ->list, just BSS data */ + memset(&bss->bss, 0, sizeof(bss->bss)); + bss->last_scanned = 0; + } + } + } else { + struct bss_element *bss; + struct bss_element *tmp_bss; + + /* Blow away current list of scan results */ + list_for_each_entry_safe(bss, tmp_bss, &priv->bss_list, list) { + if (!scan_age || + time_after(jiffies, bss->last_scanned + scan_age)) { + list_move_tail(&bss->list, + &priv->bss_free_list); + /* Don't blow away ->list, just BSS data */ + memset(&bss->bss, 0, sizeof(bss->bss)); + bss->last_scanned = 0; + } } } } +static void orinoco_add_ext_scan_result(struct orinoco_private *priv, + struct agere_ext_scan_info *atom) +{ + struct xbss_element *bss = NULL; + int found = 0; + + /* Try to update an existing bss first */ + list_for_each_entry(bss, &priv->bss_list, list) { + if (compare_ether_addr(bss->bss.bssid, atom->bssid)) + continue; + /* ESSID lengths */ + if (bss->bss.data[1] != atom->data[1]) + continue; + if (memcmp(&bss->bss.data[2], &atom->data[2], + atom->data[1])) + continue; + found = 1; + break; + } + + /* Grab a bss off the free list */ + if (!found && !list_empty(&priv->bss_free_list)) { + bss = list_entry(priv->bss_free_list.next, + struct xbss_element, list); + list_del(priv->bss_free_list.next); + + list_add_tail(&bss->list, &priv->bss_list); + } + + if (bss) { + /* Always update the BSS to get latest beacon info */ + memcpy(&bss->bss, atom, sizeof(bss->bss)); + bss->last_scanned = jiffies; + } +} + static int orinoco_process_scan_results(struct net_device *dev, unsigned char *buf, int len) @@ -1194,7 +1903,7 @@ static int orinoco_process_scan_results(struct net_device *dev, /* Read the entries one by one */ for (; offset + atom_len <= len; offset += atom_len) { int found = 0; - bss_element *bss = NULL; + struct bss_element *bss = NULL; /* Get next atom */ atom = (union hermes_scan_info *) (buf + offset); @@ -1216,7 +1925,7 @@ static int orinoco_process_scan_results(struct net_device *dev, /* Grab a bss off the free list */ if (!found && !list_empty(&priv->bss_free_list)) { bss = list_entry(priv->bss_free_list.next, - bss_element, list); + struct bss_element, list); list_del(priv->bss_free_list.next); list_add_tail(&bss->list, &priv->bss_list); @@ -1404,6 +2113,63 @@ static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw) kfree(buf); } break; + case HERMES_INQ_CHANNELINFO: + { + struct agere_ext_scan_info *bss; + + if (!priv->scan_inprogress) { + printk(KERN_DEBUG "%s: Got chaninfo without scan, " + "len=%d\n", dev->name, len); + break; + } + + /* An empty result indicates that the scan is complete */ + if (len == 0) { + union iwreq_data wrqu; + + /* Scan is no longer in progress */ + priv->scan_inprogress = 0; + + wrqu.data.length = 0; + wrqu.data.flags = 0; + wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL); + break; + } + + /* Sanity check */ + else if (len > sizeof(*bss)) { + printk(KERN_WARNING + "%s: Ext scan results too large (%d bytes). " + "Truncating results to %zd bytes.\n", + dev->name, len, sizeof(*bss)); + len = sizeof(*bss); + } else if (len < (offsetof(struct agere_ext_scan_info, + data) + 2)) { + /* Drop this result now so we don't have to + * keep checking later */ + printk(KERN_WARNING + "%s: Ext scan results too short (%d bytes)\n", + dev->name, len); + break; + } + + bss = kmalloc(sizeof(*bss), GFP_ATOMIC); + if (bss == NULL) + break; + + /* Read scan data */ + err = hermes_bap_pread(hw, IRQ_BAP, (void *) bss, len, + infofid, sizeof(info)); + if (err) { + kfree(bss); + brea |