Index | Thread | Search

From:
Dave Voutila <dv@sisu.io>
Subject:
Re: qwx(4) crypto offloading
To:
Stefan Sperling <stsp@stsp.name>
Cc:
tech@openbsd.org
Date:
Thu, 23 May 2024 11:29:12 -0400

Download raw body.

Thread
Stefan Sperling <stsp@stsp.name> writes:

> With the patch below, qwx(4) offloads CCMP and TKIP to hardware.
> The performance benefit is small with 11a/b/g modes but becomes
> significant once 11n/11ac speeds will be enabled eventually.
>
> Also, there is a firmware bug which prevents reception of broadcast
> and multicast frames when crypto is done in software. This is why
> ARP and IPv6 are kind of broken on qwx right now, and this diff should
> fix these issues on WPA networks at least.
>
> So far I have only tested CCMP-only (WPA2/AES). In ifconfig such
> networks show up as: wpaciphers cmmp wpagroupcipher ccmp

That's all I have available, but it's working well so far on my x13s. My
network mentions:

wpakey wpaprotos wpa2 wpaakms psk wpaciphers ccmp wpagroupcipher ccmp

qwx0 at pci1 dev 0 function 0 "Qualcomm QCNFA765" rev 0x01: msi
qwx0: wcn6855 hw2.1 fw 0x1106196e address 00:03:7f:12:de:ec

smbios0: vendor LENOVO version "N3HET87W (1.59 )" date 12/05/2023
smbios0: LENOVO 21BX0007US

