Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: sys/ieee80211: support of uAPSD; sys/iwx: enable uAPSD when supported by AP
To:
tech@openbsd.org
Date:
Thu, 28 May 2026 11:55:13 +0200

Download raw body.

Thread
On Thu, May 28, 2026 at 11:13:07AM +0200, Kirill A. Korinsky wrote:
> Just a ping.

Still ok by me.

> 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
> 
>