Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
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

Download raw body.

Thread
On Mon, 18 May 2026 10:40:08 +0200,
Stefan Sperling <stsp@stsp.name> 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