Index | Thread | Search

From:
Peter Hessler <phessler@theapt.org>
Subject:
Re: sys/iwx: support of 160Mhz window at 5Ghz
To:
tech@openbsd.org
Date:
Tue, 24 Mar 2026 18:49:18 +0100

Download raw body.

Thread
On 2026 Mar 23 (Mon) at 03:22:11 +0100 (+0100), Kirill A. Korinsky wrote:
:On Sun, 22 Mar 2026 22:00:50 +0100,
:Stefan Sperling <stsp@stsp.name> wrote:
:> 
:> On Sun, Mar 22, 2026 at 09:56:18PM +0100, Stefan Sperling wrote:
:> > On Sun, Mar 22, 2026 at 09:07:37PM +0100, Kirill A. Korinsky wrote:
:> > > And in beacon I see: Channel Width: 80 MHz, 160 MHz or 80+80 MHz BSS
:> > > Bandwidth (1).
:> > 
:> > I checked our net80211 headers first, which still list value 2 as 160 MHz.
:> > 
:> > Turns out the 802.11 spec has since deprecated values 2 and 3. Wireshark is
:> > correct that this value by itself doesn't mean anything and can be set to 1
:> > in all cases. We need to check the two bytes following this value specify
:> > the center frequency of two 80 MHz portions of a 160 MHz channel.
:> 
:> And in case it wasn't clear, this means that the function
:> ieee80211_node_supports_vht_chan160() will always return false at present
:> because it checks for the deprecated value 2 here:
:> 
:> 	return (op_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160);
:> 
:> Which would explain why 160 MHz is not actually being used for transmit
:> with your patch.
:> 
:
:Thanks for direction, it allows to cook a better diff.
:
:With it my unifi network says that this laptop is connected to network with
:Rx Rate 1.56 Gbps and Tx Rate 1.30 Gbps.
:
:I had added instruments to decode PHY width/NSS/MCS/SGI and let say on my
:iperf tests like 95% of frames were with 160Mhz window.
:
:But I still limited for ~400 Mbits/sec (without this diff it is ~350).
:
:My main thought that we have hardcoded MPDU as 3895, when Linux driver uses
:11454. Plus it may need a bit more bits to set inside vhtcaps. I only
:touched antenna patterns and TX/RX STBC with following Linux logic.
:
:Frankly, I think that optimization of speed should be the next diff, and
:keep this one is focused on 160Mhz window.
:

I still like this.

OK


