]> Pileus Git - ~andy/linux/blobdiff - drivers/net/wireless/rndis_wlan.c
rndis_wlan: enable infrastructure before setting random essid
[~andy/linux] / drivers / net / wireless / rndis_wlan.c
index c5b921bf5a964caefd4ee51188ff44008fd40f83..c5a674d8d1fbc37b85b671bcda0e0f8b62f15048 100644 (file)
@@ -413,6 +413,13 @@ static const struct ieee80211_rate rndis_rates[] = {
        { .bitrate = 540 }
 };
 
+static const u32 rndis_cipher_suites[] = {
+       WLAN_CIPHER_SUITE_WEP40,
+       WLAN_CIPHER_SUITE_WEP104,
+       WLAN_CIPHER_SUITE_TKIP,
+       WLAN_CIPHER_SUITE_CCMP,
+};
+
 struct rndis_wlan_encr_key {
        int len;
        int cipher;
@@ -441,6 +448,7 @@ struct rndis_wlan_private {
        struct ieee80211_supported_band band;
        struct ieee80211_channel channels[ARRAY_SIZE(rndis_channels)];
        struct ieee80211_rate rates[ARRAY_SIZE(rndis_rates)];
+       u32 cipher_suites[ARRAY_SIZE(rndis_cipher_suites)];
 
        struct iw_statistics iwstats;
        struct iw_statistics privstats;
@@ -462,6 +470,7 @@ struct rndis_wlan_private {
        int radio_on;
        int infra_mode;
        struct ndis_80211_ssid essid;
+       __le32 current_command_oid;
 
        /* encryption stuff */
        int  encr_tx_key_index;
@@ -505,11 +514,6 @@ static struct cfg80211_ops rndis_config_ops = {
 static void *rndis_wiphy_privid = &rndis_wiphy_privid;
 
 
-static const unsigned char zero_bssid[ETH_ALEN] = {0,};
-static const unsigned char ffff_bssid[ETH_ALEN] = { 0xff, 0xff, 0xff,
-                                                       0xff, 0xff, 0xff };
-
-
 static struct rndis_wlan_private *get_rndis_wlan_priv(struct usbnet *dev)
 {
        return (struct rndis_wlan_private *)dev->driver_priv;
@@ -657,7 +661,9 @@ static int rndis_query_oid(struct usbnet *dev, __le32 oid, void *data, int *len)
        u.get->msg_len = cpu_to_le32(sizeof *u.get);
        u.get->oid = oid;
 
+       priv->current_command_oid = oid;
        ret = rndis_command(dev, u.header, buflen);
+       priv->current_command_oid = 0;
        if (ret < 0)
                devdbg(dev, "rndis_query_oid(%s): rndis_command() failed, %d "
                        "(%08x)", oid_to_string(oid), ret,
@@ -665,7 +671,8 @@ static int rndis_query_oid(struct usbnet *dev, __le32 oid, void *data, int *len)
 
        if (ret == 0) {
                ret = le32_to_cpu(u.get_c->len);
-               *len = (*len > ret) ? ret : *len;
+               if (ret > *len)
+                       *len = ret;
                memcpy(data, u.buf + le32_to_cpu(u.get_c->offset) + 8, *len);
                ret = rndis_error_status(u.get_c->status);
 
@@ -717,7 +724,9 @@ static int rndis_set_oid(struct usbnet *dev, __le32 oid, void *data, int len)
        u.set->handle = cpu_to_le32(0);
        memcpy(u.buf + sizeof(*u.set), data, len);
 
+       priv->current_command_oid = oid;
        ret = rndis_command(dev, u.header, buflen);
+       priv->current_command_oid = 0;
        if (ret < 0)
                devdbg(dev, "rndis_set_oid(%s): rndis_command() failed, %d "
                        "(%08x)", oid_to_string(oid), ret,
@@ -752,6 +761,7 @@ static int rndis_reset(struct usbnet *usbdev)
        memset(reset, 0, sizeof(*reset));
        reset->msg_type = RNDIS_MSG_RESET;
        reset->msg_len = cpu_to_le32(sizeof(*reset));
+       priv->current_command_oid = 0;
        ret = rndis_command(usbdev, (void *)reset, CONTROL_BUFFER_SIZE);
 
        mutex_unlock(&priv->command_lock);
@@ -911,7 +921,9 @@ static int freq_to_dsconfig(struct iw_freq *freq, unsigned int *dsconfig)
 /*
  * common functions
  */
+static int set_infra_mode(struct usbnet *usbdev, int mode);
 static void restore_keys(struct usbnet *usbdev);
+static int rndis_check_bssid_list(struct usbnet *usbdev);
 
 static int get_essid(struct usbnet *usbdev, struct ndis_80211_ssid *ssid)
 {
@@ -979,7 +991,7 @@ static int is_associated(struct usbnet *usbdev)
 
        ret = get_bssid(usbdev, bssid);
 
-       return(ret == 0 && memcmp(bssid, zero_bssid, ETH_ALEN) != 0);
+       return (ret == 0 && !is_zero_ether_addr(bssid));
 }
 
 
@@ -1003,6 +1015,11 @@ static int disassociate(struct usbnet *usbdev, int reset_ssid)
        /* disassociate causes radio to be turned off; if reset_ssid
         * is given, set random ssid to enable radio */
        if (reset_ssid) {
+               /* Set device to infrastructure mode so we don't get ad-hoc
+                * 'media connect' indications with the random ssid.
+                */
+               set_infra_mode(usbdev, NDIS_80211_INFRA_INFRA);
+
                ssid.length = cpu_to_le32(sizeof(ssid.essid));
                get_random_bytes(&ssid.essid[2], sizeof(ssid.essid)-2);
                ssid.essid[0] = 0x1;
@@ -1277,8 +1294,8 @@ static int add_wpa_key(struct usbnet *usbdev, const u8 *key, int key_len,
                devdbg(usbdev, "add_wpa_key: recv seq flag without buffer");
                return -EINVAL;
        }
-       is_addr_ok = addr && memcmp(addr, zero_bssid, ETH_ALEN) != 0 &&
-                       memcmp(addr, ffff_bssid, ETH_ALEN) != 0;
+       is_addr_ok = addr && !is_zero_ether_addr(addr) &&
+                                       !is_broadcast_ether_addr(addr);
        if ((flags & NDIS_80211_ADDKEY_PAIRWISE_KEY) && !is_addr_ok) {
                devdbg(usbdev, "add_wpa_key: pairwise but bssid invalid (%pM)",
                        addr);
@@ -1363,8 +1380,8 @@ static int restore_key(struct usbnet *usbdev, int key_idx)
                /*if (priv->encr_tx_key_index == key_idx)
                        flags |= NDIS_80211_ADDKEY_TRANSMIT_KEY;*/
 
-               if (memcmp(key.bssid, zero_bssid, ETH_ALEN) != 0 &&
-                               memcmp(key.bssid, ffff_bssid, ETH_ALEN) != 0)
+               if (!is_zero_ether_addr(key.bssid) &&
+                               !is_broadcast_ether_addr(key.bssid))
                        flags |= NDIS_80211_ADDKEY_PAIRWISE_KEY;
 
                return add_wpa_key(usbdev, key.material, key.len, key_idx,
@@ -1414,7 +1431,7 @@ static int remove_key(struct usbnet *usbdev, int index, u8 bssid[ETH_ALEN])
                remove_key.index = cpu_to_le32(index);
                if (bssid) {
                        /* pairwise key */
-                       if (memcmp(bssid, ffff_bssid, ETH_ALEN) != 0)
+                       if (!is_broadcast_ether_addr(bssid))
                                remove_key.index |=
                                        NDIS_80211_ADDKEY_PAIRWISE_KEY;
                        memcpy(remove_key.bssid, bssid,
@@ -1515,7 +1532,8 @@ static int rndis_change_virtual_intf(struct wiphy *wiphy,
                                        enum nl80211_iftype type, u32 *flags,
                                        struct vif_params *params)
 {
-       struct usbnet *usbdev = netdev_priv(dev);
+       struct rndis_wlan_private *priv = wiphy_priv(wiphy);
+       struct usbnet *usbdev = priv->usbdev;
        int mode;
 
        switch (type) {
@@ -1529,6 +1547,8 @@ static int rndis_change_virtual_intf(struct wiphy *wiphy,
                return -EINVAL;
        }
 
+       priv->wdev.iftype = type;
+
        return set_infra_mode(usbdev, mode);
 }
 
@@ -1591,7 +1611,7 @@ static int rndis_get_tx_power(struct wiphy *wiphy, int *dbm)
 }
 
 
-#define SCAN_DELAY_JIFFIES (HZ)
+#define SCAN_DELAY_JIFFIES (6 * HZ)
 static int rndis_scan(struct wiphy *wiphy, struct net_device *dev,
                        struct cfg80211_scan_request *request)
 {
@@ -1602,6 +1622,11 @@ static int rndis_scan(struct wiphy *wiphy, struct net_device *dev,
 
        devdbg(usbdev, "cfg80211.scan");
 
+       /* Get current bssid list from device before new scan, as new scan
+        * clears internal bssid list.
+        */
+       rndis_check_bssid_list(usbdev);
+
        if (!request)
                return -EINVAL;
 
@@ -1636,6 +1661,9 @@ static struct cfg80211_bss *rndis_bss_info_update(struct usbnet *usbdev,
        int ie_len, bssid_len;
        u8 *ie;
 
+       devdbg(usbdev, " found bssid: '%.32s' [%pM]", bssid->ssid.essid,
+                                                       bssid->mac);
+
        /* parse bssid structure */
        bssid_len = le32_to_cpu(bssid->length);
 
@@ -1675,10 +1703,12 @@ static int rndis_check_bssid_list(struct usbnet *usbdev)
        struct ndis_80211_bssid_list_ex *bssid_list;
        struct ndis_80211_bssid_ex *bssid;
        int ret = -EINVAL, len, count, bssid_len;
+       bool resized = false;
 
        devdbg(usbdev, "check_bssid_list");
 
        len = CONTROL_BUFFER_SIZE;
+resize_buf:
        buf = kmalloc(len, GFP_KERNEL);
        if (!buf) {
                ret = -ENOMEM;
@@ -1689,11 +1719,18 @@ static int rndis_check_bssid_list(struct usbnet *usbdev)
        if (ret != 0)
                goto out;
 
+       if (!resized && len > CONTROL_BUFFER_SIZE) {
+               resized = true;
+               kfree(buf);
+               goto resize_buf;
+       }
+
        bssid_list = buf;
        bssid = bssid_list->bssid;
        bssid_len = le32_to_cpu(bssid->length);
        count = le32_to_cpu(bssid_list->num_items);
-       devdbg(usbdev, "check_bssid_list: %d BSSIDs found", count);
+       devdbg(usbdev, "check_bssid_list: %d BSSIDs found (buflen: %d)", count,
+                                                                       len);
 
        while (count && ((void *)bssid + bssid_len) <= (buf + len)) {
                rndis_bss_info_update(usbdev, bssid);
@@ -2286,68 +2323,77 @@ static const struct iw_handler_def rndis_iw_handlers = {
 };
 
 
-static void rndis_wlan_worker(struct work_struct *work)
+static void rndis_wlan_do_link_up_work(struct usbnet *usbdev)
 {
-       struct rndis_wlan_private *priv =
-               container_of(work, struct rndis_wlan_private, work);
-       struct usbnet *usbdev = priv->usbdev;
-       union iwreq_data evt;
-       unsigned char bssid[ETH_ALEN];
        struct ndis_80211_assoc_info *info;
-       int assoc_size = sizeof(*info) + IW_CUSTOM_MAX + 32;
+       union iwreq_data evt;
+       u8 assoc_buf[sizeof(*info) + IW_CUSTOM_MAX + 32];
+       u8 bssid[ETH_ALEN];
        int ret, offset;
 
-       if (test_and_clear_bit(WORK_LINK_UP, &priv->work_pending)) {
-               netif_carrier_on(usbdev->net);
-
-               info = kzalloc(assoc_size, GFP_KERNEL);
-               if (!info)
-                       goto get_bssid;
-
-               /* Get association info IEs from device and send them back to
-                * userspace. */
-               ret = get_association_info(usbdev, info, assoc_size);
-               if (!ret) {
-                       evt.data.length = le32_to_cpu(info->req_ie_length);
-                       if (evt.data.length > 0) {
-                               offset = le32_to_cpu(info->offset_req_ies);
-                               wireless_send_event(usbdev->net,
-                                       IWEVASSOCREQIE, &evt,
-                                       (char *)info + offset);
-                       }
-
-                       evt.data.length = le32_to_cpu(info->resp_ie_length);
-                       if (evt.data.length > 0) {
-                               offset = le32_to_cpu(info->offset_resp_ies);
-                               wireless_send_event(usbdev->net,
-                                       IWEVASSOCRESPIE, &evt,
-                                       (char *)info + offset);
-                       }
+       memset(assoc_buf, 0, sizeof(assoc_buf));
+       info = (void *)assoc_buf;
+
+       netif_carrier_on(usbdev->net);
+
+       /* Get association info IEs from device and send them back to
+        * userspace. */
+       ret = get_association_info(usbdev, info, sizeof(assoc_buf));
+       if (!ret) {
+               evt.data.length = le32_to_cpu(info->req_ie_length);
+               if (evt.data.length > 0) {
+                       offset = le32_to_cpu(info->offset_req_ies);
+                       wireless_send_event(usbdev->net,
+                               IWEVASSOCREQIE, &evt,
+                               (char *)info + offset);
                }
 
-               kfree(info);
-
-get_bssid:
-               ret = get_bssid(usbdev, bssid);
-               if (!ret) {
-                       evt.data.flags = 0;
-                       evt.data.length = 0;
-                       memcpy(evt.ap_addr.sa_data, bssid, ETH_ALEN);
-                       wireless_send_event(usbdev->net, SIOCGIWAP, &evt, NULL);
+               evt.data.length = le32_to_cpu(info->resp_ie_length);
+               if (evt.data.length > 0) {
+                       offset = le32_to_cpu(info->offset_resp_ies);
+                       wireless_send_event(usbdev->net,
+                               IWEVASSOCRESPIE, &evt,
+                               (char *)info + offset);
                }
 
                usbnet_resume_rx(usbdev);
        }
 
-       if (test_and_clear_bit(WORK_LINK_DOWN, &priv->work_pending)) {
-               netif_carrier_off(usbdev->net);
-
+       ret = get_bssid(usbdev, bssid);
+       if (!ret) {
                evt.data.flags = 0;
                evt.data.length = 0;
-               memset(evt.ap_addr.sa_data, 0, ETH_ALEN);
+               memcpy(evt.ap_addr.sa_data, bssid, ETH_ALEN);
                wireless_send_event(usbdev->net, SIOCGIWAP, &evt, NULL);
        }
 
+       usbnet_resume_rx(usbdev);
+}
+
+static void rndis_wlan_do_link_down_work(struct usbnet *usbdev)
+{
+       union iwreq_data evt;
+
+       netif_carrier_off(usbdev->net);
+
+       evt.data.flags = 0;
+       evt.data.length = 0;
+       memset(evt.ap_addr.sa_data, 0, ETH_ALEN);
+       wireless_send_event(usbdev->net, SIOCGIWAP, &evt, NULL);
+}
+
+static void rndis_wlan_worker(struct work_struct *work)
+{
+       struct rndis_wlan_private *priv =
+               container_of(work, struct rndis_wlan_private, work);
+       struct usbnet *usbdev = priv->usbdev;
+
+       if (test_and_clear_bit(WORK_LINK_UP, &priv->work_pending))
+               rndis_wlan_do_link_up_work(usbdev);
+
+       if (test_and_clear_bit(WORK_LINK_DOWN, &priv->work_pending))
+               rndis_wlan_do_link_down_work(usbdev);
+
        if (test_and_clear_bit(WORK_SET_MULTICAST_LIST, &priv->work_pending))
                set_multicast_list(usbdev);
 }
@@ -2550,6 +2596,17 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen)
 
        switch (msg->status) {
        case RNDIS_STATUS_MEDIA_CONNECT:
+               if (priv->current_command_oid == OID_802_11_ADD_KEY) {
+                       /* OID_802_11_ADD_KEY causes sometimes extra
+                        * "media connect" indications which confuses driver
+                        * and userspace to think that device is
+                        * roaming/reassociating when it isn't.
+                        */
+                       devdbg(usbdev, "ignored OID_802_11_ADD_KEY triggered "
+                                       "'media connect'");
+                       return;
+               }
+
                usbnet_pause_rx(usbdev);
 
                devinfo(usbdev, "media connect");
@@ -2892,7 +2949,7 @@ static int rndis_wlan_bind(struct usbnet *usbdev, struct usb_interface *intf)
                                        | BIT(NL80211_IFTYPE_ADHOC);
        wiphy->max_scan_ssids = 1;
 
-       /* TODO: fill-out band information based on priv->caps */
+       /* TODO: fill-out band/encr information based on priv->caps */
        rndis_wlan_get_caps(usbdev);
 
        memcpy(priv->channels, rndis_channels, sizeof(rndis_channels));
@@ -2904,6 +2961,11 @@ static int rndis_wlan_bind(struct usbnet *usbdev, struct usb_interface *intf)
        wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band;
        wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC;
 
+       memcpy(priv->cipher_suites, rndis_cipher_suites,
+                                               sizeof(rndis_cipher_suites));
+       wiphy->cipher_suites = priv->cipher_suites;
+       wiphy->n_cipher_suites = ARRAY_SIZE(rndis_cipher_suites);
+
        set_wiphy_dev(wiphy, &usbdev->udev->dev);
 
        if (wiphy_register(wiphy)) {