git: 103a4f05c77f - main - iwm: update phy context routines; add HT channel width parameter

From: Adrian Chadd <adrian_at_FreeBSD.org>
Date: Thu, 15 Oct 2026 02:19:06 UTC
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=103a4f05c77feacacd4e8191b4bfc92a71d1673d

commit 103a4f05c77feacacd4e8191b4bfc92a71d1673d
Author:     Adrian Chadd <adrian@FreeBSD.org>
AuthorDate: 2026-10-15 01:49:33 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-10-15 01:49:35 +0000

    iwm: update phy context routines; add HT channel width parameter
    
    Update the phy context routines to match what the openbsd snapshot
    is doing.  This involves some HT channel width stuff which although
    I am not doing HT right now, it was juts easier to sync these
    entirely versus just a partial sync.
    
    * Update the phy context routines to match what openbsd is doing
    * Although the openbsd driver has an iwm_phy_ctxt_update() routine
      instead of add and modify, I'm leaving it as-is for now.
      A bunch of refactoring is going to need to be done to properly
      use an iwm_phy_ctxt_update() call and I didn't want to tackle
      that in here.
    * Add a UHB firmware capability phy context call; I can't test it
      but it's mostly copy/paste.
    * Add in a util to turn ieee80211_channel into the 'sco' variable
      (the HT low/hi parameter) since it's done in openbsd in a
      slightly different way.
    
    Locally tested:
    
    * 3160, STA mode
    * 7260, STA mode
    * 8260, STA mode
    * 9260, STA mode
    
    Obtained from: OpenBSD a6e25c727a2214219715b51ba6af2221d7e0d674
    
    Differential Revision:  https://reviews.freebsd.org/D60082
---
 sys/dev/iwm/if_iwm.c          |   8 ++-
 sys/dev/iwm/if_iwm_phy_ctxt.c | 120 +++++++++++++++++++++++++++++++++++-------
 sys/dev/iwm/if_iwm_phy_ctxt.h |   5 ++
 sys/dev/iwm/if_iwm_util.h     |  18 +++++++
 sys/dev/iwm/if_iwmvar.h       |   1 +
 5 files changed, 131 insertions(+), 21 deletions(-)

diff --git a/sys/dev/iwm/if_iwm.c b/sys/dev/iwm/if_iwm.c
index dd154e44aa8f..e67aad2af7e4 100644
--- a/sys/dev/iwm/if_iwm.c
+++ b/sys/dev/iwm/if_iwm.c
@@ -5042,8 +5042,11 @@ iwm_init_hw(struct iwm_softc *sc)
 		 * going to be overwritten in the other flows.
 		 * For now use the first channel we have.
 		 */
-		if ((error = iwm_phy_ctxt_add(sc,
-		    &sc->sc_phyctxt[i], &ic->ic_channels[1], 1, 1)) != 0)
+		sc->sc_phyctxt[i].id = i;
+		sc->sc_phyctxt[i].channel = &ic->ic_channels[1];
+		error = iwm_phy_ctxt_cmd(sc, &sc->sc_phyctxt[i], 1, 1,
+		    IWM_FW_CTXT_ACTION_ADD, 0, IEEE80211_HTINFO_2NDCHAN_NONE);
+		if (error)
 			goto error;
 	}
 
