[net80211] Add initial U-APSD negotiation support.

U-APSD (unscheduled automatic power save delivery) is a power save method
that's a bit better than legacy PS-POLL - stations can mark frames with
an extra flag that tells the AP to leak out more frames after it sends
its own frames rather than needing to send a PS-POLL to get another frame
from the AP.

Now, this code just handles the negotiation bits; it doesn't actually
implement U-APSD.  That's up to drivers, and nothing in the tree yet
implements this.  I /may/ implement this for ath(4) if I eventually care
enough but right now I plan on just implementing it for firmware offload
based NICs that handle this in the NIC.

I'll commit the ifconfig bit after this and I may have some follow-up
commits as this gets used more by me in local testing.

This should be a glorious no-op for everyone else.  If things change
for anyone that isn't fixed by a complete recompile then please reach out
to me.
This commit is contained in:
Adrian Chadd 2020-06-16 00:27:32 +00:00
parent 3d8dd98381
commit 8379e8db7a
11 changed files with 167 additions and 33 deletions

View File

@ -488,6 +488,7 @@ struct ieee80211_mimo_info {
#define IEEE80211_C_MBSS 0x00040000 /* CAPABILITY: MBSS available */
#define IEEE80211_C_SWSLEEP 0x00080000 /* CAPABILITY: do sleep here */
#define IEEE80211_C_SWAMSDUTX 0x00100000 /* CAPABILITY: software A-MSDU TX */
#define IEEE80211_C_UAPSD 0x00200000 /* CAPABILITY: U-APSD */
/* 0x7c0000 available */
#define IEEE80211_C_WPA1 0x00800000 /* CAPABILITY: WPA1 avail */
#define IEEE80211_C_WPA2 0x01000000 /* CAPABILITY: WPA2 avail */

View File

@ -616,6 +616,12 @@ ieee80211_vap_setup(struct ieee80211com *ic, struct ieee80211vap *vap,
if (vap->iv_opmode == IEEE80211_M_HOSTAP &&
(vap->iv_caps & IEEE80211_C_DFS))
vap->iv_flags_ext |= IEEE80211_FEXT_DFS;
/* NB: only flip on U-APSD for hostap/sta for now */
if ((vap->iv_opmode == IEEE80211_M_STA)
|| (vap->iv_opmode == IEEE80211_M_HOSTAP)) {
if (vap->iv_caps & IEEE80211_C_UAPSD)
vap->iv_flags_ext |= IEEE80211_FEXT_UAPSD;
}
vap->iv_des_chan = IEEE80211_CHAN_ANYC; /* any channel is ok */
vap->iv_bmissthreshold = IEEE80211_HWBMISS_DEFAULT;

View File

@ -65,6 +65,7 @@ __FBSDID("$FreeBSD$");
#endif
#include <net80211/ieee80211_wds.h>
#include <net80211/ieee80211_vht.h>
#include <net80211/ieee80211_sta.h> /* for parse_wmeie */
#define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2)
@ -2253,8 +2254,18 @@ hostap_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0,
* Mark node as capable of QoS.
*/
ni->ni_flags |= IEEE80211_NODE_QOS;
if (ieee80211_parse_wmeie(wme, wh, ni) > 0) {
if (ni->ni_uapsd != 0)
ni->ni_flags |=
IEEE80211_NODE_UAPSD;
else
ni->ni_flags &=
~IEEE80211_NODE_UAPSD;
}
} else
ni->ni_flags &= ~IEEE80211_NODE_QOS;
ni->ni_flags &=
~(IEEE80211_NODE_QOS |
IEEE80211_NODE_UAPSD);
#ifdef IEEE80211_SUPPORT_SUPERG
if (ath != NULL) {
setie(ath_ie, ath - sfrm);
@ -2268,6 +2279,7 @@ hostap_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0,
#undef setie
} else {
ni->ni_flags &= ~IEEE80211_NODE_QOS;
ni->ni_flags &= ~IEEE80211_NODE_UAPSD;
ni->ni_ath_flags = 0;
}
ieee80211_node_join(ni, resp);

View File

@ -1145,6 +1145,11 @@ ieee80211_ioctl_get80211(struct ieee80211vap *vap, u_long cmd,
if (vap->iv_flags_ht & IEEE80211_FHT_LDPC_RX)
ireq->i_val |= 2;
break;
case IEEE80211_IOC_UAPSD:
ireq->i_val = 0;
if (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)
ireq->i_val = 1;
break;
/* VHT */
case IEEE80211_IOC_VHTCONF:
@ -3462,6 +3467,16 @@ ieee80211_ioctl_set80211(struct ieee80211vap *vap, u_long cmd, struct ieee80211r
if (isvapht(vap))
error = ERESTART;
break;
case IEEE80211_IOC_UAPSD:
if ((vap->iv_caps & IEEE80211_C_UAPSD) == 0)
return EOPNOTSUPP;
if (ireq->i_val == 0)
vap->iv_flags_ext &= ~IEEE80211_FEXT_UAPSD;
else if (ireq->i_val == 1)
vap->iv_flags_ext |= IEEE80211_FEXT_UAPSD;
else
return EINVAL;
break;
/* VHT */
case IEEE80211_IOC_VHTCONF:

View File

@ -710,6 +710,8 @@ struct ieee80211req {
#define IEEE80211_IOC_GREENFIELD 112 /* Greenfield (on, off) */
#define IEEE80211_IOC_STBC 113 /* STBC Tx/RX (on, off) */
#define IEEE80211_IOC_LDPC 114 /* LDPC Tx/RX (on, off) */
#define IEEE80211_IOC_UAPSD 115 /* UAPSD (on, off) */
#define IEEE80211_IOC_UAPSD_INFO 116 /* UAPSD (SP, per-AC enable) */
/* VHT */
#define IEEE80211_IOC_VHTCONF 130 /* VHT config (off, on; widths) */

View File

@ -146,6 +146,7 @@ struct ieee80211_node {
#define IEEE80211_NODE_AMSDU_TX 0x080000 /* AMSDU tx enabled */
#define IEEE80211_NODE_VHT 0x100000 /* VHT enabled */
#define IEEE80211_NODE_LDPC 0x200000 /* LDPC enabled */
#define IEEE80211_NODE_UAPSD 0x400000 /* U-APSD power save enabled */
uint16_t ni_associd; /* association ID */
uint16_t ni_vlan; /* vlan tag */
uint16_t ni_txpower; /* current transmit power */
@ -256,6 +257,9 @@ struct ieee80211_node {
uint32_t ni_quiet_ie_set; /* Quiet time IE was seen */
struct ieee80211_quiet_ie ni_quiet_ie; /* last seen quiet IE */
/* U-APSD */
uint8_t ni_uapsd; /* U-APSD per-node flags matching WMM STA QoS Info field */
uint64_t ni_spare[3];
};
MALLOC_DECLARE(M_80211_NODE);

View File

@ -2156,26 +2156,48 @@ add_ie(uint8_t *frm, const uint8_t *ie)
* Add a WME information element to a frame.
*/
uint8_t *
ieee80211_add_wme_info(uint8_t *frm, struct ieee80211_wme_state *wme)
ieee80211_add_wme_info(uint8_t *frm, struct ieee80211_wme_state *wme,
struct ieee80211_node *ni)
{
static const struct ieee80211_wme_info info = {
.wme_id = IEEE80211_ELEMID_VENDOR,
.wme_len = sizeof(struct ieee80211_wme_info) - 2,
.wme_oui = { WME_OUI_BYTES },
.wme_type = WME_OUI_TYPE,
.wme_subtype = WME_INFO_OUI_SUBTYPE,
.wme_version = WME_VERSION,
.wme_info = 0,
};
memcpy(frm, &info, sizeof(info));
return frm + sizeof(info);
static const uint8_t oui[4] = { WME_OUI_BYTES, WME_OUI_TYPE };
struct ieee80211vap *vap = ni->ni_vap;
*frm++ = IEEE80211_ELEMID_VENDOR;
*frm++ = sizeof(struct ieee80211_wme_info) - 2;
memcpy(frm, oui, sizeof(oui));
frm += sizeof(oui);
*frm++ = WME_INFO_OUI_SUBTYPE;
*frm++ = WME_VERSION;
/* QoS info field depends upon operating mode */
switch (vap->iv_opmode) {
case IEEE80211_M_HOSTAP:
*frm = wme->wme_bssChanParams.cap_info;
if (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)
*frm |= WME_CAPINFO_UAPSD_EN;
frm++;
break;
case IEEE80211_M_STA:
/*
* NB: UAPSD drivers must set this up in their
* VAP creation method.
*/
*frm++ = vap->iv_uapsdinfo;
break;
default:
*frm++ = 0;
break;
}
return frm;
}
/*
* Add a WME parameters element to a frame.
*/
static uint8_t *
ieee80211_add_wme_param(uint8_t *frm, struct ieee80211_wme_state *wme)
ieee80211_add_wme_param(uint8_t *frm, struct ieee80211_wme_state *wme,
int uapsd_enable)
{
#define SM(_v, _f) (((_v) << _f##_S) & _f)
#define ADDSHORT(frm, v) do { \
@ -2195,8 +2217,12 @@ ieee80211_add_wme_param(uint8_t *frm, struct ieee80211_wme_state *wme)
memcpy(frm, &param, sizeof(param));
frm += __offsetof(struct ieee80211_wme_info, wme_info);
*frm++ = wme->wme_bssChanParams.cap_info; /* AC info */
*frm = wme->wme_bssChanParams.cap_info; /* AC info */
if (uapsd_enable)
*frm |= WME_CAPINFO_UAPSD_EN;
frm++;
*frm++ = 0; /* reserved field */
/* XXX TODO - U-APSD bits - SP, flags below */
for (i = 0; i < WME_NUM_AC; i++) {
const struct wmeParams *ac =
&wme->wme_bssChanParams.cap_wmeParams[i];
@ -2789,7 +2815,7 @@ ieee80211_send_mgmt(struct ieee80211_node *ni, int type, int arg)
frm = ieee80211_add_wpa(frm, vap);
if ((ic->ic_flags & IEEE80211_F_WME) &&
ni->ni_ies.wme_ie != NULL)
frm = ieee80211_add_wme_info(frm, &ic->ic_wme);
frm = ieee80211_add_wme_info(frm, &ic->ic_wme, ni);
/*
* Same deal - only send HT info if we're on an 11n
@ -2881,7 +2907,8 @@ ieee80211_send_mgmt(struct ieee80211_node *ni, int type, int arg)
}
if ((vap->iv_flags & IEEE80211_F_WME) &&
ni->ni_ies.wme_ie != NULL)
frm = ieee80211_add_wme_param(frm, &ic->ic_wme);
frm = ieee80211_add_wme_param(frm, &ic->ic_wme,
!! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD));
if ((ni->ni_flags & HTFLAGS) == HTFLAGS) {
frm = ieee80211_add_htcap_vendor(frm, ni);
frm = ieee80211_add_htinfo_vendor(frm, ni);
@ -3092,7 +3119,8 @@ ieee80211_alloc_proberesp(struct ieee80211_node *bss, int legacy)
}
frm = ieee80211_add_wpa(frm, vap);
if (vap->iv_flags & IEEE80211_F_WME)
frm = ieee80211_add_wme_param(frm, &ic->ic_wme);
frm = ieee80211_add_wme_param(frm, &ic->ic_wme,
!! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD));
if (IEEE80211_IS_CHAN_HT(bss->ni_chan) &&
(vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) &&
legacy != IEEE80211_SEND_LEGACY_11B) {
@ -3490,7 +3518,8 @@ ieee80211_beacon_construct(struct mbuf *m, uint8_t *frm,
frm = ieee80211_add_wpa(frm, vap);
if (vap->iv_flags & IEEE80211_F_WME) {
bo->bo_wme = frm;
frm = ieee80211_add_wme_param(frm, &ic->ic_wme);
frm = ieee80211_add_wme_param(frm, &ic->ic_wme,
!! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD));
}
if (IEEE80211_IS_CHAN_HT(ni->ni_chan) &&
(vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT)) {
@ -3782,7 +3811,8 @@ ieee80211_beacon_update(struct ieee80211_node *ni, struct mbuf *m, int mcast)
wme->wme_hipri_switch_hysteresis;
}
if (isset(bo->bo_flags, IEEE80211_BEACON_WME)) {
(void) ieee80211_add_wme_param(bo->bo_wme, wme);
(void) ieee80211_add_wme_param(bo->bo_wme, wme,
vap->iv_flags_ext & IEEE80211_FEXT_UAPSD);
clrbit(bo->bo_flags, IEEE80211_BEACON_WME);
}
}

View File

@ -1147,8 +1147,11 @@ ieee80211_wme_initparams_locked(struct ieee80211vap *vap)
* field and updates hardware when said field changes.
* Otherwise the hardware is programmed with defaults, not what
* the beacon actually announces.
*
* Note that we can't ever have 0xff as an actual value;
* the only valid values are 0..15.
*/
wme->wme_wmeChanParams.cap_info = 0;
wme->wme_wmeChanParams.cap_info = 0xfe;
/*
* Select mode; we can be called early in which case we

View File

@ -1129,25 +1129,56 @@ sta_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh,
IEEE80211_SCAN_FAIL_STATUS);
}
/*
* Parse the WME IE for QoS and U-APSD information.
*
* Returns -1 if the IE isn't found, 1 if it's found.
*/
int
ieee80211_parse_wmeie(uint8_t *frm, const struct ieee80211_frame *wh,
struct ieee80211_node *ni)
{
u_int len = frm[1];
ni->ni_uapsd = 0;
if (len < sizeof(struct ieee80211_wme_param)-2) {
IEEE80211_DISCARD_IE(ni->ni_vap,
IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME,
wh, "WME", "too short, len %u", len);
return -1;
}
ni->ni_uapsd = frm[WME_CAPINFO_IE_OFFSET];
IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_POWER | IEEE80211_MSG_ASSOC,
ni, "U-APSD settings from STA: 0x%02x", ni->ni_uapsd);
return 1;
}
int
ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm,
const struct ieee80211_frame *wh)
const struct ieee80211_frame *wh, uint8_t *qosinfo)
{
#define MS(_v, _f) (((_v) & _f) >> _f##_S)
struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme;
u_int len = frm[1], qosinfo;
u_int len = frm[1], qosinfo_count;
int i;
*qosinfo = 0;
if (len < sizeof(struct ieee80211_wme_param)-2) {
IEEE80211_DISCARD_IE(vap,
IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME,
wh, "WME", "too short, len %u", len);
return -1;
}
qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)];
qosinfo &= WME_QOSINFO_COUNT;
*qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)];
qosinfo_count = *qosinfo & WME_QOSINFO_COUNT;
/* XXX do proper check for wraparound */
if (qosinfo == wme->wme_wmeChanParams.cap_info)
if (qosinfo_count == wme->wme_wmeChanParams.cap_info)
return 0;
frm += __offsetof(struct ieee80211_wme_param, params_acParams);
for (i = 0; i < WME_NUM_AC; i++) {
@ -1159,9 +1190,18 @@ ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm,
wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN);
wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX);
wmep->wmep_txopLimit = le16dec(frm+2);
IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME,
"%s: WME: %d: acm=%d aifsn=%d logcwmin=%d logcwmax=%d txopLimit=%d\n",
__func__,
i,
wmep->wmep_acm,
wmep->wmep_aifsn,
wmep->wmep_logcwmin,
wmep->wmep_logcwmax,
wmep->wmep_txopLimit);
frm += 4;
}
wme->wme_wmeChanParams.cap_info = qosinfo;
wme->wme_wmeChanParams.cap_info = qosinfo_count;
return 1;
#undef MS
}
@ -1350,11 +1390,12 @@ sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype,
struct ieee80211com *ic = ni->ni_ic;
struct ieee80211_channel *rxchan = ic->ic_curchan;
struct ieee80211_frame *wh;
int ht_state_change = 0, do_ht = 0;
uint8_t *frm, *efrm;
uint8_t *rates, *xrates, *wme, *htcap, *htinfo;
uint8_t *vhtcap, *vhtopmode;
uint8_t rate;
int ht_state_change = 0, do_ht = 0;
uint8_t qosinfo;
wh = mtod(m0, struct ieee80211_frame *);
frm = (uint8_t *)&wh[1];
@ -1443,9 +1484,18 @@ sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype,
/* XXX statistic */
}
if (scan.wme != NULL &&
(ni->ni_flags & IEEE80211_NODE_QOS) &&
ieee80211_parse_wmeparams(vap, scan.wme, wh) > 0)
(ni->ni_flags & IEEE80211_NODE_QOS)) {
int _retval;
if ((_retval = ieee80211_parse_wmeparams(vap,
scan.wme, wh, &qosinfo)) >= 0) {
if (qosinfo & WME_CAPINFO_UAPSD_EN)
ni->ni_flags |=
IEEE80211_NODE_UAPSD;
if (_retval > 0)
ieee80211_wme_updateparams(vap);
}
} else
ni->ni_flags &= ~IEEE80211_NODE_UAPSD;
#ifdef IEEE80211_SUPPORT_SUPERG
if (scan.ath != NULL)
ieee80211_parse_athparams(ni, scan.ath, wh);
@ -1782,7 +1832,7 @@ sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype,
if (ni->ni_jointime == 0)
ni->ni_jointime = time_uptime;
if (wme != NULL &&
ieee80211_parse_wmeparams(vap, wme, wh) >= 0) {
ieee80211_parse_wmeparams(vap, wme, wh, &qosinfo) >= 0) {
ni->ni_flags |= IEEE80211_NODE_QOS;
ieee80211_wme_updateparams(vap);
} else

