mac80211: add U-APSD client support
Kalle Valo [Tue, 12 Jan 2010 08:42:31 +0000 (10:42 +0200)]
Add Unscheduled Automatic Power-Save Delivery (U-APSD) client support. The
idea is that the data frames from the client trigger AP to send the buffered
frames with ACs which have U-APSD enabled. This decreases latency and makes it
possible to save even more power.

Driver needs to use IEEE80211_HW_UAPSD to enable the feature. The current
implementation assumes that firmware takes care of the wakeup and
hardware needing IEEE80211_HW_PS_NULLFUNC_STACK is not yet supported.

Tested with wl1251 on a Nokia N900 and Cisco Aironet 1231G AP and running
various test traffic with ping.

Signed-off-by: Kalle Valo <kalle.valo@nokia.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

include/linux/ieee80211.h
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/ieee80211_i.h
net/mac80211/main.c
net/mac80211/mlme.c
net/mac80211/scan.c
net/mac80211/util.c
net/mac80211/work.c

index 602c069..a8c6069 100644 (file)
 #define IEEE80211_QOS_CTL_TID_MASK     0x000F
 #define IEEE80211_QOS_CTL_TAG1D_MASK   0x0007
 
+/* U-APSD queue for WMM IEs sent by AP */
+#define IEEE80211_WMM_IE_AP_QOSINFO_UAPSD      (1<<7)
+
+/* U-APSD queues for WMM IEs sent by STA */
+#define IEEE80211_WMM_IE_STA_QOSINFO_AC_VO     (1<<0)
+#define IEEE80211_WMM_IE_STA_QOSINFO_AC_VI     (1<<1)
+#define IEEE80211_WMM_IE_STA_QOSINFO_AC_BK     (1<<2)
+#define IEEE80211_WMM_IE_STA_QOSINFO_AC_BE     (1<<3)
+#define IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK   0x0f
+
+/* U-APSD max SP length for WMM IEs sent by STA */
+#define IEEE80211_WMM_IE_STA_QOSINFO_SP_ALL    0x00
+#define IEEE80211_WMM_IE_STA_QOSINFO_SP_2      0x01
+#define IEEE80211_WMM_IE_STA_QOSINFO_SP_4      0x02
+#define IEEE80211_WMM_IE_STA_QOSINFO_SP_6      0x03
+#define IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK   0x03
+#define IEEE80211_WMM_IE_STA_QOSINFO_SP_SHIFT  5
+
 struct ieee80211_hdr {
        __le16 frame_control;
        __le16 duration_id;
index e1e73c6..f313a3c 100644 (file)
@@ -113,6 +113,7 @@ struct ieee80211_tx_queue_params {
        u16 cw_min;
        u16 cw_max;
        u8 aifs;
+       bool uapsd;
 };
 
 /**
@@ -929,6 +930,11 @@ enum ieee80211_tkip_key_type {
  *     Hardware supports dynamic spatial multiplexing powersave,
  *     ie. can turn off all but one chain and then wake the rest
  *     up as required after, for example, rts/cts handshake.
+ *
+ * @IEEE80211_HW_SUPPORTS_UAPSD:
+ *     Hardware supports Unscheduled Automatic Power Save Delivery
+ *     (U-APSD) in managed mode. The mode is configured with
+ *     conf_tx() operation.
  */
 enum ieee80211_hw_flags {
        IEEE80211_HW_HAS_RATE_CONTROL                   = 1<<0,
@@ -948,6 +954,7 @@ enum ieee80211_hw_flags {
        IEEE80211_HW_BEACON_FILTER                      = 1<<14,
        IEEE80211_HW_SUPPORTS_STATIC_SMPS               = 1<<15,
        IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS              = 1<<16,
+       IEEE80211_HW_SUPPORTS_UAPSD                     = 1<<17,
 };
 
 /**
index dc12e94..8286df5 100644 (file)
@@ -1128,6 +1128,13 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy,
        p.cw_max = params->cwmax;
        p.cw_min = params->cwmin;
        p.txop = params->txop;
+
+       /*
+        * Setting tx queue params disables u-apsd because it's only
+        * called in master mode.
+        */
+       p.uapsd = false;
+
        if (drv_conf_tx(local, params->queue, &p)) {
                printk(KERN_DEBUG "%s: failed to set TX queue "
                       "parameters for queue %d\n",
index 3e4ac3f..3468e37 100644 (file)
@@ -58,6 +58,15 @@ struct ieee80211_local;
 
 #define TU_TO_EXP_TIME(x)      (jiffies + usecs_to_jiffies((x) * 1024))
 
+#define IEEE80211_DEFAULT_UAPSD_QUEUES \
+       (IEEE80211_WMM_IE_STA_QOSINFO_AC_BK |   \
+        IEEE80211_WMM_IE_STA_QOSINFO_AC_BE |   \
+        IEEE80211_WMM_IE_STA_QOSINFO_AC_VI |   \
+        IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
+
+#define IEEE80211_DEFAULT_MAX_SP_LEN           \
+       IEEE80211_WMM_IE_STA_QOSINFO_SP_ALL
+
 struct ieee80211_fragment_entry {
        unsigned long first_frag_time;
        unsigned int seq;
@@ -78,6 +87,7 @@ struct ieee80211_bss {
        u8 dtim_period;
 
        bool wmm_used;
+       bool uapsd_supported;
 
        unsigned long last_probe_resp;
 
@@ -285,7 +295,7 @@ struct ieee80211_work {
                        u8 ssid[IEEE80211_MAX_SSID_LEN];
                        u8 ssid_len;
                        u8 supp_rates_len;
-                       bool wmm_used, use_11n;
+                       bool wmm_used, use_11n, uapsd_used;
                } assoc;
                struct {
                        u32 duration;
@@ -306,6 +316,7 @@ enum ieee80211_sta_flags {
        IEEE80211_STA_DISABLE_11N       = BIT(4),
        IEEE80211_STA_CSA_RECEIVED      = BIT(5),
        IEEE80211_STA_MFP_ENABLED       = BIT(6),
+       IEEE80211_STA_UAPSD_ENABLED     = BIT(7),
 };
 
 struct ieee80211_if_managed {
index 4688291..0054bba 100644 (file)
@@ -491,6 +491,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
        else if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC)
                local->hw.wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC;
 
+       WARN((local->hw.flags & IEEE80211_HW_SUPPORTS_UAPSD)
+            && (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK),
+            "U-APSD not supported with HW_PS_NULLFUNC_STACK\n");
+
        /*
         * Calculate scan IE length -- we need this to alloc
         * memory and to subtract from the driver limit. It
index 86f025b..39c27d8 100644 (file)
@@ -569,7 +569,7 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
        struct ieee80211_tx_queue_params params;
        size_t left;
        int count;
-       u8 *pos;
+       u8 *pos, uapsd_queues = 0;
 
        if (local->hw.queues < 4)
                return;
@@ -579,6 +579,10 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
 
        if (wmm_param_len < 8 || wmm_param[5] /* version */ != 1)
                return;
+
+       if (ifmgd->flags & IEEE80211_STA_UAPSD_ENABLED)
+               uapsd_queues = IEEE80211_DEFAULT_UAPSD_QUEUES;
+
        count = wmm_param[6] & 0x0f;
        if (count == ifmgd->wmm_last_param_set)
                return;
@@ -593,6 +597,7 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
        for (; left >= 4; left -= 4, pos += 4) {
                int aci = (pos[0] >> 5) & 0x03;
                int acm = (pos[0] >> 4) & 0x01;
+               bool uapsd = false;
                int queue;
 
                switch (aci) {
@@ -600,22 +605,30 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
                        queue = 3;
                        if (acm)
                                local->wmm_acm |= BIT(1) | BIT(2); /* BK/- */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK)
+                               uapsd = true;
                        break;
                case 2: /* AC_VI */
                        queue = 1;
                        if (acm)
                                local->wmm_acm |= BIT(4) | BIT(5); /* CL/VI */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI)
+                               uapsd = true;
                        break;
                case 3: /* AC_VO */
                        queue = 0;
                        if (acm)
                                local->wmm_acm |= BIT(6) | BIT(7); /* VO/NC */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
+                               uapsd = true;
                        break;
                case 0: /* AC_BE */
                default:
                        queue = 2;
                        if (acm)
                                local->wmm_acm |= BIT(0) | BIT(3); /* BE/EE */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE)
+                               uapsd = true;
                        break;
                }
 
@@ -623,11 +636,14 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
                params.cw_max = ecw2cw((pos[1] & 0xf0) >> 4);
                params.cw_min = ecw2cw(pos[1] & 0x0f);
                params.txop = get_unaligned_le16(pos + 2);
+               params.uapsd = uapsd;
+
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
                printk(KERN_DEBUG "%s: WMM queue=%d aci=%d acm=%d aifs=%d "
-                      "cWmin=%d cWmax=%d txop=%d\n",
+                      "cWmin=%d cWmax=%d txop=%d uapsd=%d\n",
                       wiphy_name(local->hw.wiphy), queue, aci, acm,
-                      params.aifs, params.cw_min, params.cw_max, params.txop);
+                      params.aifs, params.cw_min, params.cw_max, params.txop,
+                      params.uapsd);
 #endif
                if (drv_conf_tx(local, queue, &params) && local->ops->conf_tx)
                        printk(KERN_DEBUG "%s: failed to set TX queue "
@@ -1906,6 +1922,15 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
        wk->assoc.ht_information_ie =
                ieee80211_bss_get_ie(req->bss, WLAN_EID_HT_INFORMATION);
 
+       if (bss->wmm_used && bss->uapsd_supported &&
+           (sdata->local->hw.flags & IEEE80211_HW_SUPPORTS_UAPSD)) {
+               wk->assoc.uapsd_used = true;
+               ifmgd->flags |= IEEE80211_STA_UAPSD_ENABLED;
+       } else {
+               wk->assoc.uapsd_used = false;
+               ifmgd->flags &= ~IEEE80211_STA_UAPSD_ENABLED;
+       }
+
        ssid = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID);
        memcpy(wk->assoc.ssid, ssid + 2, ssid[1]);
        wk->assoc.ssid_len = ssid[1];
index 30cb62b..9afe2f9 100644 (file)
@@ -54,6 +54,23 @@ void ieee80211_rx_bss_put(struct ieee80211_local *local,
        cfg80211_put_bss(container_of((void *)bss, struct cfg80211_bss, priv));
 }
 
+static bool is_uapsd_supported(struct ieee802_11_elems *elems)
+{
+       u8 qos_info;
+
+       if (elems->wmm_info && elems->wmm_info_len == 7
+           && elems->wmm_info[5] == 1)
+               qos_info = elems->wmm_info[6];
+       else if (elems->wmm_param && elems->wmm_param_len == 24
+                && elems->wmm_param[5] == 1)
+               qos_info = elems->wmm_param[6];
+       else
+               /* no valid wmm information or parameter element found */
+               return false;
+
+       return qos_info & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD;
+}
+
 struct ieee80211_bss *
 ieee80211_bss_info_update(struct ieee80211_local *local,
                          struct ieee80211_rx_status *rx_status,
@@ -117,6 +134,7 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
        }
 
        bss->wmm_used = elems->wmm_param || elems->wmm_info;
+       bss->uapsd_supported = is_uapsd_supported(elems);
 
        if (!beacon)
                bss->last_probe_resp = jiffies;
index a2ba6e2..e278f97 100644 (file)
@@ -792,6 +792,8 @@ void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata)
                        break;
                }
 
+               qparam.uapsd = false;
+
                drv_conf_tx(local, queue, &qparam);
        }
 }