:
:Index: sys/dev/pci/if_iwx.c
:===================================================================
:RCS file: /home/cvs/src/sys/dev/pci/if_iwx.c,v
:diff -u -p -r1.223 if_iwx.c
:--- sys/dev/pci/if_iwx.c	14 Mar 2026 15:37:44 -0000	1.223
:+++ sys/dev/pci/if_iwx.c	23 Mar 2026 00:32:20 -0000
:@@ -3207,6 +3207,8 @@ iwx_init_channel_map(struct iwx_softc *s
: 			    IEEE80211_CHAN_A;
: 		}
: 		channel->ic_freq = ieee80211_ieee2mhz(hw_value, flags);
:+		channel->ic_xflags &= ~(IEEE80211_CHANX_80MHZ |
:+		    IEEE80211_CHANX_160MHZ);
: 
: 		if (!(ch_flags & IWX_NVM_CHANNEL_ACTIVE))
: 			channel->ic_flags |= IEEE80211_CHAN_PASSIVE;
:@@ -3221,6 +3223,8 @@ iwx_init_channel_map(struct iwx_softc *s
: 			channel->ic_flags |= IEEE80211_CHAN_VHT;
: 			if (ch_flags & IWX_NVM_CHANNEL_80MHZ)
: 				channel->ic_xflags |= IEEE80211_CHANX_80MHZ;
:+			if (ch_flags & IWX_NVM_CHANNEL_160MHZ)
:+				channel->ic_xflags |= IEEE80211_CHANX_160MHZ;
: 		}
: 	}
: }
:@@ -3259,8 +3263,28 @@ void
: iwx_setup_vht_rates(struct iwx_softc *sc)
: {
: 	struct ieee80211com *ic = &sc->sc_ic;
:+	uint8_t tx_ant = iwx_fw_valid_tx_ant(sc);
: 	uint8_t rx_ant = iwx_fw_valid_rx_ant(sc);
: 	int n;
:+	int num_tx_ant, num_rx_ant;
:+
:+	ic->ic_vht_rx_max_lgi_mbit_s = 0;
:+	ic->ic_vht_tx_max_lgi_mbit_s = 0;
:+
:+	num_tx_ant = !!(tx_ant & IWX_ANT_A) + !!(tx_ant & IWX_ANT_B) +
:+	    !!(tx_ant & IWX_ANT_C);
:+	num_rx_ant = !!(rx_ant & IWX_ANT_A) + !!(rx_ant & IWX_ANT_B) +
:+	    !!(rx_ant & IWX_ANT_C);
:+
:+	ic->ic_vhtcaps &= ~(IEEE80211_VHTCAP_TX_STBC |
:+	    IEEE80211_VHTCAP_RX_ANT_PATTERN |
:+	    IEEE80211_VHTCAP_TX_ANT_PATTERN);
:+	if (num_tx_ant > 1)
:+		ic->ic_vhtcaps |= IEEE80211_VHTCAP_TX_STBC;
:+	else
:+		ic->ic_vhtcaps |= IEEE80211_VHTCAP_TX_ANT_PATTERN;
:+	if (num_rx_ant == 1)
:+		ic->ic_vhtcaps |= IEEE80211_VHTCAP_RX_ANT_PATTERN;
: 
: 	ic->ic_vht_rxmcs = (IEEE80211_VHT_MCS_0_9 <<
: 	    IEEE80211_VHT_MCS_FOR_SS_SHIFT(1));
:@@ -3620,6 +3644,10 @@ iwx_phy_ctxt_task(void *arg)
: 	else
: 		sco = IEEE80211_HTOP0_SCO_SCN;
: 	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
:+	    IEEE80211_CHAN_160MHZ_ALLOWED(in->in_ni.ni_chan) &&
:+	    ieee80211_node_supports_vht_chan160(ni))
:+		vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_160;
:+	else if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
: 	    IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
: 	    ieee80211_node_supports_vht_chan80(ni))
: 		vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_80;
:@@ -5573,6 +5601,12 @@ iwx_get_vht_ctrl_pos(struct ieee80211com
: 	uint8_t pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
: 
: 	switch (primary_idx - center_idx) {
:+	case -14:
:+		pos = IWX_PHY_VHT_CTRL_POS_4_BELOW;
:+		break;
:+	case -10:
:+		pos = IWX_PHY_VHT_CTRL_POS_3_BELOW;
:+		break;
: 	case -6:
: 		pos = IWX_PHY_VHT_CTRL_POS_2_BELOW;
: 		break;
:@@ -5585,6 +5619,12 @@ iwx_get_vht_ctrl_pos(struct ieee80211com
: 	case 6:
: 		pos = IWX_PHY_VHT_CTRL_POS_2_ABOVE;
: 		break;
:+	case 10:
:+		pos = IWX_PHY_VHT_CTRL_POS_3_ABOVE;
:+		break;
:+	case 14:
:+		pos = IWX_PHY_VHT_CTRL_POS_4_ABOVE;
:+		break;
: 	default:
: 		break;
: 	}
:@@ -5616,7 +5656,10 @@ iwx_phy_ctxt_cmd_uhb_v3_v4(struct iwx_so
: 	cmd.ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
: 	    IWX_PHY_BAND_24 : IWX_PHY_BAND_5;
: 	cmd.ci.channel = htole32(ieee80211_chan2ieee(ic, chan));
:-	if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80) {
:+	if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160) {
:+		cmd.ci.ctrl_pos = iwx_get_vht_ctrl_pos(ic, chan);
:+		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE160;
:+	} else if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80) {
: 		cmd.ci.ctrl_pos = iwx_get_vht_ctrl_pos(ic, chan);
: 		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE80;
: 	} else if (chan->ic_flags & IEEE80211_CHAN_40MHZ) {
:@@ -5676,7 +5719,10 @@ iwx_phy_ctxt_cmd_v3_v4(struct iwx_softc 
: 	cmd.ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
: 	    IWX_PHY_BAND_24 : IWX_PHY_BAND_5;
: 	cmd.ci.channel = ieee80211_chan2ieee(ic, chan);
:-	if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80) {
:+	if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160) {
:+		cmd.ci.ctrl_pos = iwx_get_vht_ctrl_pos(ic, chan);
:+		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE160;
:+	} else if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80) {
: 		cmd.ci.ctrl_pos = iwx_get_vht_ctrl_pos(ic, chan);
: 		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE80;
: 	} else if (chan->ic_flags & IEEE80211_CHAN_40MHZ) {
:@@ -6731,7 +6777,11 @@ iwx_add_sta_cmd(struct iwx_softc *sc, st
: 		}
: 
: 		if (in->in_ni.ni_flags & IEEE80211_NODE_VHT) {
:-			if (IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
:+			if (IEEE80211_CHAN_160MHZ_ALLOWED(in->in_ni.ni_chan) &&
:+			    ieee80211_node_supports_vht_chan160(&in->in_ni)) {
:+				add_sta_cmd.station_flags |= htole32(
:+				    IWX_STA_FLG_FAT_EN_160MHZ);
:+			} else if (IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
: 			    ieee80211_node_supports_vht_chan80(&in->in_ni)) {
: 				add_sta_cmd.station_flags |= htole32(
: 				    IWX_STA_FLG_FAT_EN_80MHZ);
:@@ -8433,6 +8483,13 @@ iwx_rs_init_v3(struct iwx_softc *sc, str
: 		    htole16(iwx_rs_vht_rates(sc, ni, 1));
: 		cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80] =
: 		    htole16(iwx_rs_vht_rates(sc, ni, 2));
:+		if (in->in_phyctxt->vht_chan_width ==
:+		    IEEE80211_VHTOP0_CHAN_WIDTH_160) {
:+			cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_160] =
:+			    cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80];
:+			cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_160] =
:+			    cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80];
:+		}
: 	} else if (ni->ni_flags & IEEE80211_NODE_HT) {
: 		cfg_cmd.mode = IWX_TLC_MNG_MODE_HT;
: 		cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] =
:@@ -8446,6 +8503,9 @@ iwx_rs_init_v3(struct iwx_softc *sc, str
: 
: 	cfg_cmd.sta_id = IWX_STATION_ID;
: 	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
:+	    in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160)
:+		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_160MHZ;
:+	else if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
: 	    in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80)
: 		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_80MHZ;
: 	else if ((ni->ni_flags & IEEE80211_NODE_HT) &&
:@@ -8477,6 +8537,10 @@ iwx_rs_init_v3(struct iwx_softc *sc, str
: 	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
: 	    ieee80211_node_supports_vht_sgi80(ni))
: 		cfg_cmd.sgi_ch_width_supp |= (1 << IWX_TLC_MNG_CH_WIDTH_80MHZ);
:+	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
:+	    in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160 &&
:+	    ieee80211_node_supports_vht_sgi160(ni))
:+		cfg_cmd.sgi_ch_width_supp |= (1 << IWX_TLC_MNG_CH_WIDTH_160MHZ);
: 
: 	cmd_id = iwx_cmd_id(IWX_TLC_MNG_CONFIG_CMD, IWX_DATA_PATH_GROUP, 0);
: 	return iwx_send_cmd_pdu(sc, cmd_id, IWX_CMD_ASYNC, cmd_size, &cfg_cmd);
:@@ -8508,6 +8572,13 @@ iwx_rs_init_v4(struct iwx_softc *sc, str
: 		    htole16(iwx_rs_vht_rates(sc, ni, 1));
: 		cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80] =
: 		    htole16(iwx_rs_vht_rates(sc, ni, 2));
:+		if (in->in_phyctxt->vht_chan_width ==
:+		    IEEE80211_VHTOP0_CHAN_WIDTH_160) {
:+			cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_160] =
:+			    cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80];
:+			cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_160] =
:+			    cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80];
:+		}
: 	} else if (ni->ni_flags & IEEE80211_NODE_HT) {
: 		cfg_cmd.mode = IWX_TLC_MNG_MODE_HT;
: 		cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] =
:@@ -8521,6 +8592,9 @@ iwx_rs_init_v4(struct iwx_softc *sc, str
: 
: 	cfg_cmd.sta_id = IWX_STATION_ID;
: 	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
:+	    in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160)
:+		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_160MHZ;
:+	else if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
: 	    in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80)
: 		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_80MHZ;
: 	else if ((ni->ni_flags & IEEE80211_NODE_HT) &&
:@@ -8552,6 +8626,10 @@ iwx_rs_init_v4(struct iwx_softc *sc, str
: 	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
: 	    ieee80211_node_supports_vht_sgi80(ni))
: 		cfg_cmd.sgi_ch_width_supp |= (1 << IWX_TLC_MNG_CH_WIDTH_80MHZ);
:+	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
:+	    in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160 &&
:+	    ieee80211_node_supports_vht_sgi160(ni))
:+		cfg_cmd.sgi_ch_width_supp |= (1 << IWX_TLC_MNG_CH_WIDTH_160MHZ);
: 
: 	cmd_id = iwx_cmd_id(IWX_TLC_MNG_CONFIG_CMD, IWX_DATA_PATH_GROUP, 0);
: 	return iwx_send_cmd_pdu(sc, cmd_id, IWX_CMD_ASYNC, cmd_size, &cfg_cmd);
:@@ -8936,6 +9014,10 @@ iwx_run(struct iwx_softc *sc)
: 		else
: 			sco = IEEE80211_HTOP0_SCO_SCN;
: 		if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
:+		    IEEE80211_CHAN_160MHZ_ALLOWED(in->in_ni.ni_chan) &&
:+		    ieee80211_node_supports_vht_chan160(ni))
:+			vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_160;
:+		else if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
: 		    IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
: 		    ieee80211_node_supports_vht_chan80(ni))
: 			vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_80;
:@@ -12378,11 +12460,16 @@ iwx_attach(struct device *parent, struct
: 	ic->ic_ampdu_params = (IEEE80211_AMPDU_PARAM_SS_4 | 0x3 /* 64k */);
: 
: 	ic->ic_vhtcaps = IEEE80211_VHTCAP_MAX_MPDU_LENGTH_3895 |
:-	    (IEEE80211_VHTCAP_MAX_AMPDU_LEN_64K <<
:+	    (IEEE80211_VHTCAP_MAX_AMPDU_LEN_1024K <<
: 	    IEEE80211_VHTCAP_MAX_AMPDU_LEN_SHIFT) |
:-	    (IEEE80211_VHTCAP_CHAN_WIDTH_80 <<
:-	     IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT) | IEEE80211_VHTCAP_SGI80 |
:-	    IEEE80211_VHTCAP_RX_ANT_PATTERN | IEEE80211_VHTCAP_TX_ANT_PATTERN;
:+	    (IEEE80211_VHTCAP_CHAN_WIDTH_160 <<
:+	     IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT) |
:+	    IEEE80211_VHTCAP_RX_LDPC |
:+	    (1 << IEEE80211_VHTCAP_RX_STBC_SS_SHIFT) |
:+	    IEEE80211_VHTCAP_SGI80 | IEEE80211_VHTCAP_SGI160 |
:+	    IEEE80211_VHTCAP_SU_BEAMFORMEE |
:+	    (3 << IEEE80211_VHTCAP_BEAMFORMEE_STS_SHIFT) |
:+	    IEEE80211_VHTCAP_MU_BEAMFORMEE;
: 
: 	ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a;
: 	ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b;
:Index: sys/net80211/ieee80211.h
:===================================================================
:RCS file: /home/cvs/src/sys/net80211/ieee80211.h,v
:diff -u -p -r1.65 ieee80211.h
:--- sys/net80211/ieee80211.h	19 Mar 2026 16:50:32 -0000	1.65
:+++ sys/net80211/ieee80211.h	22 Mar 2026 21:57:37 -0000
:@@ -720,7 +720,8 @@ enum {
: #define IEEE80211_HTOP1_NONGF_STA	0x0004
: /* Bit 3 is reserved. */
: #define IEEE80211_HTOP1_OBSS_NONHT_STA	0x0010
:-/* Bits 5-15 are reserved. */
:+#define IEEE80211_HTOP1_CCFS2_SHIFT	5
:+#define IEEE80211_HTOP1_CCFS2_MASK	0x1fe0
: /* Bytes 4-5. */
: /* Bits 0-5 are reserved. */
: #define IEEE80211_HTOP2_DUALBEACON	0x0040
:@@ -776,6 +777,8 @@ enum {
: #define IEEE80211_VHTCAP_LINK_ADAPT_MRQ_MFB	3
: #define IEEE80211_VHTCAP_RX_ANT_PATTERN		0x10000000
: #define IEEE80211_VHTCAP_TX_ANT_PATTERN		0x20000000
:+#define IEEE80211_VHTCAP_EXT_NSS_BW_SHIFT	30
:+#define IEEE80211_VHTCAP_EXT_NSS_BW_MASK	0xc0000000
: 
: /*
:  * VHT-MCS and NSS map (see 802.11ac-2013 8.4.2.160.3, Figure 8-401bs).
:@@ -792,6 +795,7 @@ enum {
: 
: #define IEEE80211_VHT_MAX_LGI_MBIT_S_MASK	0x1fff
: #define IEEE80211_VHT_MAX_LGI_MBIT_S_SHIFT	0
:+#define IEEE80211_VHT_EXT_NSS_BW_CAPABLE	(1 << 13)
: 
: /* The highest number of spatial streams supported by VHT. */
: #define IEEE80211_VHT_NUM_SS	8
:Index: sys/net80211/ieee80211_node.c
:===================================================================
:RCS file: /home/cvs/src/sys/net80211/ieee80211_node.c,v
:diff -u -p -r1.211 ieee80211_node.c
:--- sys/net80211/ieee80211_node.c	19 Mar 2026 16:50:32 -0000	1.211
:+++ sys/net80211/ieee80211_node.c	22 Mar 2026 21:57:37 -0000
:@@ -2534,7 +2534,7 @@ ieee80211_setup_htop(struct ieee80211_no
: 	if (!ieee80211_40mhz_center_freq_valid(data[0], data[1]))
: 		ni->ni_htop0 &= ~IEEE80211_HTOP0_SCO_MASK;
: 	ni->ni_htop1 = (data[2] | (data[3] << 8));
:-	ni->ni_htop2 = (data[3] | (data[4] << 8));
:+	ni->ni_htop2 = (data[4] | (data[5] << 8));
: 
: 	/*
: 	 * According to 802.11-2012 Table 8-130 the Basic MCS set is
:@@ -2563,8 +2563,7 @@ ieee80211_setup_vhtcaps(struct ieee80211
: 	ni->ni_vht_rx_max_lgi_mbit_s = ((data[6] | (data[7] << 8)) &
: 	    IEEE80211_VHT_MAX_LGI_MBIT_S_MASK);
: 	ni->ni_vht_txmcs = (data[8] | (data[9] << 8));
:-	ni->ni_vht_tx_max_lgi_mbit_s = ((data[10] | (data[11] << 8)) &
:-	    IEEE80211_VHT_MAX_LGI_MBIT_S_MASK);
:+	ni->ni_vht_tx_max_lgi_mbit_s = (data[10] | (data[11] << 8));
: 
: 	ni->ni_flags |= IEEE80211_NODE_VHTCAP;
: }
:@@ -2601,8 +2600,8 @@ int
: ieee80211_setup_vhtop(struct ieee80211_node *ni, const uint8_t *data,
:     uint8_t len, int isprobe)
: {
:-	uint8_t sco;
:-	int have_40mhz;
:+	uint8_t sco, ccfs0, ccfs1, ccfs2, supp_chwidth, ext_nss_bw_supp;
:+	int have_40mhz, width, ccf1;
: 
: 	if (len != 5)
: 		return 0;
:@@ -2619,14 +2618,66 @@ ieee80211_setup_vhtop(struct ieee80211_n
: 	    sco == IEEE80211_HTOP0_SCO_SCB);
: 
: 	if (have_40mhz && ieee80211_80mhz_center_freq_valid(data[1])) {
:-		ni->ni_vht_chan_width = data[0];
:-		ni->ni_vht_chan_center_freq_idx0 = data[1];
:-
:-		/* Only used in non-consecutive 80-80 160MHz configs. */
:+		width = data[0];
:+		ccfs0 = data[1];
: 		if (data[2] && ieee80211_80mhz_center_freq_valid(data[2]))
:-			ni->ni_vht_chan_center_freq_idx1 = data[2];
:+			ccfs1 = data[2];
: 		else
:+			ccfs1 = 0;
:+		ccfs2 = (ni->ni_htop1 & IEEE80211_HTOP1_CCFS2_MASK) >>
:+		    IEEE80211_HTOP1_CCFS2_SHIFT;
:+		if (!ieee80211_80mhz_center_freq_valid(ccfs2))
:+			ccfs2 = 0;
:+
:+		supp_chwidth = (ni->ni_vhtcaps & IEEE80211_VHTCAP_CHAN_WIDTH_MASK) >>
:+		    IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT;
:+		ext_nss_bw_supp =
:+		    (ni->ni_vhtcaps & IEEE80211_VHTCAP_EXT_NSS_BW_MASK) >>
:+		    IEEE80211_VHTCAP_EXT_NSS_BW_SHIFT;
:+
:+		switch ((supp_chwidth << 4) | ext_nss_bw_supp) {
:+		case 0x01:
:+		case 0x02:
:+		case 0x03:
:+			ccf1 = ccfs2;
:+			break;
:+		case 0x10:
:+			ccf1 = ccfs1;
:+			break;
:+		case 0x11:
:+		case 0x12:
:+			if (ccfs1 != 0)
:+				ccf1 = ccfs1;
:+			else
:+				ccf1 = ccfs2;
:+			break;
:+		case 0x13:
:+		case 0x20:
:+		case 0x23:
:+			ccf1 = ccfs1;
:+			break;
:+		default:
:+			ccf1 = 0;
:+			break;
:+		}
:+
:+		ni->ni_vht_chan_center_freq_idx0 = ccfs0;
:+		ni->ni_vht_chan_center_freq_idx1 = ccfs1;
:+
:+		if (width == IEEE80211_VHTOP0_CHAN_WIDTH_80 && ccf1 != 0) {
:+			int diff;
:+
:+			diff = abs(ccf1 - ccfs0);
:+			if (diff == 8) {
:+				ni->ni_vht_chan_center_freq_idx0 = ccf1;
:+				ni->ni_vht_chan_center_freq_idx1 = 0;
:+				width = IEEE80211_VHTOP0_CHAN_WIDTH_160;
:+			}
:+		} else if (width == IEEE80211_VHTOP0_CHAN_WIDTH_160) {
: 			ni->ni_vht_chan_center_freq_idx1 = 0;
:+		}
:+
:+		ni->ni_vht_chan_width = width;
: 	} else {
: 		ni->ni_vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_HT;
: 		ni->ni_vht_chan_center_freq_idx0 = 0;
:Index: sys/net80211/ieee80211_node.h
:===================================================================
:RCS file: /home/cvs/src/sys/net80211/ieee80211_node.h,v
:diff -u -p -r1.99 ieee80211_node.h
:--- sys/net80211/ieee80211_node.h	19 Mar 2026 16:50:32 -0000	1.99
:+++ sys/net80211/ieee80211_node.h	22 Mar 2026 21:43:09 -0000
:@@ -607,7 +607,11 @@ ieee80211_node_supports_vht_chan160(stru
: 
: 	cap_chan_width = (ni->ni_vhtcaps & IEEE80211_VHTCAP_CHAN_WIDTH_MASK) >>
: 	    IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT;
:-	if (cap_chan_width != IEEE80211_VHTCAP_CHAN_WIDTH_160)
:+	if (cap_chan_width != IEEE80211_VHTCAP_CHAN_WIDTH_160 &&
:+	    cap_chan_width != IEEE80211_VHTCAP_CHAN_WIDTH_160_8080 &&
:+	    ((ni->ni_vhtcaps & IEEE80211_VHTCAP_EXT_NSS_BW_MASK) == 0 ||
:+	    (ni->ni_vht_tx_max_lgi_mbit_s &
:+	    IEEE80211_VHT_EXT_NSS_BW_CAPABLE) == 0))
: 		return 0;
: 
: 	op_chan_width = (ni->ni_vht_chan_width &
:
:
:-- 
:wbr, Kirill
:

-- 
Ray's Rule of Precision:
	Measure with a micrometer.  Mark with chalk.  Cut with an axe.