orinoco: address checkpatch typedef warning
[safe/jmp/linux-2.6] / drivers / net / wireless / orinoco.c
index 9e19a96..3d5570d 100644 (file)
 #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 "hermes_rid.h"
+#include "hermes_dld.h"
 #include "orinoco.h"
 
 /********************************************************************/
@@ -270,6 +272,304 @@ 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)
+               return 0;
+
+       priv->bss_data =
+           kzalloc(ORINOCO_MAX_BSS_COUNT * sizeof(struct bss_element),
+                   GFP_KERNEL);
+       if (!priv->bss_data) {
+               printk(KERN_WARNING "Out of memory allocating beacons");
+               return -ENOMEM;
+       }
+       return 0;
+}
+
+static void orinoco_bss_data_free(struct orinoco_private *priv)
+{
+       kfree(priv->bss_data);
+       priv->bss_data = NULL;
+}
+
+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);
+}
+
+
+/********************************************************************/
+/* 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;
+}
+
 /********************************************************************/
 /* Device methods                                                   */
 /********************************************************************/
@@ -423,7 +723,7 @@ static int orinoco_xmit(struct sk_buff *skb, struct net_device *dev)
        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)) {
@@ -457,21 +757,48 @@ static int orinoco_xmit(struct sk_buff *skb, struct net_device *dev)
 
        eh = (struct ethhdr *)skb->data;
 
-       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;
-       }
+       tx_control = HERMES_TXCTRL_TX_OK | HERMES_TXCTRL_TX_EX;
+
+       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);
+       }
 
        /* Encapsulate Ethernet-II frames */
        if (ntohs(eh->h_proto) > ETH_DATA_LEN) { /* Ethernet-II frame */
@@ -689,7 +1016,7 @@ static void orinoco_stat_gather(struct net_device *dev,
        /* Note : gcc will optimise the whole section away if
         * WIRELESS_SPY is not defined... - Jean II */
        if (SPY_NUMBER(priv)) {
-               orinoco_spy_gather(dev, skb->mac.raw + ETH_ALEN,
+               orinoco_spy_gather(dev, skb_mac_header(skb) + ETH_ALEN,
                                   desc->signal, desc->silence);
        }
 }
@@ -770,7 +1097,7 @@ static void orinoco_rx_monitor(struct net_device *dev, u16 rxfid,
 
        /* Copy the 802.11 header to the skb */
        memcpy(skb_put(skb, hdrlen), &(desc->frame_ctl), hdrlen);
-       skb->mac.raw = skb->data;
+       skb_reset_mac_header(skb);
 
        /* If any, copy the data from the card to the skb */
        if (datalen > 0) {
@@ -915,7 +1242,6 @@ static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw)
                memcpy(hdr->h_source, desc.addr2, ETH_ALEN);
 
        dev->last_rx = jiffies;
-       skb->dev = dev;
        skb->protocol = eth_type_trans(skb, dev);
        skb->ip_summed = CHECKSUM_NONE;
        if (fc & IEEE80211_FCTL_TODS)
@@ -980,9 +1306,11 @@ static void print_linkstatus(struct net_device *dev, u16 status)
 }
 
 /* Search scan results for requested BSSID, join it if found */
-static void orinoco_join_ap(struct net_device *dev)
+static void orinoco_join_ap(struct work_struct *work)
 {
-       struct orinoco_private *priv = netdev_priv(dev);
+       struct orinoco_private *priv =
+               container_of(work, struct orinoco_private, join_work);
+       struct net_device *dev = priv->ndev;
        struct hermes *hw = &priv->hw;
        int err;
        unsigned long flags;
@@ -1055,9 +1383,11 @@ static void orinoco_join_ap(struct net_device *dev)
 }
 
 /* Send new BSSID to userspace */
-static void orinoco_send_wevents(struct net_device *dev)
+static void orinoco_send_wevents(struct work_struct *work)
 {
-       struct orinoco_private *priv = netdev_priv(dev);
+       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;
@@ -1080,6 +1410,124 @@ static void orinoco_send_wevents(struct net_device *dev)
        orinoco_unlock(priv, &flags);
 }
 
+
+static inline void orinoco_clear_scan_results(struct orinoco_private *priv,
+                                             unsigned long scan_age)
+{
+       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, 0, sizeof(bss->bss));
+                       bss->last_scanned = 0;
+               }
+       }
+}
+
+static int orinoco_process_scan_results(struct net_device *dev,
+                                       unsigned char *buf,
+                                       int len)
+{
+       struct orinoco_private *priv = netdev_priv(dev);
+       int                     offset;         /* In the scan data */
+       union hermes_scan_info *atom;
+       int                     atom_len;
+
+       switch (priv->firmware_type) {
+       case FIRMWARE_TYPE_AGERE:
+               atom_len = sizeof(struct agere_scan_apinfo);
+               offset = 0;
+               break;
+       case FIRMWARE_TYPE_SYMBOL:
+               /* Lack of documentation necessitates this hack.
+                * Different firmwares have 68 or 76 byte long atoms.
+                * We try modulo first.  If the length divides by both,
+                * we check what would be the channel in the second
+                * frame for a 68-byte atom.  76-byte atoms have 0 there.
+                * Valid channel cannot be 0.  */
+               if (len % 76)
+                       atom_len = 68;
+               else if (len % 68)
+                       atom_len = 76;
+               else if (len >= 1292 && buf[68] == 0)
+                       atom_len = 76;
+               else
+                       atom_len = 68;
+               offset = 0;
+               break;
+       case FIRMWARE_TYPE_INTERSIL:
+               offset = 4;
+               if (priv->has_hostscan) {
+                       atom_len = le16_to_cpup((__le16 *)buf);
+                       /* Sanity check for atom_len */
+                       if (atom_len < sizeof(struct prism2_scan_apinfo)) {
+                               printk(KERN_ERR "%s: Invalid atom_len in scan "
+                                      "data: %d\n", dev->name, atom_len);
+                               return -EIO;
+                       }
+               } else
+                       atom_len = offsetof(struct prism2_scan_apinfo, atim);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       /* Check that we got an whole number of atoms */
+       if ((len - offset) % atom_len) {
+               printk(KERN_ERR "%s: Unexpected scan data length %d, "
+                      "atom_len %d, offset %d\n", dev->name, len,
+                      atom_len, offset);
+               return -EIO;
+       }
+
+       orinoco_clear_scan_results(priv, msecs_to_jiffies(15000));
+
+       /* Read the entries one by one */
+       for (; offset + atom_len <= len; offset += atom_len) {
+               int found = 0;
+               struct bss_element *bss = NULL;
+
+               /* Get next atom */
+               atom = (union hermes_scan_info *) (buf + offset);
+
+               /* Try to update an existing bss first */
+               list_for_each_entry(bss, &priv->bss_list, list) {
+                       if (compare_ether_addr(bss->bss.a.bssid, atom->a.bssid))
+                               continue;
+                       if (le16_to_cpu(bss->bss.a.essid_len) !=
+                             le16_to_cpu(atom->a.essid_len))
+                               continue;
+                       if (memcmp(bss->bss.a.essid, atom->a.essid,
+                             le16_to_cpu(atom->a.essid_len)))
+                               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 bss_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;
+               }
+       }
+
+       return 0;
+}
+
 static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw)
 {
        struct orinoco_private *priv = netdev_priv(dev);
@@ -1205,6 +1653,9 @@ static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw)
                union iwreq_data        wrqu;
                unsigned char *buf;
 