>
> There are several other cases which still need to be tested:
> 1) wpaciphers cmmp wpagroupcipher tkip
> 2) wpaprotos wpa1 wpaciphers tkip wpagroupcipher tkip
> 3) WEP  (not sure if anyone ever tested qwx with WEP before?)
>
> Due to time constraints I would appreciate help with testing the above.
> If you have an OpenBSD hostap then setting up all of these combinations
> can be done with ifconfig. For APs from other vendors the non-CCMP modes
> will usually be called something like "WPA1" or something that is not "AES".
> In any case, ifconfig qwx0 will display the config provided by the AP.
>
> diff refs/heads/master refs/heads/qwx-crypto
> commit - 30ca340796a98c20c16842cbd6e4c25de4709bdb
> commit + 7cb71619920688a04ffa06cd1aa374fc5f590796
> blob - 7d95691d20a50b10e671a3fd44fc7d6246a19d4d
> blob + c03a0d614c20a3e2f3a0f662d27b545232e19048
> --- sys/dev/ic/qwx.c
> +++ sys/dev/ic/qwx.c
> @@ -152,6 +152,11 @@ int qwx_dp_tx_send_reo_cmd(struct qwx_softc *, struct
>      void (*func)(struct qwx_dp *, void *, enum hal_reo_cmd_status));
>  void qwx_dp_rx_deliver_msdu(struct qwx_softc *, struct qwx_rx_msdu *);
>  void qwx_dp_service_mon_ring(void *);
> +void qwx_peer_frags_flush(struct qwx_softc *, struct ath11k_peer *);
> +int qwx_wmi_vdev_install_key(struct qwx_softc *,
> +    struct wmi_vdev_install_key_arg *, uint8_t);
> +int qwx_dp_peer_rx_pn_replay_config(struct qwx_softc *, struct qwx_vif *,
> +    struct ieee80211_node *, struct ieee80211_key *, int);
>
>  int qwx_scan(struct qwx_softc *);
>  void qwx_scan_abort(struct qwx_softc *);
> @@ -178,7 +183,7 @@ qwx_init(struct ifnet *ifp)
>  	struct ieee80211com *ic = &sc->sc_ic;
>
>  	sc->fw_mode = ATH11K_FIRMWARE_MODE_NORMAL;
> -	sc->crypto_mode = ATH11K_CRYPT_MODE_SW;
> +	sc->crypto_mode = ATH11K_CRYPT_MODE_HW;
>  	sc->frame_mode = ATH11K_HW_TXRX_NATIVE_WIFI;
>  	ic->ic_state = IEEE80211_S_INIT;
>  	sc->ns_nstate = IEEE80211_S_INIT;
> @@ -283,6 +288,7 @@ qwx_stop(struct ifnet *ifp)
>  	/* Cancel scheduled tasks and let any stale tasks finish up. */
>  	task_del(systq, &sc->init_task);
>  	qwx_del_task(sc, sc->sc_nswq, &sc->newstate_task);
> +	qwx_del_task(sc, systq, &sc->setkey_task);
>  	refcnt_finalize(&sc->task_refs, "qwxstop");
>
>  	clear_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags);
> @@ -496,6 +502,262 @@ qwx_media_change(struct ifnet *ifp)
>  }
>
>  int
> +qwx_queue_setkey_cmd(struct ieee80211com *ic, struct ieee80211_node *ni,
> +    struct ieee80211_key *k, int cmd)
> +{
> +	struct qwx_softc *sc = ic->ic_softc;
> +	struct qwx_setkey_task_arg *a;
> +
> +	if (sc->setkey_nkeys >= nitems(sc->setkey_arg) ||
> +	    k->k_id > WMI_MAX_KEY_INDEX)
> +		return ENOSPC;
> +
> +	a = &sc->setkey_arg[sc->setkey_cur];
> +	a->ni = ieee80211_ref_node(ni);
> +	a->k = k;
> +	a->cmd = cmd;
> +	sc->setkey_cur = (sc->setkey_cur + 1) % nitems(sc->setkey_arg);
> +	sc->setkey_nkeys++;
> +	qwx_add_task(sc, systq, &sc->setkey_task);
> +	return EBUSY;
> +}
> +
> +int
> +qwx_set_key(struct ieee80211com *ic, struct ieee80211_node *ni,
> +    struct ieee80211_key *k)
> +{
> +	struct qwx_softc *sc = ic->ic_softc;
> +
> +	if (test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags) ||
> +	    (k->k_cipher != IEEE80211_CIPHER_CCMP &&
> +	    k->k_cipher != IEEE80211_CIPHER_TKIP))
> +		return ieee80211_set_key(ic, ni, k);
> +
> +	return qwx_queue_setkey_cmd(ic, ni, k, QWX_ADD_KEY);
> +}
> +
> +void
> +qwx_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
> +    struct ieee80211_key *k)
> +{
> +	struct qwx_softc *sc = ic->ic_softc;
> +
> +	if (test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags) ||
> +	    (k->k_cipher != IEEE80211_CIPHER_CCMP &&
> +	    k->k_cipher != IEEE80211_CIPHER_TKIP)) {
> +		ieee80211_delete_key(ic, ni, k);
> +		return;
> +	}
> +
> +	if (ic->ic_state != IEEE80211_S_RUN) {
> +		/* Keys removed implicitly when firmware station is removed. */
> +		return;
> +	}
> +
> +	/*
> +	 * net80211 calls us with a NULL node when deleting group keys,
> +	 * but firmware expects a MAC address in the command.
> +	 */
> +	if (ni == NULL)
> +		ni = ic->ic_bss;
> +
> +	qwx_queue_setkey_cmd(ic, ni, k, QWX_DEL_KEY);
> +}
> +
> +int
> +qwx_wmi_install_key_cmd(struct qwx_softc *sc, struct qwx_vif *arvif,
> +    uint8_t *macaddr, struct ieee80211_key *k, uint32_t flags,
> +    int delete_key)
> +{
> +	int ret;
> +	struct wmi_vdev_install_key_arg arg = {
> +		.vdev_id = arvif->vdev_id,
> +		.key_idx = k->k_id,
> +		.key_len = k->k_len,
> +		.key_data = k->k_key,
> +		.key_flags = flags,
> +		.macaddr = macaddr,
> +	};
> +	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
> +#ifdef notyet
> +	lockdep_assert_held(&arvif->ar->conf_mutex);
> +
> +	reinit_completion(&ar->install_key_done);
> +#endif
> +	if (test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags))
> +		return 0;
> +
> +	if (delete_key) {
> +		arg.key_cipher = WMI_CIPHER_NONE;
> +		arg.key_data = NULL;
> +	} else {
> +		switch (k->k_cipher) {
> +		case IEEE80211_CIPHER_CCMP:
> +			arg.key_cipher = WMI_CIPHER_AES_CCM;
> +#if 0
> +			/* TODO: Re-check if flag is valid */
> +			key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV_MGMT;
> +#endif
> +			break;
> +		case IEEE80211_CIPHER_TKIP:
> +			arg.key_cipher = WMI_CIPHER_TKIP;
> +			arg.key_txmic_len = 8;
> +			arg.key_rxmic_len = 8;
> +			break;
> +#if 0
> +		case WLAN_CIPHER_SUITE_CCMP_256:
> +			arg.key_cipher = WMI_CIPHER_AES_CCM;
> +			break;
> +		case WLAN_CIPHER_SUITE_GCMP:
> +		case WLAN_CIPHER_SUITE_GCMP_256:
> +			arg.key_cipher = WMI_CIPHER_AES_GCM;
> +			break;
> +#endif
> +		default:
> +			printf("%s: cipher %u is not supported\n",
> +			    sc->sc_dev.dv_xname, k->k_cipher);
> +			return EOPNOTSUPP;
> +		}
> +#if 0
> +		if (test_bit(ATH11K_FLAG_RAW_MODE, &ar->ab->dev_flags))
> +			key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV |
> +				      IEEE80211_KEY_FLAG_RESERVE_TAILROOM;
> +#endif
> +	}
> +
> +	sc->install_key_done = 0;
> +	ret = qwx_wmi_vdev_install_key(sc, &arg, pdev_id);
> +	if (ret)
> +		return ret;
> +
> +	while (!sc->install_key_done) {
> +		ret = tsleep_nsec(&sc->install_key_done, 0, "qwxinstkey",
> +		    SEC_TO_NSEC(1));
> +		if (ret) {
> +			printf("%s: install key timeout\n",
> +			    sc->sc_dev.dv_xname);
> +			return -1;
> +		}
> +	}
> +
> +	return sc->install_key_status;
> +}
> +
> +int
> +qwx_add_sta_key(struct qwx_softc *sc, struct ieee80211_node *ni,
> +    struct ieee80211_key *k)
> +{
> +	struct ieee80211com *ic = &sc->sc_ic;
> +	struct qwx_node *nq = (struct qwx_node *)ni;
> +	struct ath11k_peer *peer = &nq->peer;
> +	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	int ret = 0;
> +	uint32_t flags = 0;
> +	const int want_keymask = (QWX_NODE_FLAG_HAVE_PAIRWISE_KEY |
> +	    QWX_NODE_FLAG_HAVE_GROUP_KEY);
> +
> +	/*
> +	 * Flush the fragments cache during key (re)install to
> +	 * ensure all frags in the new frag list belong to the same key.
> +	 */
> +	qwx_peer_frags_flush(sc, peer);
> +
> +	if (k->k_flags & IEEE80211_KEY_GROUP)
> +		flags |= WMI_KEY_GROUP;
> +	else
> +		flags |= WMI_KEY_PAIRWISE;
> +
> +	ret = qwx_wmi_install_key_cmd(sc, arvif, ni->ni_macaddr, k, flags, 0);
> +	if (ret) {
> +		printf("%s: installing crypto key failed (%d)\n",
> +		    sc->sc_dev.dv_xname, ret);
> +		return ret;
> +	}
> +
> +	ret = qwx_dp_peer_rx_pn_replay_config(sc, arvif, ni, k, 0);
> +	if (ret) {
> +		printf("%s: failed to offload PN replay detection %d\n",
> +		    sc->sc_dev.dv_xname, ret);
> +		return ret;
> +	}
> +
> +	if (k->k_flags & IEEE80211_KEY_GROUP)
> +		nq->flags |= QWX_NODE_FLAG_HAVE_GROUP_KEY;
> +	else
> +		nq->flags |= QWX_NODE_FLAG_HAVE_PAIRWISE_KEY;
> +
> +	if ((nq->flags & want_keymask) == want_keymask) {
> +		DPRINTF("marking port %s valid\n",
> +		    ether_sprintf(ni->ni_macaddr));
> +		ni->ni_port_valid = 1;
> +		ieee80211_set_link_state(ic, LINK_STATE_UP);
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +qwx_del_sta_key(struct qwx_softc *sc, struct ieee80211_node *ni,
> +    struct ieee80211_key *k)
> +{
> +	struct qwx_node *nq = (struct qwx_node *)ni;
> +	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
> +	int ret = 0;
> +
> +	ret = qwx_wmi_install_key_cmd(sc, arvif, ni->ni_macaddr, k, 0, 1);
> +	if (ret) {
> +		printf("%s: deleting crypto key failed (%d)\n",
> +		    sc->sc_dev.dv_xname, ret);
> +		return ret;
> +	}
> +
> +	ret = qwx_dp_peer_rx_pn_replay_config(sc, arvif, ni, k, 1);
> +	if (ret) {
> +		printf("%s: failed to disable PN replay detection %d\n",
> +		    sc->sc_dev.dv_xname, ret);
> +		return ret;
> +	}
> +
> +	if (k->k_flags & IEEE80211_KEY_GROUP)
> +		nq->flags &= ~QWX_NODE_FLAG_HAVE_GROUP_KEY;
> +	else
> +		nq->flags &= ~QWX_NODE_FLAG_HAVE_PAIRWISE_KEY;
> +
> +	return 0;
> +}
> +
> +void
> +qwx_setkey_task(void *arg)
> +{
> +	struct qwx_softc *sc = arg;
> +	struct ieee80211com *ic = &sc->sc_ic;
> +	struct qwx_setkey_task_arg *a;
> +	int err = 0, s = splnet();
> +
> +	while (sc->setkey_nkeys > 0) {
> +		if (err || test_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags))
> +			break;
> +		a = &sc->setkey_arg[sc->setkey_tail];
> +		KASSERT(a->cmd == QWX_ADD_KEY || a->cmd == QWX_DEL_KEY);
> +		if (ic->ic_state == IEEE80211_S_RUN) {
> +			if (a->cmd == QWX_ADD_KEY)
> +				err = qwx_add_sta_key(sc, a->ni, a->k);
> +			else
> +				err = qwx_del_sta_key(sc, a->ni, a->k);
> +		}
> +		ieee80211_release_node(ic, a->ni);
> +		a->ni = NULL;
> +		a->k = NULL;
> +		sc->setkey_tail = (sc->setkey_tail + 1) %
> +		    nitems(sc->setkey_arg);
> +		sc->setkey_nkeys--;
> +	}
> +
> +	refcnt_rele_wake(&sc->task_refs);
> +	splx(s);
> +}
> +
> +int
>  qwx_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
>  {
>  	struct ifnet *ifp = &ic->ic_if;
> @@ -510,15 +772,27 @@ qwx_newstate(struct ieee80211com *ic, enum ieee80211_s
>  	if (sc->ns_nstate == nstate && nstate != IEEE80211_S_SCAN &&
>  	    nstate != IEEE80211_S_AUTH)
>  		return 0;
> -#if 0
>  	if (ic->ic_state == IEEE80211_S_RUN) {
> +		struct qwx_setkey_task_arg *a;
> +#if 0
>  		qwx_del_task(sc, systq, &sc->ba_task);
> +#endif
>  		qwx_del_task(sc, systq, &sc->setkey_task);
> +		while (sc->setkey_nkeys > 0) {
> +			a = &sc->setkey_arg[sc->setkey_tail];
> +			ieee80211_release_node(ic, a->ni);
> +			a->ni = NULL;
> +			sc->setkey_tail = (sc->setkey_tail + 1) %
> +			    nitems(sc->setkey_arg);
> +			sc->setkey_nkeys--;
> +		}
>  		memset(sc->setkey_arg, 0, sizeof(sc->setkey_arg));
>  		sc->setkey_cur = sc->setkey_tail = sc->setkey_nkeys = 0;
> +#if 0
>  		qwx_del_task(sc, systq, &sc->bgscan_done_task);
> -	}
>  #endif
> +	}
> +
>  	sc->ns_nstate = nstate;
>  	sc->ns_arg = arg;
>
> @@ -893,6 +1167,13 @@ qwx_hw_mac_id_to_srng_id_qca6390(struct ath11k_hw_para
>  	return mac_id;
>  }
>
> +int
> +qwx_hw_ipq8074_rx_desc_get_first_msdu(struct hal_rx_desc *desc)
> +{
> +	return !!FIELD_GET(RX_MSDU_END_INFO2_FIRST_MSDU,
> +	    le32toh(desc->u.ipq8074.msdu_end.info2));
> +}
> +
>  uint8_t
>  qwx_hw_ipq8074_rx_desc_get_l3_pad_bytes(struct hal_rx_desc *desc)
>  {
> @@ -1060,6 +1341,12 @@ qwx_hw_ipq8074_rx_desc_set_msdu_len(struct hal_rx_desc
>  }
>
>  int
> +qwx_dp_rx_h_msdu_end_first_msdu(struct qwx_softc *sc, struct hal_rx_desc *desc)
> +{
> +	return sc->hw_params.hw_ops->rx_desc_get_first_msdu(desc);
> +}
> +
> +int
>  qwx_hw_ipq8074_rx_desc_mac_addr2_valid(struct hal_rx_desc *desc)
>  {
>  	return le32toh(desc->u.ipq8074.mpdu_start.info1) &
> @@ -1524,7 +1811,9 @@ const struct ath11k_hw_ops ipq8074_ops = {
>  	.mac_id_to_srng_id = qwx_hw_mac_id_to_srng_id_ipq8074,
>  #if notyet
>  	.tx_mesh_enable = ath11k_hw_ipq8074_tx_mesh_enable,
> -	.rx_desc_get_first_msdu = ath11k_hw_ipq8074_rx_desc_get_first_msdu,
> +#endif
> +	.rx_desc_get_first_msdu = qwx_hw_ipq8074_rx_desc_get_first_msdu,
> +#if notyet
>  	.rx_desc_get_last_msdu = ath11k_hw_ipq8074_rx_desc_get_last_msdu,
>  #endif
>  	.rx_desc_get_l3_pad_bytes = qwx_hw_ipq8074_rx_desc_get_l3_pad_bytes,
> @@ -1576,7 +1865,9 @@ const struct ath11k_hw_ops ipq6018_ops = {
>  	.mac_id_to_srng_id = qwx_hw_mac_id_to_srng_id_ipq8074,
>  #if notyet
>  	.tx_mesh_enable = ath11k_hw_ipq8074_tx_mesh_enable,
> -	.rx_desc_get_first_msdu = ath11k_hw_ipq8074_rx_desc_get_first_msdu,
> +#endif
> +	.rx_desc_get_first_msdu = qwx_hw_ipq8074_rx_desc_get_first_msdu,
> +#if notyet
>  	.rx_desc_get_last_msdu = ath11k_hw_ipq8074_rx_desc_get_last_msdu,
>  #endif
>  	.rx_desc_get_l3_pad_bytes = qwx_hw_ipq8074_rx_desc_get_l3_pad_bytes,
> @@ -1628,7 +1919,9 @@ const struct ath11k_hw_ops qca6390_ops = {
>  	.mac_id_to_srng_id = qwx_hw_mac_id_to_srng_id_qca6390,
>  #if notyet
>  	.tx_mesh_enable = ath11k_hw_ipq8074_tx_mesh_enable,
> -	.rx_desc_get_first_msdu = ath11k_hw_ipq8074_rx_desc_get_first_msdu,
> +#endif
> +	.rx_desc_get_first_msdu = qwx_hw_ipq8074_rx_desc_get_first_msdu,
> +#if notyet
>  	.rx_desc_get_last_msdu = ath11k_hw_ipq8074_rx_desc_get_last_msdu,
>  #endif
>  	.rx_desc_get_l3_pad_bytes = qwx_hw_ipq8074_rx_desc_get_l3_pad_bytes,
> @@ -1680,7 +1973,9 @@ const struct ath11k_hw_ops qcn9074_ops = {
>  	.mac_id_to_srng_id = qwx_hw_mac_id_to_srng_id_ipq8074,
>  #if notyet
>  	.tx_mesh_enable = ath11k_hw_qcn9074_tx_mesh_enable,
> -	.rx_desc_get_first_msdu = ath11k_hw_qcn9074_rx_desc_get_first_msdu,
> +#endif
> +	.rx_desc_get_first_msdu = qwx_hw_qcn9074_rx_desc_get_first_msdu,
> +#if notyet
>  	.rx_desc_get_last_msdu = ath11k_hw_qcn9074_rx_desc_get_last_msdu,
>  #endif
>  	.rx_desc_get_l3_pad_bytes = qwx_hw_qcn9074_rx_desc_get_l3_pad_bytes,
> @@ -1732,7 +2027,9 @@ const struct ath11k_hw_ops wcn6855_ops = {
>  	.mac_id_to_srng_id = qwx_hw_mac_id_to_srng_id_qca6390,
>  #if notyet
>  	.tx_mesh_enable = ath11k_hw_wcn6855_tx_mesh_enable,
> -	.rx_desc_get_first_msdu = ath11k_hw_wcn6855_rx_desc_get_first_msdu,
> +#endif
> +	.rx_desc_get_first_msdu = qwx_hw_wcn6855_rx_desc_get_first_msdu,
> +#if notyet
>  	.rx_desc_get_last_msdu = ath11k_hw_wcn6855_rx_desc_get_last_msdu,
>  #endif
>  	.rx_desc_get_l3_pad_bytes = qwx_hw_wcn6855_rx_desc_get_l3_pad_bytes,
> @@ -1784,7 +2081,9 @@ const struct ath11k_hw_ops wcn6750_ops = {
>  	.mac_id_to_srng_id = qwx_hw_mac_id_to_srng_id_qca6390,
>  #if notyet
>  	.tx_mesh_enable = ath11k_hw_qcn9074_tx_mesh_enable,
> -	.rx_desc_get_first_msdu = ath11k_hw_qcn9074_rx_desc_get_first_msdu,
> +#endif
> +	.rx_desc_get_first_msdu = qwx_hw_qcn9074_rx_desc_get_first_msdu,
> +#if notyet
>  	.rx_desc_get_last_msdu = ath11k_hw_qcn9074_rx_desc_get_last_msdu,
>  #endif
>  	.rx_desc_get_l3_pad_bytes = qwx_hw_qcn9074_rx_desc_get_l3_pad_bytes,
> @@ -12990,7 +13289,85 @@ qwx_roam_event(struct qwx_softc *sc, struct mbuf *m)
>  	}
>  }
>
> +int
> +qwx_pull_vdev_install_key_compl_ev(struct qwx_softc *sc, struct mbuf *m,
> +    struct wmi_vdev_install_key_complete_arg *arg)
> +{
> +	const void **tb;
> +	const struct wmi_vdev_install_key_compl_event *ev;
> +	int ret;
> +
> +	tb = qwx_wmi_tlv_parse_alloc(sc, mtod(m, void *), m->m_pkthdr.len);
> +	if (tb == NULL) {
> +		ret = ENOMEM;
> +		printf("%s: failed to parse tlv: %d\n",
> +		    sc->sc_dev.dv_xname, ret);
> +		return ret;
> +	}
> +
> +	ev = tb[WMI_TAG_VDEV_INSTALL_KEY_COMPLETE_EVENT];
> +	if (!ev) {
> +		printf("%s: failed to fetch vdev install key compl ev\n",
> +		    sc->sc_dev.dv_xname);
> +		free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
> +		return EPROTO;
> +	}
> +
> +	arg->vdev_id = ev->vdev_id;
> +	arg->macaddr = ev->peer_macaddr.addr;
> +	arg->key_idx = ev->key_idx;
> +	arg->key_flags = ev->key_flags;
> +	arg->status = ev->status;
> +
> +	free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
> +	return 0;
> +}
> +
>  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) {
> +		printf("%s: failed to extract install key compl event\n",
> +		    sc->sc_dev.dv_xname);
> +		return;
> +	}
> +
> +	DNPRINTF(QWX_D_WMI, "%s: event vdev install key ev idx %d flags %08x "
> +	    "macaddr %s status %d\n", __func__, install_key_compl.key_idx,
> +	    install_key_compl.key_flags,
> +	    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 !=
> +	    WMI_VDEV_INSTALL_KEY_COMPL_STATUS_SUCCESS) {
> +		printf("%s: install key failed for %s status %d\n",
> +		    sc->sc_dev.dv_xname,
> +		    ether_sprintf((u_char *)install_key_compl.macaddr),
> +		    install_key_compl.status);
> +		sc->install_key_status = install_key_compl.status;
> +	}
> +
> +	sc->install_key_done = 1;
> +	wakeup(&sc->install_key_done);
> +}
> +
> +void
>  qwx_wmi_tlv_op_rx(struct qwx_softc *sc, struct mbuf *m)
>  {
>  	struct wmi_cmd_hdr *cmd_hdr;
> @@ -13060,10 +13437,10 @@ qwx_wmi_tlv_op_rx(struct qwx_softc *sc, struct mbuf *m
>  	case WMI_PDEV_BSS_CHAN_INFO_EVENTID:
>  		ath11k_pdev_bss_chan_info_event(ab, skb);
>  		break;
> +#endif
>  	case WMI_VDEV_INSTALL_KEY_COMPLETE_EVENTID:
> -		ath11k_vdev_install_key_compl_event(ab, skb);
> +		qwx_vdev_install_key_compl_event(sc, m);
>  		break;
> -#endif
>  	case WMI_SERVICE_AVAILABLE_EVENTID:
>  		qwx_service_available_event(sc, m);
>  		break;
> @@ -15813,6 +16190,16 @@ qwx_dp_rx_get_attention(struct qwx_softc *sc, struct h
>  	return sc->hw_params.hw_ops->rx_desc_get_attention(desc);
>  }
>
> +int
> +qwx_dp_rx_h_attn_is_mcbc(struct qwx_softc *sc, struct hal_rx_desc *desc)
> +{
> +	struct rx_attention *attn = qwx_dp_rx_get_attention(sc, desc);
> +
> +	return qwx_dp_rx_h_msdu_end_first_msdu(sc, desc) &&
> +		(!!FIELD_GET(RX_ATTENTION_INFO1_MCAST_BCAST,
> +		 le32toh(attn->info1)));
> +}
> +
>  static inline uint8_t
>  qwx_dp_rx_h_msdu_end_l3pad(struct qwx_softc *sc, struct hal_rx_desc *desc)
>  {
> @@ -15874,6 +16261,13 @@ qwx_dp_rx_h_attn_msdu_len_err(struct qwx_softc *sc, st
>  }
>
>  int
> +qwx_dp_rx_h_attn_is_decrypted(struct rx_attention *attn)
> +{
> +	return (FIELD_GET(RX_ATTENTION_INFO2_DCRYPT_STATUS_CODE,
> +	    le32toh(attn->info2)) == RX_DESC_DECRYPT_STATUS_CODE_OK);
> +}
> +
> +int
>  qwx_dp_rx_msdu_coalesce(struct qwx_softc *sc, struct qwx_rx_msdu_list *msdu_list,
>      struct qwx_rx_msdu *first, struct qwx_rx_msdu *last, uint8_t l3pad_bytes,
>      int msdu_len)
> @@ -15908,7 +16302,13 @@ void
>  qwx_dp_rx_h_undecap_nwifi(struct qwx_softc *sc, struct qwx_rx_msdu *msdu,
>      uint8_t *first_hdr, enum hal_encrypt_type enctype)
>  {
> -	printf("%s: not implemented\n", __func__);
> +	/*
> +	* This function will need to do some work once we are receiving
> +	* aggregated frames. For now, it needs to do nothing.
> +	*/
> +
> +	if (!msdu->is_first_msdu)
> +		printf("%s: not implemented\n", __func__);
>  }
>
>  void
> @@ -16034,28 +16434,28 @@ qwx_dp_rx_h_undecap(struct qwx_softc *sc, struct qwx_r
>  	}
>  }
>
> -
> -void
> +int
>  qwx_dp_rx_h_mpdu(struct qwx_softc *sc, struct qwx_rx_msdu *msdu,
>      struct hal_rx_desc *rx_desc)
>  {
> -#if 0
> -	bool  fill_crypto_hdr;
> -#endif
> +	struct ieee80211com *ic = &sc->sc_ic;
> +	int fill_crypto_hdr = 0;
>  	enum hal_encrypt_type enctype;
>  	int is_decrypted = 0;
>  #if 0
>  	struct ath11k_skb_rxcb *rxcb;
> -	struct ieee80211_hdr *hdr;
> +#endif
> +	struct ieee80211_frame *wh;
> +#if 0
>  	struct ath11k_peer *peer;
> +#endif
>  	struct rx_attention *rx_attention;
> -	u32 err_bitmap;
> +	uint32_t err_bitmap;
>
> -	/* PN for multicast packets will be checked in mac80211 */
> -	rxcb = ATH11K_SKB_RXCB(msdu);
> -	fill_crypto_hdr = ath11k_dp_rx_h_attn_is_mcbc(ar->ab, rx_desc);
> -	rxcb->is_mcbc = fill_crypto_hdr;
> -
> +	/* PN for multicast packets will be checked in net80211 */
> +	fill_crypto_hdr = qwx_dp_rx_h_attn_is_mcbc(sc, rx_desc);
> +	msdu->is_mcbc = fill_crypto_hdr;
> +#if 0
>  	if (rxcb->is_mcbc) {
>  		rxcb->peer_id = ath11k_dp_rx_h_mpdu_start_peer_id(ar->ab, rx_desc);
>  		rxcb->seq_no = ath11k_dp_rx_h_mpdu_start_seq_no(ar->ab, rx_desc);
> @@ -16074,12 +16474,12 @@ qwx_dp_rx_h_mpdu(struct qwx_softc *sc, struct qwx_rx_m
>  #if 0
>  	}
>  	spin_unlock_bh(&ar->ab->base_lock);
> -
> -	rx_attention = ath11k_dp_rx_get_attention(ar->ab, rx_desc);
> -	err_bitmap = ath11k_dp_rx_h_attn_mpdu_err(rx_attention);
> +#endif
> +	rx_attention = qwx_dp_rx_get_attention(sc, rx_desc);
> +	err_bitmap = qwx_dp_rx_h_attn_mpdu_err(rx_attention);
>  	if (enctype != HAL_ENCRYPT_TYPE_OPEN && !err_bitmap)
> -		is_decrypted = ath11k_dp_rx_h_attn_is_decrypted(rx_attention);
> -
> +		is_decrypted = qwx_dp_rx_h_attn_is_decrypted(rx_attention);
> +#if 0
>  	/* Clear per-MPDU flags while leaving per-PPDU flags intact */
>  	rx_status->flag &= ~(RX_FLAG_FAILED_FCS_CRC |
>  			     RX_FLAG_MMIC_ERROR |
> @@ -16087,12 +16487,23 @@ qwx_dp_rx_h_mpdu(struct qwx_softc *sc, struct qwx_rx_m
>  			     RX_FLAG_IV_STRIPPED |
>  			     RX_FLAG_MMIC_STRIPPED);
>
> -	if (err_bitmap & DP_RX_MPDU_ERR_FCS)
> -		rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
> +#endif
> +	if (err_bitmap & DP_RX_MPDU_ERR_FCS) {
> +		if (ic->ic_flags & IEEE80211_F_RSNON)
> +			ic->ic_stats.is_rx_decryptcrc++;
> +		else
> +			ic->ic_stats.is_rx_decap++;
> +	}
> +
> +	/* XXX Trusting firmware to handle Michael MIC counter-measures... */
>  	if (err_bitmap & DP_RX_MPDU_ERR_TKIP_MIC)
> -		rx_status->flag |= RX_FLAG_MMIC_ERROR;
> +		ic->ic_stats.is_rx_locmicfail++;
>
> +	if (err_bitmap & DP_RX_MPDU_ERR_DECRYPT)
> +		ic->ic_stats.is_rx_wepfail++;
> +
>  	if (is_decrypted) {
> +#if 0
>  		rx_status->flag |= RX_FLAG_DECRYPTED | RX_FLAG_MMIC_STRIPPED;
>
>  		if (fill_crypto_hdr)
> @@ -16101,21 +16512,23 @@ qwx_dp_rx_h_mpdu(struct qwx_softc *sc, struct qwx_rx_m
>  		else
>  			rx_status->flag |= RX_FLAG_IV_STRIPPED |
>  					   RX_FLAG_PN_VALIDATED;
> +#endif
> +		msdu->rxi.rxi_flags |= IEEE80211_RXI_HWDEC;
>  	}
> -
> +#if 0
>  	ath11k_dp_rx_h_csum_offload(ar, msdu);
>  #endif
>  	qwx_dp_rx_h_undecap(sc, msdu, rx_desc, enctype, is_decrypted);
> -#if 0
> -	if (!is_decrypted || fill_crypto_hdr)
> -		return;
>
> -	if (ath11k_dp_rx_h_msdu_start_decap_type(ar->ab, rx_desc) !=
> +	if (is_decrypted && !fill_crypto_hdr &&
> +	    qwx_dp_rx_h_msdu_start_decap_type(sc, rx_desc) !=
>  	    DP_RX_DECAP_TYPE_ETHERNET2_DIX) {
> -		hdr = (void *)msdu->data;
> -		hdr->frame_control &= ~__cpu_to_le16(IEEE80211_FCTL_PROTECTED);
> +		/* Hardware has stripped the IV. */
> +		wh = mtod(msdu->m, struct ieee80211_frame *);
> +		wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
>  	}
> -#endif
> +
> +	return err_bitmap ? EIO : 0;
>  }
>
>  int
> @@ -16189,9 +16602,8 @@ qwx_dp_rx_process_msdu(struct qwx_softc *sc, struct qw
>
>  	memset(&msdu->rxi, 0, sizeof(msdu->rxi));
>  	qwx_dp_rx_h_ppdu(sc, rx_desc, &msdu->rxi);
> -	qwx_dp_rx_h_mpdu(sc, msdu, rx_desc);
>
> -	return 0;
> +	return qwx_dp_rx_h_mpdu(sc, msdu, rx_desc);
>  }
>
>  void
> @@ -18003,6 +18415,65 @@ qwx_wmi_send_peer_delete_cmd(struct qwx_softc *sc, con
>  	return 0;
>  }
>
> +int
> +qwx_wmi_vdev_install_key(struct qwx_softc *sc,
> +    struct wmi_vdev_install_key_arg *arg, uint8_t pdev_id)
> +{
> +	struct qwx_pdev_wmi *wmi = &sc->wmi.wmi[pdev_id];
> +	struct wmi_vdev_install_key_cmd *cmd;
> +	struct wmi_tlv *tlv;
> +	struct mbuf *m;
> +	int ret, len;
> +	int key_len_aligned = roundup(arg->key_len, sizeof(uint32_t));
> +
> +	len = sizeof(*cmd) + TLV_HDR_SIZE + key_len_aligned;
> +
> +	m = qwx_wmi_alloc_mbuf(len);
> +	if (m == NULL)
> +		return -ENOMEM;
> +
> +	cmd = (struct wmi_vdev_install_key_cmd *)(mtod(m, uint8_t *) +
> +	    sizeof(struct ath11k_htc_hdr) + sizeof(struct wmi_cmd_hdr));
> +	cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG,
> +	    WMI_TAG_VDEV_INSTALL_KEY_CMD) |
> +	    FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);
> +	cmd->vdev_id = arg->vdev_id;
> +	IEEE80211_ADDR_COPY(cmd->peer_macaddr.addr, arg->macaddr);
> +	cmd->key_idx = arg->key_idx;
> +	cmd->key_flags = arg->key_flags;
> +	cmd->key_cipher = arg->key_cipher;
> +	cmd->key_len = arg->key_len;
> +	cmd->key_txmic_len = arg->key_txmic_len;
> +	cmd->key_rxmic_len = arg->key_rxmic_len;
> +
> +	if (arg->key_rsc_counter)
> +		memcpy(&cmd->key_rsc_counter, &arg->key_rsc_counter,
> +		       sizeof(struct wmi_key_seq_counter));
> +
> +	tlv = (struct wmi_tlv *)(mtod(m, uint8_t *) +
> +	    sizeof(struct ath11k_htc_hdr) + sizeof(struct wmi_cmd_hdr) +
> +	    sizeof(*cmd));
> +	tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) |
> +	    FIELD_PREP(WMI_TLV_LEN, key_len_aligned);
> +	if (arg->key_data)
> +		memcpy(tlv->value, (uint8_t *)arg->key_data,
> +		    key_len_aligned);
> +
> +	ret = qwx_wmi_cmd_send(wmi, m, WMI_VDEV_INSTALL_KEY_CMDID);
> +	if (ret) {
> +		printf("%s: failed to send WMI_VDEV_INSTALL_KEY cmd\n",
> +		    sc->sc_dev.dv_xname);
> +		m_freem(m);
> +		return ret;
> +	}
> +
> +	DNPRINTF(QWX_D_WMI,
> +	    "%s: cmd vdev install key idx %d cipher %d len %d\n",
> +	    __func__, arg->key_idx, arg->key_cipher, arg->key_len);
> +
> +	return ret;
> +}
> +
>  void
>  qwx_wmi_copy_peer_flags(struct wmi_peer_assoc_complete_cmd *cmd,
>      struct peer_assoc_params *param, int hw_crypto_disabled)
> @@ -23303,6 +23774,26 @@ qwx_dp_rx_frags_cleanup(struct qwx_softc *sc, struct d
>  }
>
>  void
> +qwx_peer_frags_flush(struct qwx_softc *sc, struct ath11k_peer *peer)
> +{
> +	struct dp_rx_tid *rx_tid;
> +	int i;
> +#ifdef notyet
> +	lockdep_assert_held(&ar->ab->base_lock);
> +#endif
> +	for (i = 0; i < IEEE80211_NUM_TID; i++) {
> +		rx_tid = &peer->rx_tid[i];
> +
> +		qwx_dp_rx_frags_cleanup(sc, rx_tid, 1);
> +#if 0
> +		spin_unlock_bh(&ar->ab->base_lock);
> +		del_timer_sync(&rx_tid->frag_timer);
> +		spin_lock_bh(&ar->ab->base_lock);
> +#endif
> +	}
> +}
> +
> +void
>  qwx_peer_rx_tid_cleanup(struct qwx_softc *sc, struct ath11k_peer *peer)
>  {
>  	struct dp_rx_tid *rx_tid;
> @@ -23556,6 +24047,70 @@ peer_clean:
>  	return ret;
>  }
>
> +int
> +qwx_dp_peer_rx_pn_replay_config(struct qwx_softc *sc, struct qwx_vif *arvif,
> +    struct ieee80211_node *ni, struct ieee80211_key *k, int delete_key)
> +{
> +	struct ath11k_hal_reo_cmd cmd = {0};
> +	struct qwx_node *nq = (struct qwx_node *)ni;
> +	struct ath11k_peer *peer = &nq->peer;
> +	struct dp_rx_tid *rx_tid;
> +	uint8_t tid;
> +	int ret = 0;
> +
> +	/*
> +	 * NOTE: Enable PN/TSC replay check offload only for unicast frames.
> +	 * We use net80211 PN/TSC replay check functionality for bcast/mcast
> +	 * for now.
> +	 */
> +	if (k->k_flags & IEEE80211_KEY_GROUP)
> +		return 0;
> +
> +	cmd.flag |= HAL_REO_CMD_FLG_NEED_STATUS;
> +	cmd.upd0 |= HAL_REO_CMD_UPD0_PN |
> +		    HAL_REO_CMD_UPD0_PN_SIZE |
> +		    HAL_REO_CMD_UPD0_PN_VALID |
> +		    HAL_REO_CMD_UPD0_PN_CHECK |
> +		    HAL_REO_CMD_UPD0_SVLD;
> +
> +	switch (k->k_cipher) {
> +	case IEEE80211_CIPHER_TKIP:
> +	case IEEE80211_CIPHER_CCMP:
> +#if 0
> +	case WLAN_CIPHER_SUITE_CCMP_256:
> +	case WLAN_CIPHER_SUITE_GCMP:
> +	case WLAN_CIPHER_SUITE_GCMP_256:
> +#endif
> +		if (!delete_key) {
> +			cmd.upd1 |= HAL_REO_CMD_UPD1_PN_CHECK;
> +			cmd.pn_size = 48;
> +		}
> +		break;
> +	default:
> +		printf("%s: cipher %u is not supported\n",
> +		    sc->sc_dev.dv_xname, k->k_cipher);
> +		return EOPNOTSUPP;
> +	}
> +
> +	for (tid = 0; tid < IEEE80211_NUM_TID; tid++) {
> +		rx_tid = &peer->rx_tid[tid];
> +		if (!rx_tid->active)
> +			continue;
> +		cmd.addr_lo = rx_tid->paddr & 0xffffffff;
> +		cmd.addr_hi = (rx_tid->paddr >> 32);
> +		ret = qwx_dp_tx_send_reo_cmd(sc, rx_tid,
> +		    HAL_REO_CMD_UPDATE_RX_QUEUE, &cmd, NULL);
> +		if (ret) {
> +			printf("%s: failed to configure rx tid %d queue "
> +			    "for pn replay detection %d\n",
> +			    sc->sc_dev.dv_xname, tid, ret);
> +			break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
>  enum hal_tcl_encap_type
>  qwx_dp_tx_get_encap_type(struct qwx_softc *sc)
>  {
> @@ -23676,20 +24231,25 @@ qwx_dp_tx(struct qwx_softc *sc, struct qwx_vif *arvif,
>
>  	ti.meta_data_flags = arvif->tcl_metadata;
>
> -	if (ti.encap_type == HAL_TCL_ENCAP_TYPE_RAW) {
> -#if 0
> -		if (skb_cb->flags & ATH11K_SKB_CIPHER_SET) {
> -			ti.encrypt_type =
> -				ath11k_dp_tx_get_encrypt_type(skb_cb->cipher);
> -
> -			if (ieee80211_has_protected(hdr->frame_control))
> -				skb_put(skb, IEEE80211_CCMP_MIC_LEN);
> -		} else
> -#endif
> +	if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
> +	    ti.encap_type == HAL_TCL_ENCAP_TYPE_RAW) {
> +		k = ieee80211_get_txkey(ic, wh, ni);
> +		switch (k->k_cipher) {
> +		case IEEE80211_CIPHER_CCMP:
> +			ti.encrypt_type = HAL_ENCRYPT_TYPE_CCMP_128;
> +			m->m_pkthdr.len += IEEE80211_CCMP_MICLEN;
> +			break;
> +		case IEEE80211_CIPHER_TKIP:
> +			ti.encrypt_type = HAL_ENCRYPT_TYPE_TKIP_MIC;
> +			m->m_pkthdr.len += IEEE80211_TKIP_MICLEN;
> +			break;
> +		default:
> +			/* Fallback to software crypto for other ciphers. */
>  			ti.encrypt_type = HAL_ENCRYPT_TYPE_OPEN;
> +			break;
> +		}
>
> -		if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
> -			k = ieee80211_get_txkey(ic, wh, ni);
> +		if (ti.encrypt_type == HAL_ENCRYPT_TYPE_OPEN) {
>  			if ((m = ieee80211_encrypt(ic, m, k)) == NULL)
>  				return ENOBUFS;
>  			/* 802.11 header may have moved. */
> @@ -24582,6 +25142,25 @@ qwx_peer_assoc_h_basic(struct qwx_softc *sc, struct qw
>  	arg->peer_caps = ni->ni_capinfo;
>  }
>
> +void
> +qwx_peer_assoc_h_crypto(struct qwx_softc *sc, struct qwx_vif *arvif,
> +    struct ieee80211_node *ni, struct peer_assoc_params *arg)
> +{
> +	struct ieee80211com *ic = &sc->sc_ic;
> +
> +	if (ic->ic_flags & IEEE80211_F_RSNON) {
> +		arg->need_ptk_4_way = 1;
> +		if (ni->ni_rsnprotos == IEEE80211_PROTO_WPA)
> +			arg->need_gtk_2_way = 1;
> +	}
> +#if 0
> +	if (sta->mfp) {
> +		/* TODO: Need to check if FW supports PMF? */
> +		arg->is_pmf_enabled = true;
> +	}
> +#endif
> +}
> +
>  int
>  qwx_mac_rate_is_cck(uint8_t rate)
>  {
> @@ -24641,9 +25220,7 @@ qwx_peer_assoc_prepare(struct qwx_softc *sc, struct qw
>
>  	arg->peer_new_assoc = !reassoc;
>  	qwx_peer_assoc_h_basic(sc, arvif, ni, arg);
> -#if 0
>  	qwx_peer_assoc_h_crypto(sc, arvif, ni, arg);
> -#endif
>  	qwx_peer_assoc_h_rates(ni, arg);
>  	qwx_peer_assoc_h_phymode(sc, ni, arg);
>  #if 0
> @@ -24757,12 +25334,15 @@ qwx_run_stop(struct qwx_softc *sc)
>  	struct ieee80211com *ic = &sc->sc_ic;
>  	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
>  	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
> +	struct qwx_node *nq = (void *)ic->ic_bss;
>  	int ret;
>
>  	sc->ops.irq_disable(sc);
>
> -	if (ic->ic_opmode == IEEE80211_M_STA)
> +	if (ic->ic_opmode == IEEE80211_M_STA) {
>  		ic->ic_bss->ni_txrate = 0;
> +		nq->flags = 0;
> +	}
>
>  	ret = qwx_wmi_vdev_down(sc, arvif->vdev_id, pdev_id);
>  	if (ret)
> @@ -24801,6 +25381,7 @@ qwx_attach(struct qwx_softc *sc)
>
>  	task_set(&sc->init_task, qwx_init_task, sc);
>  	task_set(&sc->newstate_task, qwx_newstate_task, sc);
> +	task_set(&sc->setkey_task, qwx_setkey_task, sc);
>  	timeout_set_proc(&sc->scan.timeout, qwx_scan_timeout, sc);
>  #if NBPFILTER > 0
>  	qwx_radiotap_attach(sc);
> blob - 58c0bf6460d6cace7da8c13b8f66b90f5a022617
> blob + 9bafe25e29d455cf89c5508053d3daa116b84cb6
> --- sys/dev/ic/qwxvar.h
> +++ sys/dev/ic/qwxvar.h
> @@ -264,7 +264,9 @@ struct ath11k_hw_ops {
>  #if notyet
>  	void (*tx_mesh_enable)(struct ath11k_base *ab,
>  			       struct hal_tcl_data_cmd *tcl_cmd);
> -	bool (*rx_desc_get_first_msdu)(struct hal_rx_desc *desc);
> +#endif
> +	int (*rx_desc_get_first_msdu)(struct hal_rx_desc *desc);
> +#if notyet
>  	bool (*rx_desc_get_last_msdu)(struct hal_rx_desc *desc);
>  #endif
>  	uint8_t (*rx_desc_get_l3_pad_bytes)(struct hal_rx_desc *desc);
> @@ -1745,6 +1747,14 @@ struct qwx_tx_radiotap_header {
>
>  #define IWX_TX_RADIOTAP_PRESENT	0 /* TODO add more information */
>
> +struct qwx_setkey_task_arg {
> +	struct ieee80211_node *ni;
> +	struct ieee80211_key *k;
> +	int cmd;
> +#define QWX_ADD_KEY	1
> +#define QWX_DEL_KEY	2
> +};
> +
>  struct qwx_softc {
>  	struct device			sc_dev;
>  	struct ieee80211com		sc_ic;
> @@ -1762,6 +1772,23 @@ struct qwx_softc {
>  	enum ieee80211_state	ns_nstate;
>  	int			ns_arg;
>
> +	/* Task for setting encryption keys and its arguments. */
> +	struct task		setkey_task;
> +	/*
> +	 * At present we need to process at most two keys at once:
> +	 * Our pairwise key and a group key.
> +	 * When hostap mode is implemented this array needs to grow or
> +	 * it might become a bottleneck for associations that occur at
> +	 * roughly the same time.
> +	 */
> +	struct qwx_setkey_task_arg setkey_arg[2];
> +	int setkey_cur;
> +	int setkey_tail;
> +	int setkey_nkeys;
> +
> +	int install_key_done;
> +	int install_key_status;
> +
>  	enum ath11k_11d_state	state_11d;
>  	int			completed_11d_scan;
>  	uint32_t		vdev_id_11d_scan;
> @@ -1962,9 +1989,16 @@ struct ath11k_peer {
>  struct qwx_node {
>  	struct ieee80211_node ni;
>  	struct ath11k_peer peer;
> +	unsigned int flags;
> +#define QWX_NODE_FLAG_HAVE_PAIRWISE_KEY	0x01
> +#define QWX_NODE_FLAG_HAVE_GROUP_KEY	0x02
>  };
>
>  struct ieee80211_node *qwx_node_alloc(struct ieee80211com *);
> +int	qwx_set_key(struct ieee80211com *, struct ieee80211_node *,
> +    struct ieee80211_key *);
> +void	qwx_delete_key(struct ieee80211com *, struct ieee80211_node *,
> +    struct ieee80211_key *);
>
>  void	qwx_qrtr_recv_msg(struct qwx_softc *, struct mbuf *);
>
> blob - 6e27e346cb3b08950b568e86ffcc6a35a4fc2970
> blob + b6b3d73b5ef65491fda360aebad5a260a9ebf3a1
> --- sys/dev/pci/if_qwx_pci.c
> +++ sys/dev/pci/if_qwx_pci.c
> @@ -1089,6 +1089,8 @@ unsupported_wcn6855_soc:
>  	/* Override 802.11 state transition machine. */
>  	sc->sc_newstate = ic->ic_newstate;
>  	ic->ic_newstate = qwx_newstate;
> +	ic->ic_set_key = qwx_set_key;
> +	ic->ic_delete_key = qwx_delete_key;
>  #if 0
>  	ic->ic_updatechan = qwx_updatechan;
>  	ic->ic_updateprot = qwx_updateprot;