Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
qwx(4) crypto offloading
To:
tech@openbsd.org
Date:
Thu, 23 May 2024 16:27:53 +0200

Download raw body.

Thread
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

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;