+               /* Scan is no longer in progress */
+               priv->scan_inprogress = 0;
+
                /* Sanity check */
                if (len > 4096) {
                        printk(KERN_WARNING "%s: Scan results too large (%d bytes)\n",
@@ -1212,15 +1663,6 @@ static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw)
                        break;
                }
 
-               /* We are a strict producer. If the previous scan results
-                * have not been consumed, we just have to drop this
-                * frame. We can't remove the previous results ourselves,
-                * that would be *very* racy... Jean II */
-               if (priv->scan_result != NULL) {
-                       printk(KERN_WARNING "%s: Previous scan results not consumed, dropping info frame.\n", dev->name);
-                       break;
-               }
-
                /* Allocate buffer for results */
                buf = kmalloc(len, GFP_ATOMIC);
                if (buf == NULL)
@@ -1245,18 +1687,17 @@ static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw)
                }
 #endif /* ORINOCO_DEBUG */
 
-               /* Allow the clients to access the results */
-               priv->scan_len = len;
-               priv->scan_result = buf;
-
-               /* Send an empty event to user space.
-                * We don't send the received data on the event because
-                * it would require us to do complex transcoding, and
-                * we want to minimise the work done in the irq handler
-                * Use a request to extract the data - Jean II */
-               wrqu.data.length = 0;
-               wrqu.data.flags = 0;
-               wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
+               if (orinoco_process_scan_results(dev, buf, len) == 0) {
+                       /* Send an empty event to user space.
+                        * We don't send the received data on the event because
+                        * it would require us to do complex transcoding, and
+                        * we want to minimise the work done in the irq handler
+                        * Use a request to extract the data - Jean II */
+                       wrqu.data.length = 0;
+                       wrqu.data.flags = 0;
+                       wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
+               }
+               kfree(buf);
        }
        break;
        case HERMES_INQ_SEC_STAT_AGERE:
@@ -1853,20 +2294,15 @@ __orinoco_set_multicast_list(struct net_device *dev)
                else
                        priv->mc_count = mc_count;
        }
-
-       /* Since we can set the promiscuous flag when it wasn't asked
-          for, make sure the net_device knows about it. */
-       if (priv->promiscuous)
-               dev->flags |= IFF_PROMISC;
-       else
-               dev->flags &= ~IFF_PROMISC;
 }
 
 /* This must be called from user context, without locks held - use
  * schedule_work() */
-static void orinoco_reset(struct net_device *dev)
+static void orinoco_reset(struct work_struct *work)
 {
-       struct orinoco_private *priv = netdev_priv(dev);
+       struct orinoco_private *priv =
+               container_of(work, struct orinoco_private, reset_work);
+       struct net_device *dev = priv->ndev;
        struct hermes *hw = &priv->hw;
        int err;
        unsigned long flags;
@@ -1891,8 +2327,7 @@ static void orinoco_reset(struct net_device *dev)
        orinoco_unlock(priv, &flags);
 
        /* Scanning support: Cleanup of driver struct */
-       kfree(priv->scan_result);
-       priv->scan_result = NULL;
+       orinoco_clear_scan_results(priv, 0);
        priv->scan_inprogress = 0;
 
        if (priv->hard_reset) {
@@ -1904,6 +2339,12 @@ static void orinoco_reset(struct net_device *dev)
                }
        }
 
