]> err.no Git - linux-2.6/commitdiff
[PATCH] orinoco: manual roaming for Symbol and Intersilfirmware
authorChristoph Hellwig <hch@lst.de>
Sat, 18 Jun 2005 23:27:51 +0000 (01:27 +0200)
committerJeff Garzik <jgarzik@pobox.com>
Mon, 27 Jun 2005 04:23:55 +0000 (00:23 -0400)
Patch from Pavel Roskin

drivers/net/wireless/orinoco.c
drivers/net/wireless/orinoco.h

index b5f626cae98ccd0f8cf1104392ef36d98c03e0da..c057b7f9a02a58b872ba348c33f937bceecf3f62 100644 (file)
@@ -1247,6 +1247,75 @@ static void print_linkstatus(struct net_device *dev, u16 status)
               dev->name, s, status);
 }
 
+/* Search scan results for requested BSSID, join it if found */
+static void orinoco_join_ap(struct net_device *dev)
+{
+       struct orinoco_private *priv = netdev_priv(dev);
+       struct hermes *hw = &priv->hw;
+       int err;
+       unsigned long flags;
+       struct join_req {
+               u8 bssid[ETH_ALEN];
+               u16 channel;
+       } __attribute__ ((packed)) req;
+       const int atom_len = offsetof(struct prism2_scan_apinfo, atim);
+       struct prism2_scan_apinfo *atom;
+       int offset = 4;
+       u8 *buf;
+       u16 len;
+
+       /* Allocate buffer for scan results */
+       buf = kmalloc(MAX_SCAN_LEN, GFP_KERNEL);
+       if (! buf)
+               return;
+
+       if (orinoco_lock(priv, &flags) != 0)
+               goto out;
+
+       /* Sanity checks in case user changed something in the meantime */
+       if (! priv->bssid_fixed)
+               goto out;
+
+       if (strlen(priv->desired_essid) == 0)
+               goto out;
+
+       /* Read scan results from the firmware */
+       err = hermes_read_ltv(hw, USER_BAP,
+                             HERMES_RID_SCANRESULTSTABLE,
+                             MAX_SCAN_LEN, &len, buf);
+       if (err) {
+               printk(KERN_ERR "%s: Cannot read scan results\n",
+                      dev->name);
+               goto out;
+       }
+
+       len = HERMES_RECLEN_TO_BYTES(len);
+
+       /* Go through the scan results looking for the channel of the AP
+        * we were requested to join */
+       for (; offset + atom_len <= len; offset += atom_len) {
+               atom = (struct prism2_scan_apinfo *) (buf + offset);
+               if (memcmp(&atom->bssid, priv->desired_bssid, ETH_ALEN) == 0)
+                       goto found;
+       }
+
+       DEBUG(1, "%s: Requested AP not found in scan results\n",
+             dev->name);
+       goto out;
+
+ found:
+       memcpy(req.bssid, priv->desired_bssid, ETH_ALEN);
+       req.channel = atom->channel;    /* both are little-endian */
+       err = HERMES_WRITE_RECORD(hw, USER_BAP, HERMES_RID_CNFJOINREQUEST,
+                                 &req);
+       if (err)
+               printk(KERN_ERR "%s: Error issuing join request\n", dev->name);
+
+ out:
+       kfree(buf);
+       orinoco_unlock(priv, &flags);
+}
+
 static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw)
 {
        struct orinoco_private *priv = netdev_priv(dev);
@@ -1477,6 +1546,36 @@ static int __orinoco_hw_set_bitrate(struct orinoco_private *priv)
        return err;
 }
 
+/* Set fixed AP address */
+static int __orinoco_hw_set_wap(struct orinoco_private *priv)
+{
+       int roaming_flag;
+       int err = 0;
+       hermes_t *hw = &priv->hw;
+
+       switch (priv->firmware_type) {
+       case FIRMWARE_TYPE_AGERE:
+               /* not supported */
+               break;
+       case FIRMWARE_TYPE_INTERSIL:
+               if (priv->bssid_fixed)
+                       roaming_flag = 2;
+               else
+                       roaming_flag = 1;
+
+               err = hermes_write_wordrec(hw, USER_BAP,
+                                          HERMES_RID_CNFROAMINGMODE,
+                                          roaming_flag);
+               break;
+       case FIRMWARE_TYPE_SYMBOL:
+               err = HERMES_WRITE_RECORD(hw, USER_BAP,
+                                         HERMES_RID_CNFMANDATORYBSSID_SYMBOL,
+                                         &priv->desired_bssid);
+               break;
+       }
+       return err;
+}
+
 /* Change the WEP keys and/or the current keys.  Can be called
  * either from __orinoco_hw_setup_wep() or directly from
  * orinoco_ioctl_setiwencode().  In the later case the association
@@ -1662,6 +1761,13 @@ static int __orinoco_program_rids(struct net_device *dev)
                }
        }
 
+       /* Set the desired BSSID */
+       err = __orinoco_hw_set_wap(priv);
+       if (err) {
+               printk(KERN_ERR "%s: Error %d setting AP address\n",
+                      dev->name, err);
+               return err;
+       }
        /* Set the desired ESSID */
        idbuf.len = cpu_to_le16(strlen(priv->desired_essid));
        memcpy(&idbuf.val, priv->desired_essid, sizeof(idbuf.val));