@@ -6482,6 +6485,7 @@ iwm_attach(device_t dev)
 	ic->ic_flags_ext |= IEEE80211_FEXT_SEQNO_OFFLOAD;
 	for (i = 0; i < nitems(sc->sc_phyctxt); i++) {
 		sc->sc_phyctxt[i].id = i;
+		sc->sc_phyctxt[i].sco = IEEE80211_HTINFO_2NDCHAN_NONE;
 		sc->sc_phyctxt[i].color = 0;
 		sc->sc_phyctxt[i].ref = 0;
 		sc->sc_phyctxt[i].channel = NULL;
diff --git a/sys/dev/iwm/if_iwm_phy_ctxt.c b/sys/dev/iwm/if_iwm_phy_ctxt.c
index 7ca86b31267f..7d0f115e3d07 100644
--- a/sys/dev/iwm/if_iwm_phy_ctxt.c
+++ b/sys/dev/iwm/if_iwm_phy_ctxt.c
@@ -188,7 +188,7 @@ iwm_phy_ctxt_cmd_hdr(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt,
 static void
 iwm_phy_ctxt_cmd_data(struct iwm_softc *sc,
 	struct iwm_phy_context_cmd *cmd, struct ieee80211_channel *chan,
-	uint8_t chains_static, uint8_t chains_dynamic)
+	uint8_t chains_static, uint8_t chains_dynamic, uint8_t sco)
 {
 	struct ieee80211com *ic = &sc->sc_ic;
 	uint8_t active_cnt, idle_cnt;
@@ -209,11 +209,23 @@ iwm_phy_ctxt_cmd_data(struct iwm_softc *sc,
 	    IWM_PHY_BAND_24 : IWM_PHY_BAND_5;
 
 	cmd->ci.channel = ieee80211_chan2ieee(ic, chan);
-
-	/* TODO: HT40 */
-	/* TODO: VHT40/VHT80 */
-	cmd->ci.width = IWM_PHY_VHT_CHANNEL_MODE20;
-	cmd->ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+	if (IEEE80211_IS_CHAN_HT40(chan)) {
+		if (sco == IEEE80211_HTINFO_2NDCHAN_ABOVE) {
+			/* secondary chan above -> control chan below */
+			cmd->ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+			cmd->ci.width = IWM_PHY_VHT_CHANNEL_MODE40;
+		} else if (sco == IEEE80211_HTINFO_2NDCHAN_BELOW) {
+			/* secondary chan below -> control chan above */
+			cmd->ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_ABOVE;
+			cmd->ci.width = IWM_PHY_VHT_CHANNEL_MODE40;
+		} else {
+			cmd->ci.width = IWM_PHY_VHT_CHANNEL_MODE20;
+			cmd->ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+		}
+	} else {
+		cmd->ci.width = IWM_PHY_VHT_CHANNEL_MODE20;
+		cmd->ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+	}
 
 	/* Set rx the chains */
 	idle_cnt = chains_static;
@@ -240,17 +252,60 @@ iwm_phy_ctxt_cmd_data(struct iwm_softc *sc,
 	cmd->txchain_info = htole32(iwm_get_valid_tx_ant(sc));
 }
 
-/*
- * Send a command
- * only if something in the configuration changed: in case that this is the
- * first time that the phy configuration is applied or in case that the phy
- * configuration changed from the previous apply.
- */
 static int
-iwm_phy_ctxt_apply(struct iwm_softc *sc,
+iwm_phy_ctxt_cmd_uhb(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt,
+    uint8_t chains_static, uint8_t chains_dynamic, uint32_t action,
+    uint32_t apply_time, uint8_t sco)
+{
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct iwm_phy_context_cmd_uhb cmd;
+	uint8_t active_cnt, idle_cnt;
+	struct ieee80211_channel *chan = ctxt->channel;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.id_and_color = htole32(IWM_FW_CMD_ID_AND_COLOR(ctxt->id,
+	    ctxt->color));
+	cmd.action = htole32(action);
+	cmd.apply_time = htole32(apply_time);
+
+	cmd.ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
+	    IWM_PHY_BAND_24 : IWM_PHY_BAND_5;
+	cmd.ci.channel = htole32(ieee80211_chan2ieee(ic, chan));
+	if (IEEE80211_IS_CHAN_HT40(chan)) {
+		if (sco == IEEE80211_HTINFO_2NDCHAN_ABOVE) {
+			/* secondary chan above -> control chan below */
+			cmd.ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+			cmd.ci.width = IWM_PHY_VHT_CHANNEL_MODE40;
+		} else if (sco == IEEE80211_HTINFO_2NDCHAN_BELOW) {
+			/* secondary chan below -> control chan above */
+			cmd.ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_ABOVE;
+			cmd.ci.width = IWM_PHY_VHT_CHANNEL_MODE40;
+		} else {
+			cmd.ci.width = IWM_PHY_VHT_CHANNEL_MODE20;
+			cmd.ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+		}
+	} else {
+		cmd.ci.width = IWM_PHY_VHT_CHANNEL_MODE20;
+		cmd.ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+	}
+
+	idle_cnt = chains_static;
+	active_cnt = chains_dynamic;
+	cmd.rxchain_info = htole32(iwm_get_valid_rx_ant(sc) <<
+					IWM_PHY_RX_CHAIN_VALID_POS);
+	cmd.rxchain_info |= htole32(idle_cnt << IWM_PHY_RX_CHAIN_CNT_POS);
+	cmd.rxchain_info |= htole32(active_cnt <<
+	    IWM_PHY_RX_CHAIN_MIMO_CNT_POS);
+	cmd.txchain_info = htole32(iwm_get_valid_tx_ant(sc));
+
+	return iwm_send_cmd_pdu(sc, IWM_PHY_CONTEXT_CMD, 0, sizeof(cmd), &cmd);
+}
+
+int
+iwm_phy_ctxt_cmd(struct iwm_softc *sc,
 	struct iwm_phy_ctxt *ctxt,
 	uint8_t chains_static, uint8_t chains_dynamic,
-	uint32_t action, uint32_t apply_time)
+	uint32_t action, uint32_t apply_time, uint8_t sco)
 {
 	struct iwm_phy_context_cmd cmd;
 	int ret;
@@ -260,12 +315,23 @@ iwm_phy_ctxt_apply(struct iwm_softc *sc,
 	    __func__,
 	    ctxt->channel);
 
+	/*
+	 * Intel increased the size of the fw_channel_info struct and neglected
+	 * to bump the phy_context_cmd struct, which contains an fw_channel_info
+	 * member in the middle.
+	 * To keep things simple we use a separate function to handle the larger
+	 * variant of the phy context command.
+	 */
+	if (iwm_fw_has_capa(sc, IWM_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS))
+		return iwm_phy_ctxt_cmd_uhb(sc, ctxt, chains_static,
+		    chains_dynamic, action, apply_time, sco);
+
 	/* Set the command header fields */
 	iwm_phy_ctxt_cmd_hdr(sc, ctxt, &cmd, action, apply_time);
 
 	/* Set the command data */
 	iwm_phy_ctxt_cmd_data(sc, &cmd, ctxt->channel,
-	    chains_static, chains_dynamic);
+	    chains_static, chains_dynamic, sco);
 
 	ret = iwm_send_cmd_pdu(sc, IWM_PHY_CONTEXT_CMD, IWM_CMD_SYNC,
 	    sizeof(struct iwm_phy_context_cmd), &cmd);
@@ -285,14 +351,22 @@ iwm_phy_ctxt_add(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt,
 	uint8_t chains_static, uint8_t chains_dynamic)
 {
 	ctxt->channel = chan;
+	int sco, ret;
+
+	sco = iwm_get_phy_sco(sc, chan);
 
 	IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_CMD,
 	    "%s: called; channel=%d\n",
 	    __func__,
 	    ieee80211_chan2ieee(&sc->sc_ic, chan));
 
-	return iwm_phy_ctxt_apply(sc, ctxt,
-	    chains_static, chains_dynamic, IWM_FW_CTXT_ACTION_ADD, 0);
+	ret = iwm_phy_ctxt_cmd(sc, ctxt,
+	    chains_static, chains_dynamic, IWM_FW_CTXT_ACTION_ADD, 0, sco);
+	if (ret != 0)
+		return (ret);
+
+	ctxt->sco = sco;
+	return (0);
 }
 
 /*
@@ -306,14 +380,22 @@ iwm_phy_ctxt_changed(struct iwm_softc *sc,
 	uint8_t chains_static, uint8_t chains_dynamic)
 {
 	ctxt->channel = chan;
+	int sco, ret;
+
+	sco = iwm_get_phy_sco(sc, chan);
 
 	IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_CMD,
 	    "%s: called; channel=%d\n",
 	    __func__,
 	    ieee80211_chan2ieee(&sc->sc_ic, chan));
 
-	return iwm_phy_ctxt_apply(sc, ctxt,
-	    chains_static, chains_dynamic, IWM_FW_CTXT_ACTION_MODIFY, 0);
+	ret = iwm_phy_ctxt_cmd(sc, ctxt,
+	    chains_static, chains_dynamic, IWM_FW_CTXT_ACTION_MODIFY, 0, sco);
+	if (ret != 0)
+		return (ret);
+
+	ctxt->sco = sco;
+	return (0);
 }
 
 /*
diff --git a/sys/dev/iwm/if_iwm_phy_ctxt.h b/sys/dev/iwm/if_iwm_phy_ctxt.h
index dcf07d1620a8..d9547c44a4e1 100644
--- a/sys/dev/iwm/if_iwm_phy_ctxt.h
+++ b/sys/dev/iwm/if_iwm_phy_ctxt.h
@@ -113,4 +113,9 @@ extern	int iwm_phy_ctxt_changed(struct iwm_softc *sc,
 	    struct iwm_phy_ctxt *ctxt, struct ieee80211_channel *chan,
 	    uint8_t chains_static, uint8_t chains_dynamic);
 
+extern	int iwm_phy_ctxt_cmd(struct iwm_softc *sc,
+	    struct iwm_phy_ctxt *ctxt,
+	    uint8_t chains_static, uint8_t chains_dynamic,
+	    uint32_t action, uint32_t apply_time, uint8_t sco);
+
 #endif	/* __IF_IWM_PHY_CTXT_H__ */
diff --git a/sys/dev/iwm/if_iwm_util.h b/sys/dev/iwm/if_iwm_util.h
index 4154efad7992..3512b3bcf8d3 100644
--- a/sys/dev/iwm/if_iwm_util.h
+++ b/sys/dev/iwm/if_iwm_util.h
@@ -159,4 +159,22 @@ iwm_get_phy_config(struct iwm_softc *sc)
 	return sc->sc_fw.phy_config & phy_config;
 }
 
+/**
+ * @brief Return the HT above/below field for the given channel width
+ *
+ * @param sc driver softc
+ * @param c channel
+ * @return IEEE80211_HTINFO_2NDCHAN_* value
+ */
+static inline uint8_t
+iwm_get_phy_sco(struct iwm_softc *sc, const struct ieee80211_channel *c)
+{
+	if (IEEE80211_IS_CHAN_HT40U(c))
+		return (IEEE80211_HTINFO_2NDCHAN_ABOVE);
+	else if (IEEE80211_IS_CHAN_HT40D(c))
+		return (IEEE80211_HTINFO_2NDCHAN_BELOW);
+	else
+		return (IEEE80211_HTINFO_2NDCHAN_NONE);
+}
+
 #endif	/* __IF_IWM_UTIL_H__ */
diff --git a/sys/dev/iwm/if_iwmvar.h b/sys/dev/iwm/if_iwmvar.h
index 8f2760f114f3..55a1ec427482 100644
--- a/sys/dev/iwm/if_iwmvar.h
+++ b/sys/dev/iwm/if_iwmvar.h
@@ -349,6 +349,7 @@ struct iwm_phy_ctxt {
 	uint16_t color;
 	uint32_t ref;
 	struct ieee80211_channel *channel;
+	uint8_t sco; /* 40MHz secondary channel offset */
 };
 
 struct iwm_bf_data {