From: Kirill A. Korinsky Subject: Re: sys/iwx: support of 160Mhz window at 5Ghz To: tech@openbsd.org Date: Sun, 29 Mar 2026 15:15:06 +0200 On Wed, 25 Mar 2026 11:50:04 +0100, Stefan Sperling wrote: > > On Tue, Mar 24, 2026 at 10:03:20PM +0100, Kirill A. Korinsky wrote: > > I think it is strong proof that 160Mhz actually works as expected > > This is nice overall. I am happy the Tx side is working well now. > > Can we please try to reduce the amount of unrelated changes in the diff, > even if they happen to increase performance in your tests? > > Specifically, enabling support for LDPC, STBC, beam-forming, and announcing > support for larger aggregates in VHT-caps are all unrelated changes. > > I don't know exactly what the consequences of enabling these features are > and unfortunately I lack the time to dig into them now. But I'm quite sure > more changes will be required than simply flipping feature bits in VHT caps. > These features will trigger new code paths in firmware which the driver > may not yet be prepared to handle, potentially resulting in firmware > crashes or other instabilities in some setups. > For STBC we will likely need to configure related parameters in the rate > selection firmware command, for example. Beamforming might require us > to handle sounding frames perhaps. Not sure about the others, they will > also require some consideration. > > I would prefer to enable all of these features one-by-one in isolation > to make it easier to review the changes and spot any potential regressions. > Yes, moving by small steps is the right way. Here updated diff which includes only 160MHz, I also spoted one missed point which related to last non-MIMO fix. With only this diff my iperf test shows with -P 16: [SUM] 0.00-30.05 sec 1.76 GBytes 504 Mbits/sec 268540 sender [SUM] 0.00-30.01 sec 1.72 GBytes 493 Mbits/sec receiver and unifi confirms that laptop is connected with 160Mhz window and RX/Tx Rate 1.3 / 1.2 Gbps Ok? Index: sys/dev/pci/if_iwx.c =================================================================== RCS file: /home/cvs/src/sys/dev/pci/if_iwx.c,v diff -u -p -r1.225 if_iwx.c --- sys/dev/pci/if_iwx.c 26 Mar 2026 15:39:04 -0000 1.225 +++ sys/dev/pci/if_iwx.c 29 Mar 2026 13:02:54 -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; } } } @@ -3620,6 +3624,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 +5581,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 +5599,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 +5636,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 +5699,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 +6757,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); @@ -8431,9 +8461,19 @@ iwx_rs_init_v3(struct iwx_softc *sc, str cfg_cmd.mode = IWX_TLC_MNG_MODE_VHT; cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] = htole16(iwx_rs_vht_rates(sc, ni, 1)); + 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]; + } if (iwx_mimo_enabled(sc)) { 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_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; @@ -8450,6 +8490,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) && @@ -8481,6 +8524,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); @@ -8510,9 +8557,19 @@ iwx_rs_init_v4(struct iwx_softc *sc, str cfg_cmd.mode = IWX_TLC_MNG_MODE_VHT; cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] = htole16(iwx_rs_vht_rates(sc, ni, 1)); + 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]; + } if (iwx_mimo_enabled(sc)) { 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_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; @@ -8529,6 +8586,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) && @@ -8560,6 +8620,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); @@ -8944,6 +9008,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; @@ -12404,8 +12472,9 @@ iwx_attach(struct device *parent, struct ic->ic_vhtcaps = IEEE80211_VHTCAP_MAX_MPDU_LENGTH_3895 | (IEEE80211_VHTCAP_MAX_AMPDU_LEN_64K << IEEE80211_VHTCAP_MAX_AMPDU_LEN_SHIFT) | - (IEEE80211_VHTCAP_CHAN_WIDTH_80 << - IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT) | IEEE80211_VHTCAP_SGI80 | + (IEEE80211_VHTCAP_CHAN_WIDTH_160 << + IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT) | + IEEE80211_VHTCAP_SGI80 | IEEE80211_VHTCAP_SGI160 | IEEE80211_VHTCAP_RX_ANT_PATTERN | IEEE80211_VHTCAP_TX_ANT_PATTERN; ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a; 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 29 Mar 2026 11:17:09 -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.212 ieee80211_node.c --- sys/net80211/ieee80211_node.c 26 Mar 2026 12:15:01 -0000 1.212 +++ sys/net80211/ieee80211_node.c 29 Mar 2026 11:17:09 -0000 @@ -2536,7 +2536,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 @@ -2565,8 +2565,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; } @@ -2603,8 +2602,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; @@ -2621,14 +2620,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.100 ieee80211_node.h --- sys/net80211/ieee80211_node.h 26 Mar 2026 12:15:01 -0000 1.100 +++ sys/net80211/ieee80211_node.h 29 Mar 2026 11:17:09 -0000 @@ -609,7 +609,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