index 7c5d95b..a74fd6e 100644 (file)
@@ -202,7 +202,7 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_local *local = sdata->local;
        struct sk_buff *skb;
        struct ieee80211_mgmt *mgmt;
-       u8 *pos;
+       u8 *pos, qos_info;
        const u8 *ies;
        size_t offset = 0, noffset;
        int i, len, count, rates_len, supp_rates_len;
@@ -375,6 +375,14 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata,
        }
 
        if (wk->assoc.wmm_used && local->hw.queues >= 4) {
+               if (wk->assoc.uapsd_used) {
+                       qos_info = IEEE80211_DEFAULT_UAPSD_QUEUES;
+                       qos_info |= (IEEE80211_DEFAULT_MAX_SP_LEN <<
+                                    IEEE80211_WMM_IE_STA_QOSINFO_SP_SHIFT);
+               } else {
+                       qos_info = 0;
+               }
+
                pos = skb_put(skb, 9);
                *pos++ = WLAN_EID_VENDOR_SPECIFIC;
                *pos++ = 7; /* len */
@@ -384,7 +392,7 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata,
                *pos++ = 2; /* WME */
                *pos++ = 0; /* WME info */
                *pos++ = 1; /* WME ver */
-               *pos++ = 0;
+               *pos++ = qos_info;
        }
 
        /* add any remaining custom (i.e. vendor specific here) IEs */