git: 401434a46c10 - main - iwm: add phy/mac context tasks; clean up task stop; add net80211 MAC methods

From: Adrian Chadd <adrian_at_FreeBSD.org>
Date: Fri, 16 Oct 2026 03:54:18 UTC
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=401434a46c1067dd5fea8ba5e4886efb66c47908

commit 401434a46c1067dd5fea8ba5e4886efb66c47908
Author:     Adrian Chadd <adrian@FreeBSD.org>
AuthorDate: 2026-10-16 03:53:19 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-10-16 03:53:22 +0000

    iwm: add phy/mac context tasks; clean up task stop; add net80211 MAC methods
    
    * Add phy/mac tasks, the phy task is currently a placeholder
    * Add in the MAC methods from net80211 that are called whenever
      the VAP mode needs updating
    * Mark the hardware as STOPPED during shutdown so scheduled tasks
      don't run
    
    The fun side of this work:
    
    * detaching net80211 causes the parent method to be called which leads
      to iwm_stop() being called, and at least one of the entry paths
      into iwm_stop() has IWM_LOCK held, and we can't do a taskqueue_drain()
      or taskqueue_drain_all() call with any lock held as it will sleep.
    
    * the net80211 detach path is calling at least one of the MAC methods
      during detach, and since the taskqueue is being deleted /before/
      the net80211 detach path, we have to make sure nothing is actually
      going to do taskqueue work after that!
    
    * STOPPED in FreeBSD is MOSTLY doing the same as SHUTDOWN in OpenBSD.
      It should be fine enough for this.
    
    Locally tested:
    
    * 3160/7260/8260/9260 STA mode - create, ctrl-C wpa_supplicant;  create,
      destroy; create, unload; create, destroy, unload.  The create+unload paths
      generate interesting net80211 tear down states which were causing
      issues here.
    
    Differential Revision:  https://reviews.freebsd.org/D60607
---
 sys/dev/iwm/if_iwm.c    | 169 +++++++++++++++++++++++++++++++++++++++++++++++-
 sys/dev/iwm/if_iwmvar.h |   6 ++
 2 files changed, 174 insertions(+), 1 deletion(-)

diff --git a/sys/dev/iwm/if_iwm.c b/sys/dev/iwm/if_iwm.c
index ae00187d7819..fef762024c6f 100644
--- a/sys/dev/iwm/if_iwm.c
+++ b/sys/dev/iwm/if_iwm.c
@@ -2149,6 +2149,69 @@ iwm_setup_ht_rates(struct iwm_softc *sc)
 	/* XXX TODO: MCS8..15 if >1 antenna and mimo enabled */
 }
 
+static void
+iwm_vap_update_slot(struct ieee80211vap *vap)
+{
+	struct ieee80211com *ic = vap->iv_ic;
+	struct iwm_softc *sc = ic->ic_softc;
+
+	IWM_LOCK_ASSERT_UNLOCKED(sc);
+
+	IWM_LOCK(sc);
+	if ((sc->sc_flags & IWM_FLAG_STOPPED) == 0) {
+		taskqueue_enqueue(sc->sc_tq, &sc->mac_ctxt_task);
+	}
+	IWM_UNLOCK(sc);
+}
+
+static void
+iwm_vap_update_preamble(struct ieee80211vap *vap)
+{
+	struct ieee80211com *ic = vap->iv_ic;
+	struct iwm_softc *sc = ic->ic_softc;
+
+	IWM_LOCK_ASSERT_UNLOCKED(sc);
+
+	IWM_LOCK(sc);
+	if ((sc->sc_flags & IWM_FLAG_STOPPED) == 0) {
+		taskqueue_enqueue(sc->sc_tq, &sc->mac_ctxt_task);
+	}
+	IWM_UNLOCK(sc);
+}
+
+static int
+iwm_vap_update_wme(struct ieee80211vap *vap,
+    const struct wmeParams *wme_params)
+{
+	struct ieee80211com *ic = vap->iv_ic;
+	struct iwm_softc *sc = ic->ic_softc;
+
+	IWM_LOCK_ASSERT_UNLOCKED(sc);
+
+	IWM_LOCK(sc);
+	if ((sc->sc_flags & IWM_FLAG_STOPPED) == 0) {
+		taskqueue_enqueue(sc->sc_tq, &sc->mac_ctxt_task);
+	}
+	IWM_UNLOCK(sc);
+
+	return (0);
+}
+
+static void
+iwm_vap_update_erp_protmode(struct ieee80211vap *vap)
+{
+	struct ieee80211com *ic = vap->iv_ic;
+	struct iwm_softc *sc = ic->ic_softc;
+
+	IWM_LOCK_ASSERT_UNLOCKED(sc);
+
+	IWM_LOCK(sc);
+	if ((sc->sc_flags & IWM_FLAG_STOPPED) == 0) {
+		taskqueue_enqueue(sc->sc_tq, &sc->mac_ctxt_task);
+	}
+	IWM_UNLOCK(sc);
+}
+
 static void
 iwm_set_hw_address_family_8000(struct iwm_softc *sc, struct iwm_nvm_data *data,
 	const uint16_t *mac_override, const uint16_t *nvm_hw)
