From: Kirill A. Korinsky Subject: Re: sys/ieee80211: support of uAPSD; sys/iwx: enable uAPSD when supported by AP To: tech@openbsd.org Date: Wed, 20 May 2026 01:21:49 +0200 On Mon, 18 May 2026 10:40:08 +0200, Stefan Sperling wrote: > > This looks great. I mostly have small cosmetic concerns which could > be fixed after commit. And I spotted one potential problem which > could cause regressions in older drivers. See below. > > On Mon, May 18, 2026 at 01:08:00AM +0200, Kirill A. Korinsky wrote: > > +static uint8_t > > +iwx_uapsd_ac_flags(struct ieee80211_node *ni) > > +{ > > + uint8_t ac_flags = 0; > > + > > + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) > > + ac_flags |= 1 << 0; > > Could we please avoid magic numbers by using EDCA_AC_BE here instead of 0? > And similarly for the following lines: > > > + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) > > + ac_flags |= 1 << 1; > > + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) > > + ac_flags |= 1 << 2; > > + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) > > + ac_flags |= 1 << 3; > > + > > + return ac_flags; > > +} > > > @@ -6673,6 +6740,21 @@ iwx_set_pslevel(struct iwx_softc *sc, in > > IWX_POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); > > mcmd.rx_data_timeout = htole32(pmgt->rxtimeout * 1024); > > mcmd.tx_data_timeout = htole32(pmgt->txtimeout * 1024); > > + if (ni->ni_uapsd && > > + (sc->sc_capaflags & IWX_UCODE_TLV_FLAGS_UAPSD_SUPPORT)) { > > For clarity, could we rename ni->ni_uapsd to ni->ni_use_uapsd or similar? > Or make it a flag such as ni->ni_flags & IEEE80211_NODE_UAPSD? > > This would provide a very obvious indication to readers of this code > that ni_uapsd is a configuration flag, rather than some bit of data which > was parsed from the node's beacon (or assoc request in hostap mode). > > > @@ -1601,6 +1611,9 @@ ieee80211_get_assoc_req(struct ieee80211 > > IEEE80211_HE_MCS_NSS_SIZE(ic->ic_he_phy_cap[0]); > > } > > > > + addwme = (ni->ni_flags & IEEE80211_NODE_QOS) || > > + (ic->ic_flags & IEEE80211_F_HTON); > > Shouldn't this be && instead of || ? > > So far we've only enabled WME in 11n and 11ac modes. > I don't think enabling WME in 11a/b/g only mode (and drivers) makes sense, > even if the peer supports QoS. Because this could break some of the older > drivers, which are not being tested very often. > Thanks for feedback. I've addressed everything, moved logic after addwme to actually used it and added comment why here is && and not || Ok? Index: sys/dev/pci/if_iwx.c =================================================================== RCS file: /home/cvs/src/sys/dev/pci/if_iwx.c,v diff -u -p -r1.228 if_iwx.c --- sys/dev/pci/if_iwx.c 2 Apr 2026 11:19:45 -0000 1.228 +++ sys/dev/pci/if_iwx.c 19 May 2026 22:43:26 -0000 @@ -6612,6 +6612,73 @@ iwx_update_beacon_abort(struct iwx_softc return iwx_beacon_filter_send_cmd(sc, &cmd); } +static uint8_t +iwx_uapsd_qndp_tid(struct ieee80211com *ic, uint8_t acs) +{ + if ((acs & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) && + !ic->ic_edca_ac[EDCA_AC_VO].ac_acm) + return 6; + if ((acs & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) && + !ic->ic_edca_ac[EDCA_AC_VI].ac_acm) + return 5; + if ((acs & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) && + !ic->ic_edca_ac[EDCA_AC_BE].ac_acm) + return 0; + if ((acs & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) && + !ic->ic_edca_ac[EDCA_AC_BK].ac_acm) + return 1; + return 0; +} + +static uint8_t +iwx_uapsd_acs(struct ieee80211_node *ni) +{ + uint8_t acs = 0; + + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) + acs |= 1 << IWX_AC_VO; + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) + acs |= 1 << IWX_AC_VI; + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) + acs |= 1 << IWX_AC_BE; + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) + acs |= 1 << IWX_AC_BK; + + return acs | (acs << IWX_AC_NUM); +} + +static uint8_t +iwx_uapsd_ac_flags(struct ieee80211_node *ni) +{ + uint8_t ac_flags = 0; + + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) + ac_flags |= 1 << EDCA_AC_BE; + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) + ac_flags |= 1 << EDCA_AC_BK; + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) + ac_flags |= 1 << EDCA_AC_VI; + if (ni->ni_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) + ac_flags |= 1 << EDCA_AC_VO; + + return ac_flags; +} + +static uint8_t +iwx_uapsd_sp_length(struct ieee80211_node *ni) +{ + switch (ni->ni_uapsd_maxsp & IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK) { + case IEEE80211_WMM_IE_STA_QOSINFO_SP_2: + return 2; + case IEEE80211_WMM_IE_STA_QOSINFO_SP_4: + return 4; + case IEEE80211_WMM_IE_STA_QOSINFO_SP_6: + return 6; + default: + return 128; + } +} + int iwx_set_pslevel(struct iwx_softc *sc, int dtim, int level, int async) { @@ -6673,6 +6740,21 @@ iwx_set_pslevel(struct iwx_softc *sc, in IWX_POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); mcmd.rx_data_timeout = htole32(pmgt->rxtimeout * 1024); mcmd.tx_data_timeout = htole32(pmgt->txtimeout * 1024); + if ((ni->ni_flags & IEEE80211_NODE_UAPSD) && + (sc->sc_capaflags & IWX_UCODE_TLV_FLAGS_UAPSD_SUPPORT)) { + mcmd.flags |= + htole16(IWX_POWER_FLAGS_ADVANCE_PM_ENA_MSK); + mcmd.flags |= + htole16(IWX_POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK); + mcmd.rx_data_timeout_uapsd = + htole32(IWX_UAPSD_PS_RX_DATA_TIMEOUT); + mcmd.tx_data_timeout_uapsd = + htole32(IWX_UAPSD_PS_TX_DATA_TIMEOUT); + mcmd.qndp_tid = iwx_uapsd_qndp_tid(ic, ni->ni_uapsd_ac); + mcmd.uapsd_ac_flags = iwx_uapsd_ac_flags(ni); + mcmd.uapsd_max_sp = ni->ni_uapsd_maxsp & + IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK; + } if (skip_dtim != 0) { mcmd.flags |= htole16(IWX_POWER_FLAGS_SKIP_OVER_DTIM_MSK); mcmd.skip_dtim_periods = skip_dtim + 1; @@ -6835,6 +6917,12 @@ iwx_add_sta_cmd(struct iwx_softc *sc, st break; } } + if ((in->in_ni.ni_flags & IEEE80211_NODE_UAPSD) && + (sc->sc_capaflags & IWX_UCODE_TLV_FLAGS_UAPSD_SUPPORT)) { + add_sta_cmd.modify_mask |= IWX_STA_MODIFY_UAPSD_ACS; + add_sta_cmd.uapsd_acs = iwx_uapsd_acs(&in->in_ni); + add_sta_cmd.sp_length = iwx_uapsd_sp_length(&in->in_ni); + } status = IWX_ADD_STA_SUCCESS; err = iwx_send_cmd_pdu_status(sc, IWX_ADD_STA, sizeof(add_sta_cmd), @@ -7041,6 +7129,12 @@ iwx_mld_add_sta_cmd(struct iwx_softc *sc if (in->in_ni.ni_flags & IEEE80211_NODE_MFP) sta_cmd.mfp = htole32(1); + if ((in->in_ni.ni_flags & IEEE80211_NODE_UAPSD) && + (sc->sc_capaflags & IWX_UCODE_TLV_FLAGS_UAPSD_SUPPORT)) { + sta_cmd.sp_length = + htole32(iwx_uapsd_sp_length(&in->in_ni)); + sta_cmd.uapsd_acs = htole32(iwx_uapsd_acs(&in->in_ni)); + } return iwx_send_cmd_pdu(sc, IWX_WIDE_ID(IWX_MAC_CONF_GROUP, IWX_STA_CONFIG_CMD), @@ -11104,6 +11198,33 @@ iwx_rx_pkt(struct iwx_softc *sc, struct break; } + case IWX_PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION: { + struct ieee80211com *ic = &sc->sc_ic; + struct ifnet *ifp = &ic->ic_if; + struct ieee80211_node *ni = ic->ic_bss; + struct iwx_uapsd_misbehaving_ap_notif *notif; + + SYNC_RESP_STRUCT(notif, pkt); + + if (ni == NULL || + (ni->ni_flags & IEEE80211_NODE_UAPSD) == 0) + break; + + if (ifp->if_flags & IFF_DEBUG) + printf("%s: firmware reported uAPSD " + "misbehaving AP on sta %u, disabling " + "uAPSD\n", ifp->if_xname, + le32toh(notif->sta_id)); + + ni->ni_flags &= ~IEEE80211_NODE_UAPSD; + ni->ni_uapsd_ac = 0; + ni->ni_uapsd_maxsp = 0; + + if (ic->ic_flags & IEEE80211_F_PMGTON) + (void)iwx_set_pslevel(sc, 0, 3, 1); + break; + } + case IWX_WIDE_ID(IWX_MAC_CONF_GROUP, IWX_SESSION_PROTECTION_NOTIF): { struct iwx_session_prot_notif *notif; @@ -12510,6 +12631,11 @@ iwx_attach(struct device *parent, struct IEEE80211_C_SHPREAMBLE | /* short preamble supported */ IEEE80211_C_MFP; /* management frame protection */ ic->ic_flags |= IEEE80211_F_PMGTON; + ic->ic_uapsd_ac = IEEE80211_WMM_IE_STA_QOSINFO_AC_VO | + IEEE80211_WMM_IE_STA_QOSINFO_AC_VI | + IEEE80211_WMM_IE_STA_QOSINFO_AC_BK | + IEEE80211_WMM_IE_STA_QOSINFO_AC_BE; + ic->ic_uapsd_maxsp = IEEE80211_WMM_IE_STA_QOSINFO_SP_ALL; ic->ic_htcaps = IEEE80211_HTCAP_SGI20 | IEEE80211_HTCAP_SGI40; ic->ic_htcaps |= IEEE80211_HTCAP_CBW20_40; Index: sys/dev/pci/if_iwxreg.h =================================================================== RCS file: /home/cvs/src/sys/dev/pci/if_iwxreg.h,v diff -u -p -r1.74 if_iwxreg.h --- sys/dev/pci/if_iwxreg.h 26 Mar 2026 12:15:48 -0000 1.74 +++ sys/dev/pci/if_iwxreg.h 19 May 2026 22:22:58 -0000 @@ -5473,6 +5473,8 @@ struct iwx_mac_power_cmd { #define IWX_DEFAULT_PS_TX_DATA_TIMEOUT (100 * 1000) #define IWX_DEFAULT_PS_RX_DATA_TIMEOUT (100 * 1000) +#define IWX_UAPSD_PS_TX_DATA_TIMEOUT (50 * 1000) +#define IWX_UAPSD_PS_RX_DATA_TIMEOUT (50 * 1000) #define IWX_NDTIMRANGES 3 #define IWX_NPOWERLEVELS 6 @@ -8312,6 +8314,7 @@ struct iwx_umac_scan_iter_complete_notif * @IWX_STA_MODIFY_QUEUE_REMOVAL: this command removes a queue * @IWX_STA_MODIFY_TID_DISABLE_TX: this command modifies %tid_disable_tx * @IWX_STA_MODIFY_TX_RATE: unused + * @IWX_STA_MODIFY_UAPSD_ACS: this command modifies %uapsd_acs * @IWX_STA_MODIFY_ADD_BA_TID: this command modifies %add_immediate_ba_tid * @IWX_STA_MODIFY_REMOVE_BA_TID: this command modifies %remove_immediate_ba_tid * @IWX_STA_MODIFY_SLEEPING_STA_TX_COUNT: this command modifies %sleep_tx_count @@ -8321,6 +8324,7 @@ struct iwx_umac_scan_iter_complete_notif #define IWX_STA_MODIFY_QUEUE_REMOVAL (1 << 0) #define IWX_STA_MODIFY_TID_DISABLE_TX (1 << 1) #define IWX_STA_MODIFY_TX_RATE (1 << 2) +#define IWX_STA_MODIFY_UAPSD_ACS IWX_STA_MODIFY_TX_RATE #define IWX_STA_MODIFY_ADD_BA_TID (1 << 3) #define IWX_STA_MODIFY_REMOVE_BA_TID (1 << 4) #define IWX_STA_MODIFY_SLEEPING_STA_TX_COUNT (1 << 5) Index: sbin/ifconfig/ifconfig.8 =================================================================== RCS file: /home/cvs/src/sbin/ifconfig/ifconfig.8,v diff -u -p -r1.413 ifconfig.8 --- sbin/ifconfig/ifconfig.8 3 Dec 2025 10:19:27 -0000 1.413 +++ sbin/ifconfig/ifconfig.8 19 May 2026 22:22:58 -0000 @@ -1111,6 +1111,14 @@ flag will disable MIMO reception and tra and wireless network device support MIMO. This flag can be used to work around packet loss in 11n mode if the wireless network device has unused antenna connectors. +.It uapsd +The +.Ql uapsd +flag will enable Unscheduled Automatic Power Save Delivery +(u-APSD) support. +This can reduce power consumption while the interface is idle or +carrying little traffic, but may increase latency for sparse traffic. +This flag is disabled by default. .It stayauth The .Ql stayauth Index: sys/net80211/ieee80211.h =================================================================== RCS file: /home/cvs/src/sys/net80211/ieee80211.h,v diff -u -p -r1.66 ieee80211.h --- sys/net80211/ieee80211.h 29 Mar 2026 21:16:21 -0000 1.66 +++ sys/net80211/ieee80211.h 19 May 2026 22:22:58 -0000 @@ -193,6 +193,21 @@ struct ieee80211_htframe_addr4 { /* 11n #define IEEE80211_QOS_EOSP 0x0010 #define IEEE80211_QOS_TID 0x000f +#define IEEE80211_WMM_IE_AP_QOSINFO_UAPSD 0x80 + +#define IEEE80211_WMM_IE_STA_QOSINFO_AC_VO 0x01 +#define IEEE80211_WMM_IE_STA_QOSINFO_AC_VI 0x02 +#define IEEE80211_WMM_IE_STA_QOSINFO_AC_BK 0x04 +#define IEEE80211_WMM_IE_STA_QOSINFO_AC_BE 0x08 +#define IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK 0x0f + +#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 + /* * Control frames. */ Index: sys/net80211/ieee80211_input.c =================================================================== RCS file: /home/cvs/src/sys/net80211/ieee80211_input.c,v diff -u -p -r1.261 ieee80211_input.c --- sys/net80211/ieee80211_input.c 26 Mar 2026 12:15:01 -0000 1.261 +++ sys/net80211/ieee80211_input.c 19 May 2026 22:43:26 -0000 @@ -87,6 +87,7 @@ int ieee80211_parse_edca_params_body(str const u_int8_t *); int ieee80211_parse_edca_params(struct ieee80211com *, const u_int8_t *); int ieee80211_parse_wmm_params(struct ieee80211com *, const u_int8_t *); +int ieee80211_parse_wmm_qosinfo(const u_int8_t *, u_int8_t *); enum ieee80211_cipher ieee80211_parse_rsn_cipher(const u_int8_t *); enum ieee80211_akm ieee80211_parse_rsn_akm(const u_int8_t *); int ieee80211_parse_rsn_body(struct ieee80211com *, const u_int8_t *, @@ -1387,6 +1388,32 @@ ieee80211_parse_wmm_params(struct ieee80 return ieee80211_parse_edca_params_body(ic, frm + 8); } +int +ieee80211_parse_wmm_qosinfo(const u_int8_t *frm, u_int8_t *qosinfo) +{ + if (frm[1] < 7) + return IEEE80211_REASON_IE_INVALID; + + *qosinfo = frm[8]; + return 0; +} + +void +ieee80211_setup_uapsd(struct ieee80211com *ic, struct ieee80211_node *ni, + int peer_uapsd) +{ + if (peer_uapsd && (ic->ic_userflags & IEEE80211_F_UAPSD) && + (ni->ni_flags & IEEE80211_NODE_QOS)) { + ni->ni_flags |= IEEE80211_NODE_UAPSD; + ni->ni_uapsd_ac = ic->ic_uapsd_ac; + ni->ni_uapsd_maxsp = ic->ic_uapsd_maxsp; + } else { + ni->ni_flags &= ~IEEE80211_NODE_UAPSD; + ni->ni_uapsd_ac = 0; + ni->ni_uapsd_maxsp = 0; + } +} + enum ieee80211_cipher ieee80211_parse_rsn_cipher(const u_int8_t selector[4]) { @@ -1627,7 +1654,8 @@ ieee80211_recv_probe_resp(struct ieee802 const u_int8_t *tstamp, *ssid, *rates, *xrates, *edcaie, *wmmie, *tim; const u_int8_t *rsnie, *wpaie, *htcaps, *htop, *vhtcaps, *vhtop, *hecaps, *heop; u_int16_t capinfo, bintval; - u_int8_t chan, bchan, erp; + u_int8_t chan, bchan, erp, wmm_qosinfo; + int has_wmm_qosinfo = 0; int is_new; /* @@ -1862,6 +1890,9 @@ ieee80211_recv_probe_resp(struct ieee802 ni->ni_dtimcount = tim[2]; ni->ni_dtimperiod = tim[3]; } + if (wmmie != NULL && + ieee80211_parse_wmm_qosinfo(wmmie, &wmm_qosinfo) == 0) + has_wmm_qosinfo = 1; /* * When operating in station mode, check for state updates @@ -1972,11 +2003,18 @@ ieee80211_recv_probe_resp(struct ieee802 else ni->ni_flags &= ~IEEE80211_NODE_QOS; } + ieee80211_setup_uapsd(ic, ni, has_wmm_qosinfo && + (wmm_qosinfo & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD)); if (ic->ic_state == IEEE80211_S_SCAN || (ic->ic_flags & IEEE80211_F_BGSCAN)) { struct ieee80211_rsnparams rsn, wpa; + if (edcaie != NULL || wmmie != NULL) + ni->ni_flags |= IEEE80211_NODE_QOS; + else + ni->ni_flags &= ~IEEE80211_NODE_QOS; + ni->ni_rsnprotos = IEEE80211_PROTO_NONE; ni->ni_supported_rsnprotos = IEEE80211_PROTO_NONE; ni->ni_rsnakms = 0; @@ -1997,6 +2035,9 @@ ieee80211_recv_probe_resp(struct ieee802 ni->ni_supported_rsnakms |= wpa.rsn_akms; } + ieee80211_setup_uapsd(ic, ni, has_wmm_qosinfo && + (wmm_qosinfo & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD)); + /* * If the AP advertises both WPA and RSN IEs (WPA1+WPA2), * we only use the highest protocol version we support. @@ -2625,7 +2666,8 @@ ieee80211_recv_assoc_resp(struct ieee802 const u_int8_t *rates, *xrates, *edcaie, *wmmie, *htcaps, *htop; const u_int8_t *vhtcaps, *vhtop, *hecaps, *heop; u_int16_t capinfo, status, associd; - u_int8_t rate; + u_int8_t rate, wmm_qosinfo; + int has_wmm_qosinfo = 0; if (ic->ic_opmode != IEEE80211_M_STA || ic->ic_state != IEEE80211_S_ASSOC) { @@ -2713,6 +2755,9 @@ ieee80211_recv_assoc_resp(struct ieee802 } frm += 2 + frm[1]; } + if (wmmie != NULL && + ieee80211_parse_wmm_qosinfo(wmmie, &wmm_qosinfo) == 0) + has_wmm_qosinfo = 1; /* supported rates element is mandatory */ if (rates == NULL || rates[1] > IEEE80211_RATE_MAXSIZE) { DPRINTF(("invalid supported rates element\n")); @@ -2741,6 +2786,8 @@ ieee80211_recv_assoc_resp(struct ieee802 else /* for Reassociation */ ni->ni_flags &= ~IEEE80211_NODE_QOS; } + ieee80211_setup_uapsd(ic, ni, has_wmm_qosinfo && + (wmm_qosinfo & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD)); if (htcaps) ieee80211_setup_htcaps(ni, htcaps + 2, htcaps[1]); if (htop) Index: sys/net80211/ieee80211_ioctl.h =================================================================== RCS file: /home/cvs/src/sys/net80211/ieee80211_ioctl.h,v diff -u -p -r1.45 ieee80211_ioctl.h --- sys/net80211/ieee80211_ioctl.h 26 Mar 2026 12:15:01 -0000 1.45 +++ sys/net80211/ieee80211_ioctl.h 19 May 2026 22:22:58 -0000 @@ -410,7 +410,9 @@ struct ieee80211_nodereq_all { #define IEEE80211_F_HOSTAPMASK 0x00000003 #define IEEE80211_F_STAYAUTH 0x00000004 /* CONF: ignore deauth */ #define IEEE80211_F_NOMIMO 0x00000008 /* CONF: disable MIMO */ -#define IEEE80211_F_USERBITS "\20\01HIDENWID\02NOBRIDGE\03STAYAUTH\04NOMIMO" +#define IEEE80211_F_UAPSD 0x00000010 /* CONF: enable u-APSD */ +#define IEEE80211_F_USERBITS "\20\01HIDENWID\02NOBRIDGE\03STAYAUTH\04NOMIMO" \ + "\05UAPSD" struct ieee80211_flags { const char *f_name; @@ -421,7 +423,8 @@ struct ieee80211_flags { { "hidenwid", IEEE80211_F_HIDENWID }, \ { "nobridge", IEEE80211_F_NOBRIDGE }, \ { "stayauth", IEEE80211_F_STAYAUTH }, \ - { "nomimo", IEEE80211_F_NOMIMO } \ + { "nomimo", IEEE80211_F_NOMIMO }, \ + { "uapsd", IEEE80211_F_UAPSD } \ } #define SIOCG80211FLAGS _IOWR('i', 216, struct ifreq) Index: sys/net80211/ieee80211_node.h =================================================================== RCS file: /home/cvs/src/sys/net80211/ieee80211_node.h,v diff -u -p -r1.101 ieee80211_node.h --- sys/net80211/ieee80211_node.h 29 Mar 2026 21:16:21 -0000 1.101 +++ sys/net80211/ieee80211_node.h 19 May 2026 22:43:26 -0000 @@ -306,6 +306,8 @@ struct ieee80211_node { /* power saving mode */ u_int8_t ni_pwrsave; struct mbuf_queue ni_savedq; /* packets queued for pspoll */ + u_int8_t ni_uapsd_ac; + u_int8_t ni_uapsd_maxsp; /* RSN */ struct timeout ni_eapol_to; @@ -446,6 +448,7 @@ struct ieee80211_node { #define IEEE80211_NODE_HE 0x200000 /* HE negotiated */ #define IEEE80211_NODE_HECAP 0x400000 /* claims to support HE */ #define IEEE80211_NODE_CSA 0x800000 /* channel switch announced */ +#define IEEE80211_NODE_UAPSD 0x1000000 /* uAPSD negotiated */ /* If not NULL, this function gets called when ni_refcnt hits zero. */ void (*ni_unref_cb)(struct ieee80211com *, Index: sys/net80211/ieee80211_output.c =================================================================== RCS file: /home/cvs/src/sys/net80211/ieee80211_output.c,v diff -u -p -r1.147 ieee80211_output.c --- sys/net80211/ieee80211_output.c 19 Mar 2026 16:50:32 -0000 1.147 +++ sys/net80211/ieee80211_output.c 19 May 2026 23:11:15 -0000 @@ -985,6 +985,16 @@ ieee80211_add_erp(u_int8_t *frm, struct } #endif /* IEEE80211_STA_ONLY */ +uint8_t +ieee80211_uapsd_qosinfo(struct ieee80211com *ic) +{ + if ((ic->ic_userflags & IEEE80211_F_UAPSD) == 0) + return 0; + return (ic->ic_uapsd_ac & IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) | + ((ic->ic_uapsd_maxsp & IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK) << + IEEE80211_WMM_IE_STA_QOSINFO_SP_SHIFT); +} + /* * Add a QoS Capability element to a frame (see 7.3.2.35). */ @@ -993,7 +1003,7 @@ ieee80211_add_qos_capability(u_int8_t *f { *frm++ = IEEE80211_ELEMID_QOS_CAP; *frm++ = 1; - *frm++ = 0; /* QoS Info */ + *frm++ = ieee80211_uapsd_qosinfo(ic); return frm; } @@ -1011,7 +1021,7 @@ ieee80211_add_wme_info(uint8_t *frm, str *frm++ = 2; /* OUI type */ *frm++ = 0; /* OUI subtype */ *frm++ = 1; /* version */ - *frm++ = 0; /* info */ + *frm++ = ieee80211_uapsd_qosinfo(ic); return frm; } @@ -1585,7 +1595,7 @@ ieee80211_get_assoc_req(struct ieee80211 struct mbuf *m; u_int8_t *frm; u_int16_t capinfo; - int addvht = 0; + int addvht = 0, addwme; u_int hecapslen = 0; if ((ic->ic_flags & IEEE80211_F_VHTON) && ni->ni_chan != NULL && @@ -1601,6 +1611,10 @@ ieee80211_get_assoc_req(struct ieee80211 IEEE80211_HE_MCS_NSS_SIZE(ic->ic_he_phy_cap[0]); } + /* Keep QoS Capability aligned with ieee80211_add_wme_info(). */ + addwme = (ni->ni_flags & IEEE80211_NODE_QOS) && + (ic->ic_flags & IEEE80211_F_HTON); + m = ieee80211_getmgmt(M_DONTWAIT, MT_DATA, 2 + 2 + ((type == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) ? @@ -1612,11 +1626,12 @@ ieee80211_get_assoc_req(struct ieee80211 (((ic->ic_flags & IEEE80211_F_RSNON) && (ni->ni_rsnprotos & IEEE80211_PROTO_RSN)) ? 2 + IEEE80211_RSNIE_MAXLEN : 0) + - ((ni->ni_flags & IEEE80211_NODE_QOS) ? 2 + 1 : 0) + + (addwme ? 2 + 1 : 0) + (((ic->ic_flags & IEEE80211_F_RSNON) && (ni->ni_rsnprotos & IEEE80211_PROTO_WPA)) ? 2 + IEEE80211_WPAIE_MAXLEN : 0) + - ((ic->ic_flags & IEEE80211_F_HTON) ? 28 + 9 : 0) + + ((ic->ic_flags & IEEE80211_F_HTON) ? 28 : 0) + + (addwme ? 9 : 0) + (addvht ? 14 : 0) + hecapslen); if (m == NULL) @@ -1644,15 +1659,15 @@ ieee80211_get_assoc_req(struct ieee80211 if ((ic->ic_flags & IEEE80211_F_RSNON) && (ni->ni_rsnprotos & IEEE80211_PROTO_RSN)) frm = ieee80211_add_rsn(frm, ic, ni); - if (ni->ni_flags & IEEE80211_NODE_QOS) + if (addwme) frm = ieee80211_add_qos_capability(frm, ic); if ((ic->ic_flags & IEEE80211_F_RSNON) && (ni->ni_rsnprotos & IEEE80211_PROTO_WPA)) frm = ieee80211_add_wpa(frm, ic, ni); - if (ic->ic_flags & IEEE80211_F_HTON) { + if (ic->ic_flags & IEEE80211_F_HTON) frm = ieee80211_add_htcaps(frm, ic); + if (addwme) frm = ieee80211_add_wme_info(frm, ic); - } if (addvht) frm = ieee80211_add_vhtcaps(frm, ic); if (hecapslen) Index: sys/net80211/ieee80211_proto.c =================================================================== RCS file: /home/cvs/src/sys/net80211/ieee80211_proto.c,v diff -u -p -r1.112 ieee80211_proto.c --- sys/net80211/ieee80211_proto.c 19 Mar 2026 16:50:32 -0000 1.112 +++ sys/net80211/ieee80211_proto.c 19 May 2026 22:43:26 -0000 @@ -1376,7 +1376,7 @@ justcleanup: else printf(" start %u%sMb", rate / 2, (rate & 1) ? ".5" : ""); - printf(" %s preamble %s slot time%s%s%s\n", + printf(" %s preamble %s slot time%s%s%s%s\n", (ic->ic_flags & IEEE80211_F_SHPREAMBLE) ? "short" : "long", (ic->ic_flags & IEEE80211_F_SHSLOT) ? @@ -1386,7 +1386,12 @@ justcleanup: (ni->ni_flags & IEEE80211_NODE_HT) ? " HT enabled" : "", (ni->ni_flags & IEEE80211_NODE_VHT) ? - " VHT enabled" : ""); + " VHT enabled" : "", + ic->ic_uapsd_ac != 0 ? + ((ni->ni_flags & IEEE80211_NODE_UAPSD) ? + " uAPSD enabled" : + " uAPSD disabled") : + ""); } if (!(ic->ic_flags & IEEE80211_F_RSNON)) { /* Index: sys/net80211/ieee80211_var.h =================================================================== RCS file: /home/cvs/src/sys/net80211/ieee80211_var.h,v diff -u -p -r1.114 ieee80211_var.h --- sys/net80211/ieee80211_var.h 19 Mar 2026 16:50:32 -0000 1.114 +++ sys/net80211/ieee80211_var.h 19 May 2026 22:22:58 -0000 @@ -344,6 +344,8 @@ struct ieee80211com { u_int ic_edca_txop_count[EDCA_NUM_AC]; struct timeval ic_edca_txop_time[EDCA_NUM_AC]; u_int16_t ic_tid_noack; + u_int8_t ic_uapsd_ac; + u_int8_t ic_uapsd_maxsp; u_int8_t ic_globalcnt[EAPOL_KEY_NONCE_LEN]; u_int8_t ic_nonce[EAPOL_KEY_NONCE_LEN]; u_int8_t ic_psk[IEEE80211_PMK_LEN]; -- wbr, Kirill