]> err.no Git - linux-2.6/blobdiff - net/mac80211/ieee80211_sta.c
mac80211: adding 802.11n IEs handling
[linux-2.6] / net / mac80211 / ieee80211_sta.c
index 2079e988fc56f256a7f655b2e541d7538ecb2169..1f47afeb925da8da3d86b6de27f6c51514691d4b 100644 (file)
@@ -90,7 +90,8 @@ struct ieee802_11_elems {
        u8 *ext_supp_rates;
        u8 *wmm_info;
        u8 *wmm_param;
-
+       u8 *ht_cap_elem;
+       u8 *ht_info_elem;
        /* length of them, respectively */
        u8 ssid_len;
        u8 supp_rates_len;
@@ -106,6 +107,8 @@ struct ieee802_11_elems {
        u8 ext_supp_rates_len;
        u8 wmm_info_len;
        u8 wmm_param_len;
+       u8 ht_cap_elem_len;
+       u8 ht_info_elem_len;
 };
 
 static void ieee802_11_parse_elems(u8 *start, size_t len,
@@ -190,6 +193,14 @@ static void ieee802_11_parse_elems(u8 *start, size_t len,
                        elems->ext_supp_rates = pos;
                        elems->ext_supp_rates_len = elen;
                        break;
+               case WLAN_EID_HT_CAPABILITY:
+                       elems->ht_cap_elem = pos;
+                       elems->ht_cap_elem_len = elen;
+                       break;
+               case WLAN_EID_HT_EXTRA_INFO:
+                       elems->ht_info_elem = pos;
+                       elems->ht_info_elem_len = elen;
+                       break;
                default:
                        break;
                }
@@ -332,6 +343,51 @@ static void ieee80211_handle_erp_ie(struct net_device *dev, u8 erp_value)
                ieee80211_erp_info_change_notify(dev, changes);
 }
 
+int ieee80211_ht_cap_ie_to_ht_info(struct ieee80211_ht_cap *ht_cap_ie,
+                                  struct ieee80211_ht_info *ht_info)
+{
+
+       if (ht_info == NULL)
+               return -EINVAL;
+
+       memset(ht_info, 0, sizeof(*ht_info));
+
+       if (ht_cap_ie) {
+               u8 ampdu_info = ht_cap_ie->ampdu_params_info;
+
+               ht_info->ht_supported = 1;
+               ht_info->cap = le16_to_cpu(ht_cap_ie->cap_info);
+               ht_info->ampdu_factor =
+                       ampdu_info & IEEE80211_HT_CAP_AMPDU_FACTOR;
+               ht_info->ampdu_density =
+                       (ampdu_info & IEEE80211_HT_CAP_AMPDU_DENSITY) >> 2;
+               memcpy(ht_info->supp_mcs_set, ht_cap_ie->supp_mcs_set, 16);
+       } else
+               ht_info->ht_supported = 0;
+
+       return 0;
+}
+
+int ieee80211_ht_addt_info_ie_to_ht_bss_info(
+                       struct ieee80211_ht_addt_info *ht_add_info_ie,
+                       struct ieee80211_ht_bss_info *bss_info)
+{
+       if (bss_info == NULL)
+               return -EINVAL;
+
+       memset(bss_info, 0, sizeof(*bss_info));
+
+       if (ht_add_info_ie) {
+               u16 op_mode;
+               op_mode = le16_to_cpu(ht_add_info_ie->operation_mode);
+
+               bss_info->primary_channel = ht_add_info_ie->control_chan;
+               bss_info->bss_cap = ht_add_info_ie->ht_param;
+               bss_info->bss_op_mode = (u8)(op_mode & 0xff);
+       }
+
+       return 0;
+}
 
 static void ieee80211_sta_send_associnfo(struct net_device *dev,
                                         struct ieee80211_if_sta *ifsta)
@@ -630,6 +686,19 @@ static void ieee80211_send_assoc(struct net_device *dev,
                *pos++ = 1; /* WME ver */
                *pos++ = 0;
        }
+       /* wmm support is a must to HT */
+       if (wmm && mode->ht_info.ht_supported) {
+               __le16 tmp = cpu_to_le16(mode->ht_info.cap);
+               pos = skb_put(skb, sizeof(struct ieee80211_ht_cap)+2);
+               *pos++ = WLAN_EID_HT_CAPABILITY;
+               *pos++ = sizeof(struct ieee80211_ht_cap);
+               memset(pos, 0, sizeof(struct ieee80211_ht_cap));
+               memcpy(pos, &tmp, sizeof(u16));
+               pos += sizeof(u16);
+               *pos++ = (mode->ht_info.ampdu_factor |
+                               (mode->ht_info.ampdu_density << 2));
+               memcpy(pos, mode->ht_info.supp_mcs_set, 16);
+       }
 
        kfree(ifsta->assocreq_ies);
        ifsta->assocreq_ies_len = (skb->data + skb->len) - ies;
@@ -808,12 +877,8 @@ static void ieee80211_associated(struct net_device *dev,
                sta_info_put(sta);
        }
        if (disassoc) {
-               union iwreq_data wrqu;
-               memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN);
-               wrqu.ap_addr.sa_family = ARPHRD_ETHER;
-               wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
-               mod_timer(&ifsta->timer, jiffies +
-                                     IEEE80211_MONITORING_INTERVAL + 30 * HZ);
+               ifsta->state = IEEE80211_DISABLED;
+               ieee80211_set_associated(dev, ifsta, 0);
        } else {
                mod_timer(&ifsta->timer, jiffies +
                                      IEEE80211_MONITORING_INTERVAL);
@@ -1384,6 +1449,7 @@ static void ieee80211_rx_bss_free(struct ieee80211_sta_bss *bss)
        kfree(bss->wpa_ie);
        kfree(bss->rsn_ie);
        kfree(bss->wmm_ie);
+       kfree(bss->ht_ie);
        kfree(bss);
 }
 
