Re: qwx use-after-free fix

Mark Kettenis <[email protected]> Thu, 16 Jul 2026 13:58:41 +0200
Newsgroups gmane.os.openbsd.tech
Message-ID <[email protected]>
> Date: Tue, 14 Jul 2026 18:24:24 +0200
> From: Stefan Sperling <[email protected]>
> 
> Memory allocation failures in qwx_vif_alloc() can lead to crashes.
> 
> The reason is that the driver will then use a stale vif pointer from
> sc->vif_list from previous down/up cycles. The vif structure contains
> DMA maps used for sending management frames.
> 
> Rather than trying to fix our broken management of the vif list,
> the patch at the bottom removes the list in favour of a single vif
> structure, allocates DMA maps at attach time, and keeps them allocated
> across down/up cycles. We keep a list of 8 maps, each of which can map
> a single frame, so this shouldn't be wasteful.
> 
> Here is a reproducer diff.
> if you want to reproduce my findings, boot a kernel with it, then
> suspend/resume once, then try to bring the interface up after resume.
> Since this code uses BUS_DMA_NOWAIT the crash triggers by chance on
> a stock kernel if this memory allocation path fails.

Nice.  And yes, while working on my "waitok" diff I ran into this bit
of code and couldn't work out the lifetime of the vif structures.

Lightly tested, but suspend/resume and hibernate still work on my
amd64 laptop.

ok kettenis@OB