@@ -2432,6 +2538,7 @@ struct net_device *alloc_orinocodev(int sizeof_card,
                                   * 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);
 
        netif_carrier_off(dev);
        priv->last_linkstatus = 0xffff;
@@ -2593,6 +2700,67 @@ static int orinoco_ioctl_getname(struct net_device *dev,
        return 0;
 }
 
+static int orinoco_ioctl_setwap(struct net_device *dev,
+                               struct iw_request_info *info,
+                               struct sockaddr *ap_addr,
+                               char *extra)
+{
+       struct orinoco_private *priv = netdev_priv(dev);
+       int err = -EINPROGRESS;         /* Call commit handler */
+       unsigned long flags;
+       static const u8 off_addr[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+       static const u8 any_addr[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+       if (orinoco_lock(priv, &flags) != 0)
+               return -EBUSY;
+
+       /* Enable automatic roaming - no sanity checks are needed */
+       if (memcmp(&ap_addr->sa_data, off_addr, ETH_ALEN) == 0 ||
+           memcmp(&ap_addr->sa_data, any_addr, ETH_ALEN) == 0) {
+               priv->bssid_fixed = 0;
+               memset(priv->desired_bssid, 0, ETH_ALEN);
+
+               /* "off" means keep existing connection */
+               if (ap_addr->sa_data[0] == 0) {
+                       __orinoco_hw_set_wap(priv);
+                       err = 0;
+               }
+               goto out;
+       }
+
+       if (priv->firmware_type == FIRMWARE_TYPE_AGERE) {
+               printk(KERN_WARNING "%s: Lucent/Agere firmware doesn't "
+                      "support manual roaming\n",
+                      dev->name);
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (priv->iw_mode != IW_MODE_INFRA) {
+               printk(KERN_WARNING "%s: Manual roaming supported only in "
+                      "managed mode\n", dev->name);
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       /* Intersil firmware hangs without Desired ESSID */
+       if (priv->firmware_type == FIRMWARE_TYPE_INTERSIL &&
+           strlen(priv->desired_essid) == 0) {
+               printk(KERN_WARNING "%s: Desired ESSID must be set for "
+                      "manual roaming\n", dev->name);
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       /* Finally, enable manual roaming */
+       priv->bssid_fixed = 1;
+       memcpy(priv->desired_bssid, &ap_addr->sa_data, ETH_ALEN);
+
+ out:
+       orinoco_unlock(priv, &flags);
+       return err;
+}
+
 static int orinoco_ioctl_getwap(struct net_device *dev,
                                struct iw_request_info *info,
                                struct sockaddr *ap_addr,
@@ -3890,6 +4058,7 @@ static const iw_handler   orinoco_handler[] = {
        [SIOCGIWRANGE -SIOCIWFIRST] (iw_handler) orinoco_ioctl_getiwrange,
        [SIOCSIWSPY   -SIOCIWFIRST] (iw_handler) orinoco_ioctl_setspy,
        [SIOCGIWSPY   -SIOCIWFIRST] (iw_handler) orinoco_ioctl_getspy,
+       [SIOCSIWAP    -SIOCIWFIRST] (iw_handler) orinoco_ioctl_setwap,
        [SIOCGIWAP    -SIOCIWFIRST] (iw_handler) orinoco_ioctl_getwap,
        [SIOCSIWESSID -SIOCIWFIRST] (iw_handler) orinoco_ioctl_setessid,
        [SIOCGIWESSID -SIOCIWFIRST] (iw_handler) orinoco_ioctl_getessid,
index f749b50d10883359c0a9f73596eaab8644c42ae3..329e79f6d7b17def3d28ad88e5e471bf33715579 100644 (file)
@@ -22,6 +22,8 @@
 
 #define WIRELESS_SPY           // enable iwspy support
 
+#define MAX_SCAN_LEN           4096
+
 #define ORINOCO_MAX_KEY_SIZE   14
 #define ORINOCO_MAX_KEYS       4
 
@@ -48,6 +50,7 @@ struct orinoco_private {
        /* driver state */
        int open;
        u16 last_linkstatus;
+       struct work_struct join_work;
 
        /* Net device stuff */
        struct net_device *ndev;
@@ -84,6 +87,8 @@ struct orinoco_private {
        int bitratemode;
        char nick[IW_ESSID_MAX_SIZE+1];
        char desired_essid[IW_ESSID_MAX_SIZE+1];
+       char desired_bssid[ETH_ALEN];
+       int bssid_fixed;
        u16 frag_thresh, mwo_robust;
        u16 channel;
        u16 ap_density, rts_thresh;