@@ -1487,8 +1553,18 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
                u32 supp_rates, prev_rates;
                int i, j;
 
-               mode = local->sta_scanning ?
+               mode = local->sta_sw_scanning ?
                       local->scan_hw_mode : local->oper_hw_mode;
+
+               if (local->sta_hw_scanning) {
+                       /* search for the correct mode matches the beacon */
+                       list_for_each_entry(mode, &local->modes_list, list)
+                               if (mode->mode == rx_status->phymode)
+                                       break;
+
+                       if (mode == NULL)
+                               mode = local->oper_hw_mode;
+               }
                rates = mode->rates;
                num_rates = mode->num_rates;
 
@@ -1631,7 +1707,22 @@ static void ieee80211_rx_bss_info(struct net_device *dev,
                bss->wmm_ie = NULL;
                bss->wmm_ie_len = 0;
        }
-
+       if (elems.ht_cap_elem &&
+           (!bss->ht_ie || bss->ht_ie_len != elems.ht_cap_elem_len ||
+            memcmp(bss->ht_ie, elems.ht_cap_elem, elems.ht_cap_elem_len))) {
+               kfree(bss->ht_ie);
+               bss->ht_ie = kmalloc(elems.ht_cap_elem_len + 2, GFP_ATOMIC);
+               if (bss->ht_ie) {
+                       memcpy(bss->ht_ie, elems.ht_cap_elem - 2,
+                              elems.ht_cap_elem_len + 2);
+                       bss->ht_ie_len = elems.ht_cap_elem_len + 2;
+               } else
+                       bss->ht_ie_len = 0;
+       } else if (!elems.ht_cap_elem && bss->ht_ie) {
+               kfree(bss->ht_ie);
+               bss->ht_ie = NULL;
+               bss->ht_ie_len = 0;
+       }
 
        bss->hw_mode = rx_status->phymode;
        bss->freq = rx_status->freq;
@@ -1871,31 +1962,39 @@ static void ieee80211_sta_rx_queued_mgmt(struct net_device *dev,
 }
 
 
-void ieee80211_sta_rx_scan(struct net_device *dev, struct sk_buff *skb,
-                          struct ieee80211_rx_status *rx_status)
+ieee80211_txrx_result
+ieee80211_sta_rx_scan(struct net_device *dev, struct sk_buff *skb,
+                     struct ieee80211_rx_status *rx_status)
 {
        struct ieee80211_mgmt *mgmt;
        u16 fc;
 
-       if (skb->len < 24) {
-               dev_kfree_skb(skb);
-               return;
-       }
+       if (skb->len < 2)
+               return TXRX_DROP;
 
        mgmt = (struct ieee80211_mgmt *) skb->data;
        fc = le16_to_cpu(mgmt->frame_control);
 
+       if ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_CTL)
+               return TXRX_CONTINUE;
+
+       if (skb->len < 24)
+               return TXRX_DROP;
+
        if ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_MGMT) {
                if ((fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_PROBE_RESP) {
                        ieee80211_rx_mgmt_probe_resp(dev, mgmt,
                                                     skb->len, rx_status);
+                       dev_kfree_skb(skb);
+                       return TXRX_QUEUED;
                } else if ((fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_BEACON) {
                        ieee80211_rx_mgmt_beacon(dev, mgmt, skb->len,
                                                 rx_status);
+                       dev_kfree_skb(skb);
+                       return TXRX_QUEUED;
                }
        }
-
-       dev_kfree_skb(skb);
+       return TXRX_CONTINUE;
 }
 
 
@@ -1985,7 +2084,7 @@ void ieee80211_sta_work(struct work_struct *work)
        if (!netif_running(dev))
                return;
 
-       if (local->sta_scanning)
+       if (local->sta_sw_scanning || local->sta_hw_scanning)
                return;
 
        if (sdata->type != IEEE80211_IF_TYPE_STA &&
@@ -2002,7 +2101,10 @@ void ieee80211_sta_work(struct work_struct *work)
        if (ifsta->state != IEEE80211_AUTHENTICATE &&
            ifsta->state != IEEE80211_ASSOCIATE &&
            test_and_clear_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request)) {
-               ieee80211_sta_start_scan(dev, NULL, 0);
+               if (ifsta->scan_ssid_len)
+                       ieee80211_sta_start_scan(dev, ifsta->scan_ssid, ifsta->scan_ssid_len);
+               else
+                       ieee80211_sta_start_scan(dev, NULL, 0);
                return;
        }
 
@@ -2640,11 +2742,17 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw)
        union iwreq_data wrqu;
 
        local->last_scan_completed = jiffies;