> CRASH: --- sys/dev/ic/qwx.c
> CRASH: +++ sys/dev/ic/qwx.c
> CRASH: @@ -116,6 +116,8 @@ hweight8(uint8_t x)
> CRASH:  
> CRASH:  /* #define QWX_DEBUG */
> CRASH:  
> CRASH: +static int qwx_resuming;
> CRASH: +
> CRASH:  #include <dev/ic/qwxreg.h>
> CRASH:  #include <dev/ic/qwxvar.h>
> CRASH:  
> CRASH: @@ -24022,8 +24024,13 @@ qwx_vif_alloc(struct qwx_softc *sc)
> CRASH:  	for (i = 0; i < nitems(txmgmt->data); i++) {
> CRASH:  		struct qwx_tx_data *tx_data = &txmgmt->data[i];
> CRASH:  
> CRASH: -		ret = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
> CRASH: -		    BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &tx_data->map);
> CRASH: +		if (qwx_resuming && i > 6) {
> CRASH: +			ret = ENOMEM;
> CRASH: +			qwx_resuming = 0;
> CRASH: +		} else {
> CRASH: +			ret = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
> CRASH: +			    BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &tx_data->map);
> CRASH: +		}
> CRASH:  		if (ret) {
> CRASH:  			qwx_vif_free(sc, arvif);
> CRASH:  			return NULL;
> CRASH: @@ -27489,6 +27496,7 @@ qwx_activate(struct device *self, int act)
> CRASH:  				    sc->sc_dev.dv_xname);
> CRASH:  		}
> CRASH:  		rw_exit(&sc->ioctl_rwl);
> CRASH: +		qwx_resuming = 1;
> CRASH:  		break;
> CRASH:  	}
> CRASH:  
> 
> 
> Fix:
>  
> Replace virtual interface list with a single interface structure
>  
> Should fix use of stale pointers on the vif list which caused crashes.
> We do not support multiple interfaces per driver in our net80211 stack,
> so this list inherited from Linux was pointless for us anyway.
> 
> ok?
>  
> M  sys/dev/ic/qwx.c     |  59+  138-
> M  sys/dev/ic/qwxvar.h  |   1+    6-
> 
> 2 files changed, 60 insertions(+), 144 deletions(-)
> 
> commit - 03332418d3c68eaa3a6cced9f13621a1dc17c1f9
> commit + 8b38c9a0ec77696975740fa34e14b8f85f513bbf
> blob - 62d805dffc20300433d2d806d92aa06b3801b63d
> blob + 55106d1812e2c7b3a521b044d36f76c7fc232b84
> --- sys/dev/ic/qwx.c
> +++ sys/dev/ic/qwx.c
> @@ -168,7 +168,6 @@ int qwx_wmi_vdev_install_key(struct qwx_softc *,
>  int qwx_dp_peer_rx_pn_replay_config(struct qwx_softc *, struct qwx_vif *,
>      struct ieee80211_node *, struct ieee80211_key *, int);
>  void qwx_setkey_clear(struct qwx_softc *);
> -void qwx_vif_free_all(struct qwx_softc *);
>  void qwx_dp_stop_shadow_timers(struct qwx_softc *);
>  void qwx_ce_stop_shadow_timers(struct qwx_softc *);
>  int qwx_wmi_vdev_set_param_cmd(struct qwx_softc *, uint32_t, uint8_t,
> @@ -420,6 +419,24 @@ qwx_del_task_all(struct qwx_softc *sc)
>  }
>  
>  void
> +qwx_vif_purge(struct qwx_softc *sc)
> +{
> +	struct qwx_vif *arvif = &sc->sc_vif;
> +	struct qwx_txmgmt_queue *txmgmt;
> +	int i;
> +
> +	txmgmt = &arvif->txmgmt;
> +	for (i = 0; i < nitems(txmgmt->data); i++) {
> +		struct qwx_tx_data *tx_data = &txmgmt->data[i];
> +
> +		if (tx_data->m) {
> +			m_freem(tx_data->m);
> +			tx_data->m = NULL;
> +		}
> +	}
> +}
> +
> +void
>  qwx_stop(struct ifnet *ifp)
>  {
>  	struct qwx_softc *sc = ifp->if_softc;
> @@ -497,7 +514,7 @@ qwx_stop(struct ifnet *ifp)
>  	/* free some DMA allocations and power off hardware */
>  	qwx_core_deinit(sc);
>  
> -	qwx_vif_free_all(sc);
> +	qwx_vif_purge(sc);
>  
>  	splx(s);
>  }
> @@ -574,7 +591,7 @@ qwx_tx(struct qwx_softc *sc, struct mbuf *m, struct ie
>  {
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct ieee80211_frame *wh;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	uint8_t frame_type;
>  
> @@ -884,7 +901,7 @@ qwx_add_sta_key(struct qwx_softc *sc, struct ieee80211
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct qwx_node *nq = (struct qwx_node *)ni;
>  	struct ath11k_peer *peer;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	int ret = 0;
>  	uint32_t flags = 0;
> @@ -949,7 +966,7 @@ qwx_del_sta_key(struct qwx_softc *sc, struct ieee80211
>      struct ieee80211_key *k)
>  {
>  	struct qwx_node *nq = (struct qwx_node *)ni;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	int ret = 0;
>  
>  	ret = qwx_wmi_install_key_cmd(sc, arvif, ni->ni_macaddr, k, 0, 1);
> @@ -1008,7 +1025,7 @@ qwx_setkey_task(void *arg)
>  void
>  qwx_clear_hwkeys(struct qwx_softc *sc, struct ath11k_peer *peer)
>  {
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	struct wmi_vdev_install_key_arg arg =  {
>  		.vdev_id = arvif->vdev_id,
> @@ -13266,7 +13283,6 @@ void
>  qwx_scan_event(struct qwx_softc *sc, struct mbuf *m)
>  {
>  	struct wmi_scan_event scan_ev = { 0 };
> -	struct qwx_vif *arvif;
>  
>  	if (qwx_pull_scan_ev(sc, m, &scan_ev) != 0) {
>  		printf("%s: failed to extract scan event",
> @@ -13276,20 +13292,7 @@ qwx_scan_event(struct qwx_softc *sc, struct mbuf *m)
>  #ifdef notyet
>  	rcu_read_lock();
>  #endif
> -	TAILQ_FOREACH(arvif, &sc->vif_list, entry) {
> -		if (arvif->vdev_id == scan_ev.vdev_id)
> -			break;
> -	}
> -
> -	if (!arvif) {
> -		printf("%s: received scan event for unknown vdev\n",
> -		    sc->sc_dev.dv_xname);
>  #if 0
> -		rcu_read_unlock();
> -#endif
> -		return;
> -	}
> -#if 0
>  	spin_lock_bh(&ar->data_lock);
>  #endif
>  	DNPRINTF(QWX_D_WMI,
> @@ -13375,7 +13378,6 @@ qwx_pull_chan_info_ev(struct qwx_softc *sc, uint8_t *e
>  void
>  qwx_chan_info_event(struct qwx_softc *sc, struct mbuf *m)
>  {
> -	struct qwx_vif *arvif;
>  	struct wmi_chan_info_event ch_info_ev = {0};
>  	struct qwx_survey_info *survey;
>  	int idx;
> @@ -13403,20 +13405,6 @@ qwx_chan_info_event(struct qwx_softc *sc, struct mbuf 
>  	}
>  #ifdef notyet
>  	rcu_read_lock();
> -#endif
> -	TAILQ_FOREACH(arvif, &sc->vif_list, entry) {
> -		if (arvif->vdev_id == ch_info_ev.vdev_id)
> -			break;
> -	}
> -	if (!arvif) {
> -		printf("%s: invalid vdev id in chan info ev %d\n",
> -		   sc->sc_dev.dv_xname, ch_info_ev.vdev_id);
> -#ifdef notyet
> -		rcu_read_unlock();
> -#endif
> -		return;
> -	}
> -#ifdef notyet
>  	spin_lock_bh(&ar->data_lock);
>  #endif
>  	switch (sc->scan.state) {
> @@ -13705,7 +13693,7 @@ qwx_wmi_process_mgmt_tx_comp(struct qwx_softc *sc,
>      struct wmi_mgmt_tx_compl_event *tx_compl_param)
>  {
>  	struct ieee80211com *ic = &sc->sc_ic;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct ifnet *ifp = &ic->ic_if;
>  	struct qwx_tx_data *tx_data;
>  
> @@ -13891,7 +13879,6 @@ void
>  qwx_vdev_install_key_compl_event(struct qwx_softc *sc, struct mbuf *m)
>  {
>  	struct wmi_vdev_install_key_complete_arg install_key_compl = { 0 };
> -	struct qwx_vif *arvif;
>  
>  	if (qwx_pull_vdev_install_key_compl_ev(sc, m,
>  	    &install_key_compl) != 0) {
> @@ -13906,16 +13893,6 @@ qwx_vdev_install_key_compl_event(struct qwx_softc *sc,
>  	    ether_sprintf((u_char *)install_key_compl.macaddr),
>  	    install_key_compl.status);
>  
> -	TAILQ_FOREACH(arvif, &sc->vif_list, entry) {
> -		if (arvif->vdev_id == install_key_compl.vdev_id)
> -			break;
> -	}
> -	if (!arvif) {
> -		printf("%s: invalid vdev id in install key compl ev %d\n",
> -		    sc->sc_dev.dv_xname, install_key_compl.vdev_id);
> -		return;
> -	}
> -
>  	sc->install_key_status = 0;
>  
>  	if (install_key_compl.status !=
> @@ -23475,7 +23452,7 @@ qwx_mac_config_mon_status_default(struct qwx_softc *sc
>  int
>  qwx_mac_txpower_recalc(struct qwx_softc *sc, struct qwx_pdev *pdev)
>  {
> -	struct qwx_vif *arvif;
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	int ret, txpower = -1;
>  	uint32_t param;
>  	uint32_t min_tx_power = sc->target_caps.hw_min_tx_power;
> @@ -23483,15 +23460,10 @@ qwx_mac_txpower_recalc(struct qwx_softc *sc, struct qw
>  #ifdef notyet
>  	lockdep_assert_held(&ar->conf_mutex);
>  #endif
> -	TAILQ_FOREACH(arvif, &sc->vif_list, entry) {
> -		if (arvif->txpower <= 0)
> -			continue;
> +	if (arvif->txpower <= 0)
> +		return 0;
>  
> -		if (txpower == -1)
> -			txpower = arvif->txpower;
> -		else
> -			txpower = MIN(txpower, arvif->txpower);
> -	}
> +	txpower = MIN(txpower, arvif->txpower);
>  
>  	if (txpower == -1)
>  		return 0;
> @@ -23664,10 +23636,9 @@ qwx_mac_setup_vdev_params_mbssid(struct qwx_vif *arvif
>  }
>  
>  int
> -qwx_mac_setup_vdev_create_params(struct qwx_vif *arvif, struct qwx_pdev *pdev,
> -    struct vdev_create_params *params)
> +qwx_mac_setup_vdev_create_params(struct qwx_softc *sc, struct qwx_vif *arvif,
> +    struct qwx_pdev *pdev, struct vdev_create_params *params)
>  {
> -	struct qwx_softc *sc = arvif->sc;
>  	int ret;
>  
>  	params->if_id = arvif->vdev_id;
> @@ -23969,70 +23940,45 @@ qwx_mac_vdev_start(struct qwx_softc *sc, struct qwx_vi
>  }
>  
>  void
> -qwx_vif_free(struct qwx_softc *sc, struct qwx_vif *arvif)
> +qwx_vif_free(struct qwx_softc *sc)
>  {
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct qwx_txmgmt_queue *txmgmt;
>  	int i;
>  
> -	if (arvif == NULL)
> -		return;
> -
>  	txmgmt = &arvif->txmgmt;
>  	for (i = 0; i < nitems(txmgmt->data); i++) {
>  		struct qwx_tx_data *tx_data = &txmgmt->data[i];
>  
> -		if (tx_data->m) {
> -			m_freem(tx_data->m);
> -			tx_data->m = NULL;
> -		}
>  		if (tx_data->map) {
>  			bus_dmamap_destroy(sc->sc_dmat, tx_data->map);
>  			tx_data->map = NULL;
>  		}
>  	}
> -
> -	free(arvif, M_DEVBUF, sizeof(*arvif));
>  }
>  
> -void
> -qwx_vif_free_all(struct qwx_softc *sc)
> -{
> -	struct qwx_vif *arvif;
> -
> -	while (!TAILQ_EMPTY(&sc->vif_list)) {
> -		arvif = TAILQ_FIRST(&sc->vif_list);
> -		TAILQ_REMOVE(&sc->vif_list, arvif, entry);
> -		qwx_vif_free(sc, arvif);
> -	}
> -}
> -
> -struct qwx_vif *
> +int 
>  qwx_vif_alloc(struct qwx_softc *sc)
>  {
> -	struct qwx_vif *arvif;
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct qwx_txmgmt_queue *txmgmt; 
>  	int i, ret = 0;
>  	const bus_size_t size = IEEE80211_MAX_LEN;
>  
> -	arvif = malloc(sizeof(*arvif), M_DEVBUF, M_NOWAIT | M_ZERO);
> -	if (arvif == NULL)
> -		return NULL;
> -
> +	/* Allocate DMA maps used when sending management frames. */
>  	txmgmt = &arvif->txmgmt;
>  	for (i = 0; i < nitems(txmgmt->data); i++) {
>  		struct qwx_tx_data *tx_data = &txmgmt->data[i];
>  
>  		ret = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
> -		    BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &tx_data->map);
> +		    BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &tx_data->map);
>  		if (ret) {
> -			qwx_vif_free(sc, arvif);
> -			return NULL;
> +			qwx_vif_free(sc);
> +			return ENOMEM;
>  		}
>  	}
>  
> -	arvif->sc = sc;
> -
> -	return arvif;
> +	return 0;
>  }
>  
>  int
> @@ -24040,7 +23986,7 @@ qwx_mac_op_add_interface(struct qwx_pdev *pdev)
>  {
>  	struct qwx_softc *sc = pdev->sc;
>  	struct ieee80211com *ic = &sc->sc_ic;
> -	struct qwx_vif *arvif = NULL;
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct vdev_create_params vdev_param = { 0 };
>  #if 0
>  	struct peer_create_params peer_param;
> @@ -24071,11 +24017,6 @@ qwx_mac_op_add_interface(struct qwx_pdev *pdev)
>  		goto err;
>  	}
>  
> -	arvif = qwx_vif_alloc(sc);
> -	if (arvif == NULL) {
> -		ret = ENOMEM;
> -		goto err;
> -	}
>  #if 0
>  	INIT_DELAYED_WORK(&arvif->connection_loss_work,
>  			  ath11k_mac_vif_sta_connection_loss_work);
> @@ -24130,7 +24071,7 @@ qwx_mac_op_add_interface(struct qwx_pdev *pdev)
>  	    __func__, arvif->vdev_id, arvif->vdev_type,
>  	    arvif->vdev_subtype, sc->free_vdev_map);
>  
> -	ret = qwx_mac_setup_vdev_create_params(arvif, pdev, &vdev_param);
> +	ret = qwx_mac_setup_vdev_create_params(sc, arvif, pdev, &vdev_param);
>  	if (ret) {
>  		printf("%s: failed to create vdev parameters %d: %d\n",
>  		    sc->sc_dev.dv_xname, arvif->vdev_id, ret);
> @@ -24150,13 +24091,7 @@ qwx_mac_op_add_interface(struct qwx_pdev *pdev)
>  	    ether_sprintf(ic->ic_myaddr), arvif->vdev_id);
>  	sc->allocated_vdev_map |= 1U << arvif->vdev_id;
>  	sc->free_vdev_map &= ~(1U << arvif->vdev_id);
> -#ifdef notyet
> -	spin_lock_bh(&ar->data_lock);
> -#endif
> -	TAILQ_INSERT_TAIL(&sc->vif_list, arvif, entry);
> -#ifdef notyet
> -	spin_unlock_bh(&ar->data_lock);
> -#endif
> +
>  	ret = qwx_mac_op_update_vif_offload(sc, pdev, arvif);
>  	if (ret)
>  		goto err_vdev_del;
> @@ -24294,19 +24229,10 @@ err_peer_del:
>  #endif
>  err_vdev_del:
>  	qwx_mac_vdev_delete(sc, arvif);
> -#ifdef notyet
> -	spin_lock_bh(&ar->data_lock);
> -#endif
> -	TAILQ_REMOVE(&sc->vif_list, arvif, entry);
> -#ifdef notyet
> -	spin_unlock_bh(&ar->data_lock);
> -#endif
> -
>  err:
>  #ifdef notyet
>  	mutex_unlock(&ar->conf_mutex);
>  #endif
> -	qwx_vif_free(sc, arvif);
>  	return ret;
>  }
>  
> @@ -26096,18 +26022,13 @@ int
>  qwx_scan(struct qwx_softc *sc, int bgscan)
>  {
>  	struct ieee80211com *ic = &sc->sc_ic;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list);
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct scan_req_params *arg = NULL;
>  	struct ieee80211_channel *chan, *lastc;
>  	int ret = 0, num_channels, i;
>  	uint32_t scan_timeout;
>  	int scan_2ghz = 1, scan_5ghz = 1;
>  
> -	if (arvif == NULL) {
> -		printf("%s: no vdev found\n", sc->sc_dev.dv_xname);
> -		return EINVAL;
> -	}
> -
>  	/*
>  	 * TODO Will we need separate scan iterations on devices with
>  	 * multiple radios?
> @@ -26356,7 +26277,7 @@ qwx_bgscan_done_task(void *arg)
>  {
>  	struct qwx_softc *sc = arg;
>  	struct qwx_dp *dp = &sc->dp;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct ifnet *ifp = &ic->ic_if;
>  	struct ieee80211_node *ni = ic->ic_bss;
> @@ -26589,16 +26510,10 @@ qwx_auth(struct qwx_softc *sc)
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct ieee80211_node *ni = ic->ic_bss;
>  	uint32_t param_id;
> -	struct qwx_vif *arvif;
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct qwx_pdev *pdev;
>  	int ret;
>  
> -	arvif = TAILQ_FIRST(&sc->vif_list);
> -	if (arvif == NULL) {
> -		printf("%s: no vdev found\n", sc->sc_dev.dv_xname);
> -		return EINVAL;
> -	}
> -
>  	pdev = qwx_get_pdev_for_chan(sc, ni->ni_chan);
>  	if (pdev == NULL) {
>  		printf("%s: no pdev found for channel %d\n",
> @@ -26641,7 +26556,7 @@ qwx_deauth(struct qwx_softc *sc)
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct ieee80211_node *ni = ic->ic_bss;
>  	struct qwx_node *nq = (struct qwx_node *)ni;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	struct ath11k_peer *peer;
>  	int ret;
> @@ -26946,7 +26861,7 @@ qwx_updatechan(struct ieee80211com *ic)
>  {
>  	struct ifnet *ifp = &ic->ic_if;
>  	struct qwx_softc *sc = ifp->if_softc;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list);
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	struct ieee80211_node *ni = ic->ic_bss;
>  	struct qwx_node *nq = (struct qwx_node *)ic->ic_bss;
>  	int pdev_id = 0; /* TODO: derive pdev ID somehow? */
> @@ -27034,7 +26949,7 @@ qwx_rx_agg_start(struct qwx_softc *sc, struct ieee8021
>      uint16_t ssn, uint16_t winsize)
>  {
>  	struct ieee80211com *ic = &sc->sc_ic;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	enum hal_pn_type pn_type;
>  
> @@ -27055,7 +26970,7 @@ void
>  qwx_rx_agg_stop(struct qwx_softc *sc, struct ieee80211_node *ni, uint8_t tid,
>      uint16_t ssn, uint16_t winsize, int timeout_val, int start)
>  {
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	struct qwx_node *nq = (struct qwx_node *)ni;
>  	struct ath11k_peer *peer;
> @@ -27174,7 +27089,7 @@ qwx_assoc(struct qwx_softc *sc)
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct ieee80211_node *ni = ic->ic_bss;
>  	struct qwx_node *nq = (struct qwx_node *)ni;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	struct peer_assoc_params peer_arg;
>  	int ret;
> @@ -27232,7 +27147,7 @@ qwx_run(struct qwx_softc *sc)
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct ieee80211_node *ni = ic->ic_bss;
>  	struct qwx_node *nq = (struct qwx_node *)ni;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	struct peer_assoc_params peer_arg;
>  	int ret;
> @@ -27309,7 +27224,7 @@ int
>  qwx_run_stop(struct qwx_softc *sc)
>  {
>  	struct ieee80211com *ic = &sc->sc_ic;
> -	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	struct qwx_vif *arvif = &sc->sc_vif;
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
>  	struct ieee80211_node *ni = ic->ic_bss;
>  	struct qwx_node *nq = (void *)ni;
> @@ -27377,9 +27292,12 @@ qwx_attach(struct qwx_softc *sc)
>  	for (i = 0; i < nitems(sc->pdevs); i++)
>  		sc->pdevs[i].sc = sc;
>  
> -	TAILQ_INIT(&sc->vif_list);
>  	TAILQ_INIT(&sc->peers);
>  
> +	error = qwx_vif_alloc(sc);
> +	if (error)
> +		return error;
> +
>  	error = qwx_init(ifp);
>  	if (error)
>  		return error;
> @@ -27395,6 +27313,9 @@ qwx_detach(struct qwx_softc *sc)
>  {
>  	qwx_free_peers(sc);
>  
> +	qwx_vif_purge(sc);
> +	qwx_vif_free(sc);
> +
>  	if (sc->fwmem) {
>  		qwx_dmamem_free(sc->sc_dmat, sc->fwmem);
>  		sc->fwmem = NULL;
> blob - e4ae6b82503480d3a351b05f9244868e1a852cf6
> blob + 73001394b0c3bcd13e4075db70d647b0c5a0f645
> --- sys/dev/ic/qwxvar.h
> +++ sys/dev/ic/qwxvar.h
> @@ -1668,11 +1668,8 @@ struct qwx_vif {
>  	uint8_t hal_addr_search_flags;
>  	uint8_t search_type;
>  
> -	struct qwx_softc *sc;
> -
>  	uint16_t tx_seq_no;
>  	struct wmi_wmm_params_all_arg wmm_params;
> -	TAILQ_ENTRY(qwx_vif) entry;
>  	union {
>  		struct {
>  			uint32_t uapsd;
> @@ -1720,8 +1717,6 @@ struct qwx_vif {
>  	struct qwx_txmgmt_queue txmgmt;
>  };
>  
> -TAILQ_HEAD(qwx_vif_list, qwx_vif);
> -
>  struct qwx_survey_info {
>  	int8_t noise;
>  	uint64_t time;
> @@ -1953,7 +1948,7 @@ struct qwx_softc {
>  
>  	uint32_t pktlog_defs_checksum;
>  
> -	struct qwx_vif_list vif_list;
> +	struct qwx_vif	sc_vif;
>  	struct qwx_pdev pdevs[MAX_RADIOS];
>  	struct {
>  		enum WMI_HOST_WLAN_BAND supported_bands;
> 
>