@@ -5209,9 +5272,20 @@ iwm_start(struct iwm_softc *sc)
 static void
 iwm_stop(struct iwm_softc *sc)
 {
+	IWM_LOCK_ASSERT_LOCKED(sc);
 
 	sc->sc_flags &= ~IWM_FLAG_HW_INITED;
 	sc->sc_flags |= IWM_FLAG_STOPPED;
+
+	/*
+	 * Attempt to cancel; but if we can't, then don't call drain as
+	 * that can't be done with the lock held.
+	 */
+	if (sc->sc_tq != NULL) {
+		taskqueue_cancel(sc->sc_tq, &sc->mac_ctxt_task, NULL);
+		taskqueue_cancel(sc->sc_tq, &sc->phy_ctxt_task, NULL);
+	}
+
 	sc->sc_generation++;
 	iwm_led_blink_stop(sc);
 	sc->sc_tx_timer = 0;
@@ -5249,6 +5323,8 @@ iwm_parent(struct ieee80211com *ic)
 	int startall = 0;
 	int rfkill = 0;
 
+	IWM_LOCK_ASSERT_UNLOCKED(sc);
+
 	IWM_LOCK(sc);
 	if (ic->ic_nrunning > 0) {
 		if (!(sc->sc_flags & IWM_FLAG_HW_INITED)) {
@@ -5273,6 +5349,8 @@ iwm_rftoggle_task(void *arg, int npending __unused)
 	struct ieee80211com *ic = &sc->sc_ic;
 	int rfkill;
 
+	IWM_LOCK_ASSERT_UNLOCKED(sc);
+
 	IWM_LOCK(sc);
 	rfkill = iwm_check_rfkill(sc);
 	IWM_UNLOCK(sc);
@@ -5289,6 +5367,68 @@ iwm_rftoggle_task(void *arg, int npending __unused)
 	}
 }
 
+/*
+ * Deferred MAC related task updates.
+ *
+ * This is scheduled whenever something in the STA MAC
+ * config has changed.  This driver currently only supports
+ * a single STA interface, so that's the assumption made
+ * here.
+ */
+static void
+iwm_mac_ctxt_task(void *arg, int npending __unused)
+{
+	struct iwm_softc *sc = arg;
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+	const struct iwm_vap *iv;
+	int error;
+
+	IWM_LOCK_ASSERT_UNLOCKED(sc);
+
+	if (vap == NULL)
+		return;
+
+	iv = IWM_VAP(vap);
+
+	IWM_LOCK(sc);
+
+	/* Don't scheduled work if the driver is stopped */
+	if (sc->sc_flags & IWM_FLAG_STOPPED) {
+		IWM_UNLOCK(sc);
+		return;
+	}
+
+	/*
+	 * Only trigger an update if we've already programmed in a
+	 * MAC context.
+	 */
+	if (iv->is_uploaded) {
+		if ((error = iwm_mac_ctxt_changed(sc, vap)) != 0) {
+			device_printf(sc->sc_dev,
+			    "%s: failed to update MAC\n", __func__);
+		}
+	}
+	IWM_UNLOCK(sc);
+}
+
+/*
+ * Deferred PHY related task updates.
+ *
+ * This is scheduled whenever something in the STA PHY
+ * config has changed.  This driver currently only supports
+ * a single STA interface, so that's the assumption made
+ * here.
+ */
+static void
+iwm_phy_ctxt_task(void *arg, int npending __unused)
+{
+	struct iwm_softc *sc = arg;
+	/* TODO */
+
+	device_printf(sc->sc_dev, "%s: called\n", __func__);
+}
+
 /*
  * The interrupt side of things
  */
@@ -6326,6 +6466,14 @@ iwm_attach(device_t dev)
 	TASK_INIT(&sc->sc_es_task, 0, iwm_endscan_cb, sc);
 	TASK_INIT(&sc->sc_rftoggle_task, 0, iwm_rftoggle_task, sc);
 
+	/*
+	 * Note: these would be per-ctx, but right now the driver
+	 * only knows about a single VAP.  This mirrors what
+	 * OpenBSD iwm does to make porting/updating easier.
+	 */
+	TASK_INIT(&sc->mac_ctxt_task, 0, iwm_mac_ctxt_task, sc);
+	TASK_INIT(&sc->phy_ctxt_task, 0, iwm_phy_ctxt_task, sc);
+
 	sc->sc_tq = taskqueue_create("iwm_taskq", M_WAITOK,
 	    taskqueue_thread_enqueue, &sc->sc_tq);
 	error = taskqueue_start_threads(&sc->sc_tq, 1, 0, "iwm_taskq");
@@ -6711,6 +6859,11 @@ iwm_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
 	ivp->iv_newstate = vap->iv_newstate;
 	vap->iv_newstate = iwm_newstate;
 
+	vap->iv_updateslot = iwm_vap_update_slot;
+	vap->iv_erp_protmode_update = iwm_vap_update_erp_protmode;
+	vap->iv_preamble_update = iwm_vap_update_preamble;
+	vap->iv_wme_update = iwm_vap_update_wme;
+
 	ivp->id = IWM_DEFAULT_MACID;
 	ivp->color = IWM_DEFAULT_COLOR;
 
@@ -6905,15 +7058,29 @@ iwm_detach_local(struct iwm_softc *sc, int do_net80211)
 
 	IWM_LOCK_ASSERT_UNLOCKED(sc);
 
-	if (!sc->sc_attached)
+	IWM_LOCK(sc);
+	if (!sc->sc_attached) {
+		IWM_UNLOCK(sc);
 		return 0;
+	}
 	sc->sc_attached = 0;
+	sc->sc_flags |= IWM_FLAG_STOPPED;
+	IWM_UNLOCK(sc);
+
 	if (do_net80211) {
 		ieee80211_draintask(&sc->sc_ic, &sc->sc_es_task);
 	}
 	iwm_stop_device(sc);
+
+	while (taskqueue_cancel(sc->sc_tq, &sc->mac_ctxt_task, NULL) != 0)
+		taskqueue_drain(sc->sc_tq, &sc->mac_ctxt_task);
+	while (taskqueue_cancel(sc->sc_tq, &sc->phy_ctxt_task, NULL) != 0)
+		taskqueue_drain(sc->sc_tq, &sc->phy_ctxt_task);
+
 	taskqueue_drain_all(sc->sc_tq);
 	taskqueue_free(sc->sc_tq);
+	sc->sc_tq = NULL;
+
 	if (do_net80211) {
 		IWM_LOCK(sc);
 		iwm_xmit_queue_drain(sc);
diff --git a/sys/dev/iwm/if_iwmvar.h b/sys/dev/iwm/if_iwmvar.h
index 0d61aee37d8e..4497bd91c8a1 100644
--- a/sys/dev/iwm/if_iwmvar.h
+++ b/sys/dev/iwm/if_iwmvar.h
@@ -439,6 +439,12 @@ struct iwm_softc {
 
 	struct task		init_task;
 
+	/* Task for ERP/HT prot/slot-time/EDCA updates. */
+	struct task		mac_ctxt_task;
+
+	/* Task for HT 20/40 MHz channel width updates. */
+	struct task		phy_ctxt_task;
+
 	struct resource		*sc_irq;
 	struct resource		*sc_mem;
 	bus_space_tag_t		sc_st;