+       if (priv->do_fw_download) {
+               err = orinoco_download(priv);
+               if (err)
+                       priv->do_fw_download = 0;
+       }
+
        err = orinoco_reinit_firmware(dev);
        if (err) {
                printk(KERN_ERR "%s: orinoco_reset: Error %d re-initializing firmware\n",
@@ -1952,9 +2393,9 @@ static void __orinoco_ev_wterr(struct net_device *dev, hermes_t *hw)
               dev->name);
 }
 
-irqreturn_t orinoco_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+irqreturn_t orinoco_interrupt(int irq, void *dev_id)
 {
-       struct net_device *dev = (struct net_device *)dev_id;
+       struct net_device *dev = dev_id;
        struct orinoco_private *priv = netdev_priv(dev);
        hermes_t *hw = &priv->hw;
        int count = MAX_IRQLOOPS_PER_IRQ;
@@ -2053,7 +2494,7 @@ static int determine_firmware(struct net_device *dev)
        int err;
        struct comp_id nic_id, sta_id;
        unsigned int firmver;
-       char tmp[SYMBOL_MAX_VER_LEN+1];
+       char tmp[SYMBOL_MAX_VER_LEN+1] __attribute__((aligned(2)));
 
        /* Get the hardware version */
        err = HERMES_READ_RECORD(hw, USER_BAP, HERMES_RID_NICID, &nic_id);
@@ -2115,6 +2556,8 @@ static int determine_firmware(struct net_device *dev)
        priv->has_ibss = 1;
        priv->has_wep = 0;
        priv->has_big_wep = 0;
+       priv->has_alt_txcntl = 0;
+       priv->do_fw_download = 0;
 
        /* Determine capabilities from the firmware version */
        switch (priv->firmware_type) {
@@ -2134,7 +2577,9 @@ static int determine_firmware(struct net_device *dev)
                priv->has_pm = (firmver >= 0x40020); /* Don't work in 7.52 ? */
                priv->ibss_port = 1;
                priv->has_hostscan = (firmver >= 0x8000a);
+               priv->do_fw_download = 1;
                priv->broken_monitor = (firmver >= 0x80000);
+               priv->has_alt_txcntl = (firmver >= 0x90000); /* All 9.x ? */
 
                /* Tested with Agere firmware :
                 *      1.16 ; 4.08 ; 4.52 ; 6.04 ; 6.16 ; 7.28 => Jean II
@@ -2178,6 +2623,21 @@ static int determine_firmware(struct net_device *dev)
                               firmver >= 0x31000;
                priv->has_preamble = (firmver >= 0x20000);
                priv->ibss_port = 4;
+
+               /* Symbol firmware is found on various cards, but
+                * there has been no attempt to check firmware
+                * download on non-spectrum_cs based cards.
+                *
+                * Given that the Agere firmware download works
+                * differently, we should avoid doing a firmware
+                * download with the Symbol algorithm on non-spectrum
+                * cards.
+                *
+                * For now we can identify a spectrum_cs based card
+                * because it has a firmware reset function.
+                */
+               priv->do_fw_download = (priv->stop_fw != NULL);
+
                priv->broken_disableport = (firmver == 0x25013) ||
                                           (firmver >= 0x30000 && firmver <= 0x31000);
                priv->has_hostscan = (firmver >= 0x31001) ||
@@ -2227,6 +2687,7 @@ static int orinoco_init(struct net_device *dev)
        struct hermes_idstring nickbuf;
        u16 reclen;
        int len;
+       DECLARE_MAC_BUF(mac);
 
        /* No need to lock, the hw_unavailable flag is already set in
         * alloc_orinocodev() */
@@ -2247,6 +2708,20 @@ static int orinoco_init(struct net_device *dev)
                goto out;
        }
 
+       if (priv->do_fw_download) {
+               err = orinoco_download(priv);
+               if (err)
+                       priv->do_fw_download = 0;
+
+               /* Check firmware version again */
+               err = determine_firmware(dev);
+               if (err != 0) {
+                       printk(KERN_ERR "%s: Incompatible firmware, aborting\n",
+                              dev->name);
+                       goto out;
+               }
+       }
+
        if (priv->has_port3)
                printk(KERN_DEBUG "%s: Ad-hoc demo mode supported\n", dev->name);
        if (priv->has_ibss)
@@ -2269,10 +2744,8 @@ static int orinoco_init(struct net_device *dev)
                goto out;
        }
 
-       printk(KERN_DEBUG "%s: MAC address %02X:%02X:%02X:%02X:%02X:%02X\n",
-              dev->name, dev->dev_addr[0], dev->dev_addr[1],
-              dev->dev_addr[2], dev->dev_addr[3], dev->dev_addr[4],
-              dev->dev_addr[5]);
+       printk(KERN_DEBUG "%s: MAC address %s\n",
+              dev->name, print_mac(mac, dev->dev_addr));
 
        /* Get the station name */
        err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CNFOWNNAME,