-       wmb();
-       local->sta_scanning = 0;
+       memset(&wrqu, 0, sizeof(wrqu));
+       wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
+
+       if (local->sta_hw_scanning) {
+               local->sta_hw_scanning = 0;
+               goto done;
+       }
 
+       local->sta_sw_scanning = 0;
        if (ieee80211_hw_config(local))
-               printk(KERN_DEBUG "%s: failed to restore operational"
+               printk(KERN_DEBUG "%s: failed to restore operational "
                       "channel after scan\n", dev->name);
 
 
@@ -2658,9 +2766,6 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw)
 
        netif_tx_unlock_bh(local->mdev);
 
-       memset(&wrqu, 0, sizeof(wrqu));
-       wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
-
        rcu_read_lock();
        list_for_each_entry_rcu(sdata, &local->interfaces, list) {
 
@@ -2678,6 +2783,7 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw)
        }
        rcu_read_unlock();
 
+done:
        sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        if (sdata->type == IEEE80211_IF_TYPE_IBSS) {
                struct ieee80211_if_sta *ifsta = &sdata->u.sta;
@@ -2700,7 +2806,7 @@ void ieee80211_sta_scan_work(struct work_struct *work)
        int skip;
        unsigned long next_delay = 0;
 
-       if (!local->sta_scanning)
+       if (!local->sta_sw_scanning)
                return;
 
        switch (local->scan_state) {
@@ -2763,7 +2869,7 @@ void ieee80211_sta_scan_work(struct work_struct *work)
                break;
        }
 
-       if (local->sta_scanning)
+       if (local->sta_sw_scanning)
                queue_delayed_work(local->hw.workqueue, &local->scan_work,
                                   next_delay);
 }
@@ -2795,7 +2901,7 @@ static int ieee80211_sta_start_scan(struct net_device *dev,
          * ResultCode: SUCCESS, INVALID_PARAMETERS
         */
 
-       if (local->sta_scanning) {
+       if (local->sta_sw_scanning || local->sta_hw_scanning) {
                if (local->scan_dev == dev)
                        return 0;
                return -EBUSY;
@@ -2803,15 +2909,15 @@ static int ieee80211_sta_start_scan(struct net_device *dev,
 
        if (local->ops->hw_scan) {
                int rc = local->ops->hw_scan(local_to_hw(local),
-                                           ssid, ssid_len);
+                                            ssid, ssid_len);
                if (!rc) {
-                       local->sta_scanning = 1;
+                       local->sta_hw_scanning = 1;
                        local->scan_dev = dev;
                }
                return rc;
        }
 
-       local->sta_scanning = 1;
+       local->sta_sw_scanning = 1;
 
        rcu_read_lock();
        list_for_each_entry_rcu(sdata, &local->interfaces, list) {
@@ -2866,12 +2972,15 @@ int ieee80211_sta_req_scan(struct net_device *dev, u8 *ssid, size_t ssid_len)
        if (sdata->type != IEEE80211_IF_TYPE_STA)
                return ieee80211_sta_start_scan(dev, ssid, ssid_len);
 
-       if (local->sta_scanning) {
+       if (local->sta_sw_scanning || local->sta_hw_scanning) {
                if (local->scan_dev == dev)
                        return 0;
                return -EBUSY;
        }
 
+       ifsta->scan_ssid_len = ssid_len;
+       if (ssid_len)
+               memcpy(ifsta->scan_ssid, ssid, ssid_len);
        set_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request);
        queue_work(local->hw.workqueue, &ifsta->work);
        return 0;
@@ -2892,15 +3001,6 @@ ieee80211_sta_scan_result(struct net_device *dev,
        if (!(local->enabled_modes & (1 << bss->hw_mode)))
                return current_ev;
 
-       if (local->scan_flags & IEEE80211_SCAN_WPA_ONLY &&
-           !bss->wpa_ie && !bss->rsn_ie)
-               return current_ev;
-
-       if (local->scan_flags & IEEE80211_SCAN_MATCH_SSID &&
-           (local->scan_ssid_len != bss->ssid_len ||
-            memcmp(local->scan_ssid, bss->ssid, bss->ssid_len) != 0))
-               return current_ev;
-
        memset(&iwe, 0, sizeof(iwe));
        iwe.cmd = SIOCGIWAP;
        iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
@@ -3007,9 +3107,6 @@ ieee80211_sta_scan_result(struct net_device *dev,
        do {
                char *buf;
 
-               if (!(local->scan_flags & IEEE80211_SCAN_EXTRA_INFO))
-                       break;
-
                buf = kmalloc(100, GFP_ATOMIC);
                if (!buf)
                        break;