View File

@ -40,5 +40,12 @@ void ieee80211_sta_vattach(struct ieee80211vap *);
* Used by the adhoc/mesh/tdma paths.
*/
extern int ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm,
const struct ieee80211_frame *wh);
const struct ieee80211_frame *wh, uint8_t *qosinfo);
/*
* Used in the hostap path.
*/
extern int ieee80211_parse_wmeie(uint8_t *frm,
const struct ieee80211_frame *wh, struct ieee80211_node *ni);
#endif /* !_NET80211_IEEE80211_STA_H_ */

View File

@ -581,6 +581,9 @@ struct ieee80211vap {
void (*iv_updateslot)(struct ieee80211vap *);
struct task iv_slot_task; /* deferred slot time update */
/* per-vap U-APSD state */
uint8_t iv_uapsdinfo; /* sta mode QoS Info flags */
uint64_t iv_spare[6];
};
MALLOC_DECLARE(M_80211_VAP);
@ -662,6 +665,7 @@ MALLOC_DECLARE(M_80211_VAP);
#define IEEE80211_FEXT_FRAG_OFFLOAD 0x00200000 /* CONF: hardware does 802.11 fragmentation + assignment */
#define IEEE80211_FEXT_VHT 0x00400000 /* CONF: VHT support */
#define IEEE80211_FEXT_QUIET_IE 0x00800000 /* STATUS: quiet IE in a beacon has been added */
#define IEEE80211_FEXT_UAPSD 0x01000000 /* CONF: enable U-APSD */
#define IEEE80211_FEXT_BITS \
"\20\2INACT\3SCANWAIT\4BGSCAN\5WPS\6TSN\7SCANREQ\10RESUME" \