@@ -2391,8 +2864,11 @@ static int orinoco_init(struct net_device *dev)
        return err;
 }
 
-struct net_device *alloc_orinocodev(int sizeof_card,
-                                   int (*hard_reset)(struct orinoco_private *))
+struct net_device
+*alloc_orinocodev(int sizeof_card,
+                 struct device *device,
+                 int (*hard_reset)(struct orinoco_private *),
+                 int (*stop_fw)(struct orinoco_private *, int))
 {
        struct net_device *dev;
        struct orinoco_private *priv;
@@ -2407,6 +2883,11 @@ struct net_device *alloc_orinocodev(int sizeof_card,
                                      + sizeof(struct orinoco_private));
        else
                priv->card = NULL;
+       priv->dev = device;
+
+       if (orinoco_bss_data_allocate(priv))
+               goto err_out_free;
+       orinoco_bss_data_init(priv);
 
        /* Setup / override net_device fields */
        dev->init = orinoco_init;
@@ -2428,28 +2909,32 @@ struct net_device *alloc_orinocodev(int sizeof_card,
        dev->open = orinoco_open;
        dev->stop = orinoco_stop;
        priv->hard_reset = hard_reset;
+       priv->stop_fw = stop_fw;
 
        spin_lock_init(&priv->lock);
        priv->open = 0;
        priv->hw_unavailable = 1; /* orinoco_init() must clear this
                                   * before anything else touches the
                                   * hardware */
-       INIT_WORK(&priv->reset_work, (void (*)(void *))orinoco_reset, dev);
-       INIT_WORK(&priv->join_work, (void (*)(void *))orinoco_join_ap, dev);
-       INIT_WORK(&priv->wevent_work, (void (*)(void *))orinoco_send_wevents, dev);
+       INIT_WORK(&priv->reset_work, orinoco_reset);
+       INIT_WORK(&priv->join_work, orinoco_join_ap);
+       INIT_WORK(&priv->wevent_work, orinoco_send_wevents);
 
        netif_carrier_off(dev);
        priv->last_linkstatus = 0xffff;
 
        return dev;
 
+err_out_free:
+       free_netdev(dev);
+       return NULL;
 }
 
 void free_orinocodev(struct net_device *dev)
 {
        struct orinoco_private *priv = netdev_priv(dev);
 
-       kfree(priv->scan_result);
+       orinoco_bss_data_free(priv);
        free_netdev(dev);
 }
 
@@ -2457,6 +2942,7 @@ void free_orinocodev(struct net_device *dev)
 /* Wireless extensions                                              */
 /********************************************************************/
 
+/* Return : < 0 -> error code ; >= 0 -> length */
 static int orinoco_hw_get_essid(struct orinoco_private *priv, int *active,
                                char buf[IW_ESSID_MAX_SIZE+1])
 {
@@ -2501,9 +2987,9 @@ static int orinoco_hw_get_essid(struct orinoco_private *priv, int *active,
        len = le16_to_cpu(essidbuf.len);
        BUG_ON(len > IW_ESSID_MAX_SIZE);
 
-       memset(buf, 0, IW_ESSID_MAX_SIZE+1);
+       memset(buf, 0, IW_ESSID_MAX_SIZE);
        memcpy(buf, p, len);
-       buf[len] = '\0';
+       err = len;
 
  fail_unlock:
        orinoco_unlock(priv, &flags);
@@ -2836,6 +3322,11 @@ static int orinoco_ioctl_getiwrange(struct net_device *dev,
        range->min_r_time = 0;
        range->max_r_time = 65535 * 1000;       /* ??? */
 
+       if (priv->firmware_type == FIRMWARE_TYPE_AGERE)
+               range->scan_capa = IW_SCAN_CAPA_ESSID;
+       else
+               range->scan_capa = IW_SCAN_CAPA_NONE;
+
        /* Event capability (kernel) */
        IW_EVENT_CAPA_SET_KERNEL(range->event_capa);
        /* Event capability (driver) */
@@ -3027,17 +3518,18 @@ static int orinoco_ioctl_getessid(struct net_device *dev,
 
        if (netif_running(dev)) {
                err = orinoco_hw_get_essid(priv, &active, essidbuf);
-               if (err)
+               if (err < 0)
                        return err;
+               erq->length = err;
        } else {
                if (orinoco_lock(priv, &flags) != 0)
                        return -EBUSY;
-               memcpy(essidbuf, priv->desired_essid, IW_ESSID_MAX_SIZE + 1);
+               memcpy(essidbuf, priv->desired_essid, IW_ESSID_MAX_SIZE);
+               erq->length = strlen(priv->desired_essid);
                orinoco_unlock(priv, &flags);
        }
 
        erq->flags = 1;
-       erq->length = strlen(essidbuf);
 
        return 0;
 }
@@ -3075,10 +3567,10 @@ static int orinoco_ioctl_getnick(struct net_device *dev,
        if (orinoco_lock(priv, &flags) != 0)
                return -EBUSY;
 
-       memcpy(nickbuf, priv->nick, IW_ESSID_MAX_SIZE+1);
+       memcpy(nickbuf, priv->nick, IW_ESSID_MAX_SIZE);
        orinoco_unlock(priv, &flags);
 
-       nrq->length = strlen(nickbuf);
+       nrq->length = strlen(priv->nick);
 
        return 0;
 }
@@ -3606,7 +4098,7 @@ static int orinoco_ioctl_reset(struct net_device *dev,
                printk(KERN_DEBUG "%s: Forcing reset!\n", dev->name);
 
                /* Firmware reset */
-               orinoco_reset(dev);
+               orinoco_reset(&priv->reset_work);
        } else {
                printk(KERN_DEBUG "%s: Force scheduling reset!\n", dev->name);
 
@@ -3796,7 +4288,7 @@ static int orinoco_ioctl_getrid(struct net_device *dev,
        return err;
 }
 
-/* Trigger a scan (look for other cells in the vicinity */
+/* Trigger a scan (look for other cells in the vicinity) */
 static int orinoco_ioctl_setscan(struct net_device *dev,
                                 struct iw_request_info *info,
                                 struct iw_param *srq,
@@ -3804,6 +4296,7 @@ static int orinoco_ioctl_setscan(struct net_device *dev,
 {
        struct orinoco_private *priv = netdev_priv(dev);
        hermes_t *hw = &priv->hw;
+       struct iw_scan_req *si = (struct iw_scan_req *) extra;
        int err = 0;
        unsigned long flags;
 
@@ -3835,23 +4328,9 @@ static int orinoco_ioctl_setscan(struct net_device *dev,
         * we access scan variables in priv is critical.
         *      o scan_inprogress : not touched by irq handler
         *      o scan_mode : not touched by irq handler
-        *      o scan_result : irq is strict producer, non-irq is strict
-        *              consumer.
-        *      o scan_len : synchronised with scan_result
         * Before modifying anything on those variables, please think hard !
         * Jean II */
 
-       /* If there is still some left-over scan results, get rid of it */
-       if (priv->scan_result != NULL) {
-               /* What's likely is that a client did crash or was killed
-                * between triggering the scan request and reading the
-                * results, so we need to reset everything.
-                * Some clients that are too slow may suffer from that...
-                * Jean II */
-               kfree(priv->scan_result);
-               priv->scan_result = NULL;
-       }
-
        /* Save flags */
        priv->scan_mode = srq->flags;
 
@@ -3878,7 +4357,19 @@ static int orinoco_ioctl_setscan(struct net_device *dev,
                }
                break;
                case FIRMWARE_TYPE_AGERE:
-                       err = hermes_write_wordrec(hw, USER_BAP,
+                       if (priv->scan_mode & IW_SCAN_THIS_ESSID) {
+                               struct hermes_idstring idbuf;
+                               size_t len = min(sizeof(idbuf.val),
+                                                (size_t) si->essid_len);
+                               idbuf.len = cpu_to_le16(len);
+                               memcpy(idbuf.val, si->essid, len);
+
+                               err = hermes_write_ltv(hw, USER_BAP,
+                                              HERMES_RID_CNFSCANSSID_AGERE,
+                                              HERMES_BYTES_TO_RECLEN(len + 2),
+                                              &idbuf);
+                       } else
+                               err = hermes_write_wordrec(hw, USER_BAP,
                                                   HERMES_RID_CNFSCANSSID_AGERE,
                                                   0);  /* Any ESSID */
                        if (err)
@@ -3899,169 +4390,155 @@ static int orinoco_ioctl_setscan(struct net_device *dev,
        return err;
 }
 
+#define MAX_CUSTOM_LEN 64
+
 /* Translate scan data returned from the card to a card independant
- * format that the Wireless Tools will understand - Jean II
- * Return message length or -errno for fatal errors */
-static inline int orinoco_translate_scan(struct net_device *dev,
-                                        char *buffer,
-                                        char *scan,
-                                        int scan_len)
+ * format that the Wireless Tools will understand - Jean II */
+static inline char *orinoco_translate_scan(struct net_device *dev,
+                                          struct iw_request_info *info,
+                                          char *current_ev,
+                                          char *end_buf,
+                                          union hermes_scan_info *bss,
+                                          unsigned int last_scanned)
 {
        struct orinoco_private *priv = netdev_priv(dev);
-       int                     offset;         /* In the scan data */
-       union hermes_scan_info *atom;
-       int                     atom_len;
        u16                     capabilities;
        u16                     channel;
        struct iw_event         iwe;            /* Temporary buffer */
-       char *                  current_ev = buffer;
-       char *                  end_buf = buffer + IW_SCAN_MAX_DATA;
-
-       switch (priv->firmware_type) {
-       case FIRMWARE_TYPE_AGERE:
-               atom_len = sizeof(struct agere_scan_apinfo);
-               offset = 0;
-               break;
-       case FIRMWARE_TYPE_SYMBOL:
-               /* Lack of documentation necessitates this hack.
-                * Different firmwares have 68 or 76 byte long atoms.
-                * We try modulo first.  If the length divides by both,
-                * we check what would be the channel in the second
-                * frame for a 68-byte atom.  76-byte atoms have 0 there.
-                * Valid channel cannot be 0.  */
-               if (scan_len % 76)
-                       atom_len = 68;
-               else if (scan_len % 68)
-                       atom_len = 76;
-               else if (scan_len >= 1292 && scan[68] == 0)
-                       atom_len = 76;
+       char custom[MAX_CUSTOM_LEN];
+
+       memset(&iwe, 0, sizeof(iwe));
+
+       /* First entry *MUST* be the AP MAC address */
+       iwe.cmd = SIOCGIWAP;
+       iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
+       memcpy(iwe.u.ap_addr.sa_data, bss->a.bssid, ETH_ALEN);
+       current_ev = iwe_stream_add_event(info, current_ev, end_buf,
+                                         &iwe, IW_EV_ADDR_LEN);
+
+       /* Other entries will be displayed in the order we give them */
+
+       /* Add the ESSID */
+       iwe.u.data.length = le16_to_cpu(bss->a.essid_len);
+       if (iwe.u.data.length > 32)
+               iwe.u.data.length = 32;
+       iwe.cmd = SIOCGIWESSID;
+       iwe.u.data.flags = 1;
+       current_ev = iwe_stream_add_point(info, current_ev, end_buf,
+                                         &iwe, bss->a.essid);
+
+       /* Add mode */
+       iwe.cmd = SIOCGIWMODE;
+       capabilities = le16_to_cpu(bss->a.capabilities);
+       if (capabilities & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)) {
+               if (capabilities & WLAN_CAPABILITY_ESS)
+                       iwe.u.mode = IW_MODE_MASTER;
                else
-                       atom_len = 68;
-               offset = 0;
-               break;
-       case FIRMWARE_TYPE_INTERSIL:
-               offset = 4;
-               if (priv->has_hostscan) {
-                       atom_len = le16_to_cpup((__le16 *)scan);
-                       /* Sanity check for atom_len */
-                       if (atom_len < sizeof(struct prism2_scan_apinfo)) {
-                               printk(KERN_ERR "%s: Invalid atom_len in scan data: %d\n",
-                               dev->name, atom_len);
-                               return -EIO;
-                       }
-               } else
-                       atom_len = offsetof(struct prism2_scan_apinfo, atim);
-               break;
-       default:
-               return -EOPNOTSUPP;
-       }
-
-       /* Check that we got an whole number of atoms */
-       if ((scan_len - offset) % atom_len) {
-               printk(KERN_ERR "%s: Unexpected scan data length %d, "
-                      "atom_len %d, offset %d\n", dev->name, scan_len,
-                      atom_len, offset);
-               return -EIO;
-       }
-
-       /* Read the entries one by one */
-       for (; offset + atom_len <= scan_len; offset += atom_len) {
-               /* Get next atom */
-               atom = (union hermes_scan_info *) (scan + offset);
-
-               /* First entry *MUST* be the AP MAC address */
-               iwe.cmd = SIOCGIWAP;
-               iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
-               memcpy(iwe.u.ap_addr.sa_data, atom->a.bssid, ETH_ALEN);
-               current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_ADDR_LEN);
-
-               /* Other entries will be displayed in the order we give them */
-
-               /* Add the ESSID */
-               iwe.u.data.length = le16_to_cpu(atom->a.essid_len);
-               if (iwe.u.data.length > 32)
-                       iwe.u.data.length = 32;
-               iwe.cmd = SIOCGIWESSID;
-               iwe.u.data.flags = 1;
-               current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, atom->a.essid);
-
-               /* Add mode */
-               iwe.cmd = SIOCGIWMODE;
-               capabilities = le16_to_cpu(atom->a.capabilities);
-               if (capabilities & 0x3) {
-                       if (capabilities & 0x1)
-                               iwe.u.mode = IW_MODE_MASTER;
-                       else
-                               iwe.u.mode = IW_MODE_ADHOC;
-                       current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_UINT_LEN);
-               }
-
-               channel = atom->s.channel;
-               if ( (channel >= 1) && (channel <= NUM_CHANNELS) ) {
-                       /* Add frequency */
-                       iwe.cmd = SIOCGIWFREQ;
-                       iwe.u.freq.m = channel_frequency[channel-1] * 100000;
-                       iwe.u.freq.e = 1;
-                       current_ev = iwe_stream_add_event(current_ev, end_buf,
-                                                         &iwe, IW_EV_FREQ_LEN);
-               }
-
-               /* Add quality statistics */
-               iwe.cmd = IWEVQUAL;
-               iwe.u.qual.updated = 0x10;      /* no link quality */
-               iwe.u.qual.level = (__u8) le16_to_cpu(atom->a.level) - 0x95;
-               iwe.u.qual.noise = (__u8) le16_to_cpu(atom->a.noise) - 0x95;
-               /* Wireless tools prior to 27.pre22 will show link quality
-                * anyway, so we provide a reasonable value. */
-               if (iwe.u.qual.level > iwe.u.qual.noise)
-                       iwe.u.qual.qual = iwe.u.qual.level - iwe.u.qual.noise;
-               else
-                       iwe.u.qual.qual = 0;
-               current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_QUAL_LEN);
+                       iwe.u.mode = IW_MODE_ADHOC;
+               current_ev = iwe_stream_add_event(info, current_ev, end_buf,
+                                                 &iwe, IW_EV_UINT_LEN);
+       }
+
+       channel = bss->s.channel;
+       if ((channel >= 1) && (channel <= NUM_CHANNELS)) {
+               /* Add channel and frequency */
+               iwe.cmd = SIOCGIWFREQ;
+               iwe.u.freq.m = channel;
+               iwe.u.freq.e = 0;
+               current_ev = iwe_stream_add_event(info, current_ev, end_buf,
+                                                 &iwe, IW_EV_FREQ_LEN);
+
+               iwe.u.freq.m = channel_frequency[channel-1] * 100000;
+               iwe.u.freq.e = 1;
+               current_ev = iwe_stream_add_event(info, current_ev, end_buf,
+                                                 &iwe, IW_EV_FREQ_LEN);
+       }
+
+       /* Add quality statistics. level and noise in dB. No link quality */
+       iwe.cmd = IWEVQUAL;
+       iwe.u.qual.updated = IW_QUAL_DBM | IW_QUAL_QUAL_INVALID;
+       iwe.u.qual.level = (__u8) le16_to_cpu(bss->a.level) - 0x95;
+       iwe.u.qual.noise = (__u8) le16_to_cpu(bss->a.noise) - 0x95;
+       /* Wireless tools prior to 27.pre22 will show link quality
+        * anyway, so we provide a reasonable value. */
+       if (iwe.u.qual.level > iwe.u.qual.noise)
+               iwe.u.qual.qual = iwe.u.qual.level - iwe.u.qual.noise;
+       else
+               iwe.u.qual.qual = 0;
+       current_ev = iwe_stream_add_event(info, current_ev, end_buf,
+                                         &iwe, IW_EV_QUAL_LEN);
+
+       /* Add encryption capability */
+       iwe.cmd = SIOCGIWENCODE;
+       if (capabilities & WLAN_CAPABILITY_PRIVACY)
+               iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
+       else
+               iwe.u.data.flags = IW_ENCODE_DISABLED;
+       iwe.u.data.length = 0;
+       current_ev = iwe_stream_add_point(info, current_ev, end_buf,
+                                         &iwe, NULL);
+
+       /* Bit rate is not available in Lucent/Agere firmwares */
+       if (priv->firmware_type != FIRMWARE_TYPE_AGERE) {
+               char *current_val = current_ev + iwe_stream_lcp_len(info);
+               int i;
+               int step;
 
-               /* Add encryption capability */
-               iwe.cmd = SIOCGIWENCODE;
-               if (capabilities & 0x10)
-                       iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
+               if (priv->firmware_type == FIRMWARE_TYPE_SYMBOL)
+                       step = 2;
                else
-                       iwe.u.data.flags = IW_ENCODE_DISABLED;
-               iwe.u.data.length = 0;
-               current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, atom->a.essid);
-
-               /* Bit rate is not available in Lucent/Agere firmwares */
-               if (priv->firmware_type != FIRMWARE_TYPE_AGERE) {
-                       char *  current_val = current_ev + IW_EV_LCP_LEN;
-                       int     i;
-                       int     step;
-
-                       if (priv->firmware_type == FIRMWARE_TYPE_SYMBOL)
-                               step = 2;
-                       else
-                               step = 1;
-
-                       iwe.cmd = SIOCGIWRATE;
-                       /* Those two flags are ignored... */
-                       iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
-                       /* Max 10 values */
-                       for (i = 0; i < 10; i += step) {
-                               /* NULL terminated */
-                               if (atom->p.rates[i] == 0x0)
-                                       break;
-                               /* Bit rate given in 500 kb/s units (+ 0x80) */
-                               iwe.u.bitrate.value = ((atom->p.rates[i] & 0x7f) * 500000);
-                               current_val = iwe_stream_add_value(current_ev, current_val,
-                                                                  end_buf, &iwe,
-                                                                  IW_EV_PARAM_LEN);
-                       }
-                       /* Check if we added any event */
-                       if ((current_val - current_ev) > IW_EV_LCP_LEN)
-                               current_ev = current_val;
+                       step = 1;
+
+               iwe.cmd = SIOCGIWRATE;
+               /* Those two flags are ignored... */
+               iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
+               /* Max 10 values */
+               for (i = 0; i < 10; i += step) {
+                       /* NULL terminated */
+                       if (bss->p.rates[i] == 0x0)
+                               break;
+                       /* Bit rate given in 500 kb/s units (+ 0x80) */
+                       iwe.u.bitrate.value =
+                               ((bss->p.rates[i] & 0x7f) * 500000);
+                       current_val = iwe_stream_add_value(info, current_ev,
+                                                          current_val,
+                                                          end_buf, &iwe,
+                                                          IW_EV_PARAM_LEN);
                }
-
-               /* The other data in the scan result are not really
-                * interesting, so for now drop it - Jean II */
-       }
-       return current_ev - buffer;
+               /* Check if we added any event */
+               if ((current_val - current_ev) > iwe_stream_lcp_len(info))
+                       current_ev = current_val;
+       }
+
+       /* Beacon interval */
+       iwe.cmd = IWEVCUSTOM;
+       iwe.u.data.length = snprintf(custom, MAX_CUSTOM_LEN,
+                                    "bcn_int=%d",
+                                    le16_to_cpu(bss->a.beacon_interv));
+       if (iwe.u.data.length)
+               current_ev = iwe_stream_add_point(info, current_ev, end_buf,
+                                                 &iwe, custom);
+
+       /* Capabilites */
+       iwe.cmd = IWEVCUSTOM;
+       iwe.u.data.length = snprintf(custom, MAX_CUSTOM_LEN,
+                                    "capab=0x%04x",
+                                    capabilities);
+       if (iwe.u.data.length)
+               current_ev = iwe_stream_add_point(info, current_ev, end_buf,
+                                                 &iwe, custom);
+
+       /* Add EXTRA: Age to display seconds since last beacon/probe response
+        * for given network. */
+       iwe.cmd = IWEVCUSTOM;
+       iwe.u.data.length = snprintf(custom, MAX_CUSTOM_LEN,
+                                    " Last beacon: %dms ago",
+                                    jiffies_to_msecs(jiffies - last_scanned));
+       if (iwe.u.data.length)
+               current_ev = iwe_stream_add_point(info, current_ev, end_buf,
+                                                 &iwe, custom);
+
+       return current_ev;
 }
 
 /* Return results of a scan */
@@ -4071,68 +4548,45 @@ static int orinoco_ioctl_getscan(struct net_device *dev,
                                 char *extra)
 {
        struct orinoco_private *priv = netdev_priv(dev);
+       struct bss_element *bss;
        int err = 0;
        unsigned long flags;
+       char *current_ev = extra;
 
        if (orinoco_lock(priv, &flags) != 0)
                return -EBUSY;
 
-       /* If no results yet, ask to try again later */
-       if (priv->scan_result == NULL) {
-               if (priv->scan_inprogress)
-                       /* Important note : we don't want to block the caller
-                        * until results are ready for various reasons.
-                        * First, managing wait queues is complex and racy.
-                        * Second, we grab some rtnetlink lock before comming
-                        * here (in dev_ioctl()).
-                        * Third, we generate an Wireless Event, so the
-                        * caller can wait itself on that - Jean II */
-                       err = -EAGAIN;
-               else
-                       /* Client error, no scan results...
-                        * The caller need to restart the scan. */
-                       err = -ENODATA;
-       } else {
-               /* We have some results to push back to user space */
-
-               /* Translate to WE format */
-               int ret = orinoco_translate_scan(dev, extra,
-                                                priv->scan_result,
-                                                priv->scan_len);
-
-               if (ret < 0) {
-                       err = ret;
-                       kfree(priv->scan_result);
-                       priv->scan_result = NULL;
-               } else {
-                       srq->length = ret;
-
-                       /* Return flags */
-                       srq->flags = (__u16) priv->scan_mode;
+       if (priv->scan_inprogress) {
+               /* Important note : we don't want to block the caller
+                * until results are ready for various reasons.
+                * First, managing wait queues is complex and racy.
+                * Second, we grab some rtnetlink lock before comming
+                * here (in dev_ioctl()).
+                * Third, we generate an Wireless Event, so the
+                * caller can wait itself on that - Jean II */
+               err = -EAGAIN;
+               goto out;
+       }
 
-                       /* In any case, Scan results will be cleaned up in the
-                        * reset function and when exiting the driver.
-                        * The person triggering the scanning may never come to
-                        * pick the results, so we need to do it in those places.
-                        * Jean II */
+       list_for_each_entry(bss, &priv->bss_list, list) {
+               /* Translate to WE format this entry */
+               current_ev = orinoco_translate_scan(dev, info, current_ev,
+                                                   extra + srq->length,
+                                                   &bss->bss,
+                                                   bss->last_scanned);
 
-#ifdef SCAN_SINGLE_READ
-                       /* If you enable this option, only one client (the first
-                        * one) will be able to read the result (and only one
-                        * time). If there is multiple concurent clients that
-                        * want to read scan results, this behavior is not
-                        * advisable - Jean II */
-                       kfree(priv->scan_result);
-                       priv->scan_result = NULL;
-#endif /* SCAN_SINGLE_READ */
-                       /* Here, if too much time has elapsed since last scan,
-                        * we may want to clean up scan results... - Jean II */
+               /* Check if there is space for one more entry */
+               if ((extra + srq->length - current_ev) <= IW_EV_ADDR_LEN) {
+                       /* Ask user space to try again with a bigger buffer */
+                       err = -E2BIG;
+                       goto out;
                }
-
-               /* Scan is no longer in progress */
-               priv->scan_inprogress = 0;
        }
-         
+
+       srq->length = (current_ev - extra);
+       srq->flags = (__u16) priv->scan_mode;
+
+out:
        orinoco_unlock(priv, &flags);
        return err;
 }
@@ -4152,7 +4606,7 @@ static int orinoco_ioctl_commit(struct net_device *dev,
                return 0;
 
        if (priv->broken_disableport) {
-               orinoco_reset(dev);
+               orinoco_reset(&priv->reset_work);
                return 0;
        }
 
@@ -4285,8 +4739,8 @@ static void orinoco_get_drvinfo(struct net_device *dev,
        strncpy(info->driver, DRIVER_NAME, sizeof(info->driver) - 1);
        strncpy(info->version, DRIVER_VERSION, sizeof(info->version) - 1);
        strncpy(info->fw_version, priv->fw_name, sizeof(info->fw_version) - 1);
-       if (dev->class_dev.dev)
-               strncpy(info->bus_info, dev->class_dev.dev->bus_id,
+       if (dev->dev.parent)
+               strncpy(info->bus_info, dev->dev.parent->bus_id,
                        sizeof(info->bus_info) - 1);
        else
                snprintf(info->bus_info, sizeof(info->bus_info) - 1,