diff --git a/package/kernel/mac80211/Makefile b/package/kernel/mac80211/Makefile
index d0620c556a43190c1b7d5fd8b21fdcec4491eb8b..dd39c2d069322f8af7f58dac46ed43823a0ade8d 100644
--- a/package/kernel/mac80211/Makefile
+++ b/package/kernel/mac80211/Makefile
@@ -127,7 +127,7 @@ define KernelPackage/mac80211
   $(call KernelPackage/mac80211/Default)
   TITLE:=Linux 802.11 Wireless Networking Stack
   # +kmod-crypto-cmac is a runtime only dependency of net/mac80211/aes_cmac.c
-  DEPENDS+= +kmod-cfg80211 +hostapd-common
+  DEPENDS+= +kmod-cfg80211 +kmod-crypto-cmac +kmod-crypto-ccm +kmod-crypto-gcm +hostapd-common
   KCONFIG:=\
 	CONFIG_AVERAGE=y
   FILES:= $(PKG_BUILD_DIR)/net/mac80211/mac80211.ko
diff --git a/package/kernel/mac80211/patches/ath/402-ath_regd_optional.patch b/package/kernel/mac80211/patches/ath/402-ath_regd_optional.patch
index bf87d3551a18bd3ceb35f9ab98e5ee80609952f7..3c9180b1137cc67d011122afd4696c54c0b5c49f 100644
--- a/package/kernel/mac80211/patches/ath/402-ath_regd_optional.patch
+++ b/package/kernel/mac80211/patches/ath/402-ath_regd_optional.patch
@@ -82,7 +82,7 @@
  	help
 --- a/local-symbols
 +++ b/local-symbols
-@@ -85,6 +85,7 @@ ADM8211=
+@@ -86,6 +86,7 @@ ADM8211=
  ATH_COMMON=
  WLAN_VENDOR_ATH=
  ATH_DEBUG=
diff --git a/package/kernel/mac80211/patches/ath10k/080-ath10k_thermal_config.patch b/package/kernel/mac80211/patches/ath10k/080-ath10k_thermal_config.patch
index d183419a47bd0be606b37e5ca6898a1075c11755..9ce44fd2880184269e2636b1ed6109dd71172471 100644
--- a/package/kernel/mac80211/patches/ath10k/080-ath10k_thermal_config.patch
+++ b/package/kernel/mac80211/patches/ath10k/080-ath10k_thermal_config.patch
@@ -37,7 +37,7 @@
  void ath10k_thermal_event_temperature(struct ath10k *ar, int temperature);
 --- a/local-symbols
 +++ b/local-symbols
-@@ -144,6 +144,7 @@ ATH10K_SNOC=
+@@ -145,6 +145,7 @@ ATH10K_SNOC=
  ATH10K_DEBUG=
  ATH10K_DEBUGFS=
  ATH10K_SPECTRAL=
diff --git a/package/kernel/mac80211/patches/ath10k/974-ath10k_add-LED-and-GPIO-controlling-support-for-various-chipsets.patch b/package/kernel/mac80211/patches/ath10k/974-ath10k_add-LED-and-GPIO-controlling-support-for-various-chipsets.patch
index ce8effe3c3d8c1a66141b9f5f70cf56a96a73a97..fa007e73a1f17cb4bef56dc5ca041fdf63f88468 100644
--- a/package/kernel/mac80211/patches/ath10k/974-ath10k_add-LED-and-GPIO-controlling-support-for-various-chipsets.patch
+++ b/package/kernel/mac80211/patches/ath10k/974-ath10k_add-LED-and-GPIO-controlling-support-for-various-chipsets.patch
@@ -114,7 +114,7 @@ v13:
  ath10k_core-$(CONFIG_DEV_COREDUMP) += coredump.o
 --- a/local-symbols
 +++ b/local-symbols
-@@ -145,6 +145,7 @@ ATH10K_DEBUG=
+@@ -146,6 +146,7 @@ ATH10K_DEBUG=
  ATH10K_DEBUGFS=
  ATH10K_SPECTRAL=
  ATH10K_THERMAL=
diff --git a/package/kernel/mac80211/patches/ath9k/551-ath9k_ubnt_uap_plus_hsr.patch b/package/kernel/mac80211/patches/ath9k/551-ath9k_ubnt_uap_plus_hsr.patch
index acb9ad443c0b90cf4cbc1c26891c37c1697ba2a7..cd2bdbf1a0c52d29d0c4eca4f8b34d64cf3f1d33 100644
--- a/package/kernel/mac80211/patches/ath9k/551-ath9k_ubnt_uap_plus_hsr.patch
+++ b/package/kernel/mac80211/patches/ath9k/551-ath9k_ubnt_uap_plus_hsr.patch
@@ -371,7 +371,7 @@
  
 --- a/local-symbols
 +++ b/local-symbols
-@@ -112,6 +112,7 @@ ATH9K_WOW=
+@@ -113,6 +113,7 @@ ATH9K_WOW=
  ATH9K_RFKILL=
  ATH9K_CHANNEL_CONTEXT=
  ATH9K_PCOEM=
diff --git a/package/kernel/mac80211/patches/rt2x00/602-rt2x00-introduce-rt2x00eeprom.patch b/package/kernel/mac80211/patches/rt2x00/602-rt2x00-introduce-rt2x00eeprom.patch
index e74d9a9aa0d8e546233210e919cb4f111df3ef89..1c52132da65c8383b25d733715e790d344a6e018 100644
--- a/package/kernel/mac80211/patches/rt2x00/602-rt2x00-introduce-rt2x00eeprom.patch
+++ b/package/kernel/mac80211/patches/rt2x00/602-rt2x00-introduce-rt2x00eeprom.patch
@@ -1,6 +1,6 @@
 --- a/local-symbols
 +++ b/local-symbols
-@@ -332,6 +332,7 @@ RT2X00_LIB_FIRMWARE=
+@@ -333,6 +333,7 @@ RT2X00_LIB_FIRMWARE=
  RT2X00_LIB_CRYPTO=
  RT2X00_LIB_LEDS=
  RT2X00_LIB_DEBUGFS=
diff --git a/package/kernel/mac80211/patches/subsys/100-remove-cryptoapi-dependencies.patch b/package/kernel/mac80211/patches/subsys/100-remove-cryptoapi-dependencies.patch
deleted file mode 100644
index ca02dfb06f9f0ab619833fd8ed64d2d9c5f73727..0000000000000000000000000000000000000000
--- a/package/kernel/mac80211/patches/subsys/100-remove-cryptoapi-dependencies.patch
+++ /dev/null
@@ -1,699 +0,0 @@
---- a/net/mac80211/Makefile
-+++ b/net/mac80211/Makefile
-@@ -7,7 +7,6 @@ mac80211-y := \
- 	driver-ops.o \
- 	sta_info.o \
- 	wep.o \
--	aead_api.o \
- 	wpa.o \
- 	scan.o offchannel.o \
- 	ht.o agg-tx.o agg-rx.o \
-@@ -19,8 +18,8 @@ mac80211-y := \
- 	rate.o \
- 	michael.o \
- 	tkip.o \
-+	aes_ccm.o \
- 	aes_cmac.o \
--	aes_gmac.o \
- 	fils_aead.o \
- 	cfg.o \
- 	ethtool.o \
---- a/net/mac80211/aead_api.c
-+++ /dev/null
-@@ -1,113 +0,0 @@
--// SPDX-License-Identifier: GPL-2.0-only
--/*
-- * Copyright 2003-2004, Instant802 Networks, Inc.
-- * Copyright 2005-2006, Devicescape Software, Inc.
-- * Copyright 2014-2015, Qualcomm Atheros, Inc.
-- *
-- * Rewrite: Copyright (C) 2013 Linaro Ltd <ard.biesheuvel@linaro.org>
-- */
--
--#include <linux/kernel.h>
--#include <linux/types.h>
--#include <linux/err.h>
--#include <linux/scatterlist.h>
--#include <crypto/aead.h>
--
--#include "aead_api.h"
--
--int aead_encrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad, size_t aad_len,
--		 u8 *data, size_t data_len, u8 *mic)
--{
--	size_t mic_len = crypto_aead_authsize(tfm);
--	struct scatterlist sg[3];
--	struct aead_request *aead_req;
--	int reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
--	u8 *__aad;
--	int ret;
--
--	aead_req = kzalloc(reqsize + aad_len, GFP_ATOMIC);
--	if (!aead_req)
--		return -ENOMEM;
--
--	__aad = (u8 *)aead_req + reqsize;
--	memcpy(__aad, aad, aad_len);
--
--	sg_init_table(sg, 3);
--	sg_set_buf(&sg[0], __aad, aad_len);
--	sg_set_buf(&sg[1], data, data_len);
--	sg_set_buf(&sg[2], mic, mic_len);
--
--	aead_request_set_tfm(aead_req, tfm);
--	aead_request_set_crypt(aead_req, sg, sg, data_len, b_0);
--	aead_request_set_ad(aead_req, sg[0].length);
--
--	ret = crypto_aead_encrypt(aead_req);
--	kfree_sensitive(aead_req);
--
--	return ret;
--}
--
--int aead_decrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad, size_t aad_len,
--		 u8 *data, size_t data_len, u8 *mic)
--{
--	size_t mic_len = crypto_aead_authsize(tfm);
--	struct scatterlist sg[3];
--	struct aead_request *aead_req;
--	int reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
--	u8 *__aad;
--	int err;
--
--	if (data_len == 0)
--		return -EINVAL;
--
--	aead_req = kzalloc(reqsize + aad_len, GFP_ATOMIC);
--	if (!aead_req)
--		return -ENOMEM;
--
--	__aad = (u8 *)aead_req + reqsize;
--	memcpy(__aad, aad, aad_len);
--
--	sg_init_table(sg, 3);
--	sg_set_buf(&sg[0], __aad, aad_len);
--	sg_set_buf(&sg[1], data, data_len);
--	sg_set_buf(&sg[2], mic, mic_len);
--
--	aead_request_set_tfm(aead_req, tfm);
--	aead_request_set_crypt(aead_req, sg, sg, data_len + mic_len, b_0);
--	aead_request_set_ad(aead_req, sg[0].length);
--
--	err = crypto_aead_decrypt(aead_req);
--	kfree_sensitive(aead_req);
--
--	return err;
--}
--
--struct crypto_aead *
--aead_key_setup_encrypt(const char *alg, const u8 key[],
--		       size_t key_len, size_t mic_len)
--{
--	struct crypto_aead *tfm;
--	int err;
--
--	tfm = crypto_alloc_aead(alg, 0, CRYPTO_ALG_ASYNC);
--	if (IS_ERR(tfm))
--		return tfm;
--
--	err = crypto_aead_setkey(tfm, key, key_len);
--	if (err)
--		goto free_aead;
--	err = crypto_aead_setauthsize(tfm, mic_len);
--	if (err)
--		goto free_aead;
--
--	return tfm;
--
--free_aead:
--	crypto_free_aead(tfm);
--	return ERR_PTR(err);
--}
--
--void aead_key_free(struct crypto_aead *tfm)
--{
--	crypto_free_aead(tfm);
--}
---- a/net/mac80211/aead_api.h
-+++ /dev/null
-@@ -1,23 +0,0 @@
--/* SPDX-License-Identifier: GPL-2.0-only */
--
--#ifndef _AEAD_API_H
--#define _AEAD_API_H
--
--#include <crypto/aead.h>
--#include <linux/crypto.h>
--
--struct crypto_aead *
--aead_key_setup_encrypt(const char *alg, const u8 key[],
--		       size_t key_len, size_t mic_len);
--
--int aead_encrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad,
--		 size_t aad_len, u8 *data,
--		 size_t data_len, u8 *mic);
--
--int aead_decrypt(struct crypto_aead *tfm, u8 *b_0, u8 *aad,
--		 size_t aad_len, u8 *data,
--		 size_t data_len, u8 *mic);
--
--void aead_key_free(struct crypto_aead *tfm);
--
--#endif /* _AEAD_API_H */
---- a/net/mac80211/aes_ccm.h
-+++ b/net/mac80211/aes_ccm.h
-@@ -7,39 +7,17 @@
- #ifndef AES_CCM_H
- #define AES_CCM_H
- 
--#include "aead_api.h"
-+#include <linux/crypto.h>
- 
--#define CCM_AAD_LEN	32
--
--static inline struct crypto_aead *
--ieee80211_aes_key_setup_encrypt(const u8 key[], size_t key_len, size_t mic_len)
--{
--	return aead_key_setup_encrypt("ccm(aes)", key, key_len, mic_len);
--}
--
--static inline int
--ieee80211_aes_ccm_encrypt(struct crypto_aead *tfm,
--			  u8 *b_0, u8 *aad, u8 *data,
--			  size_t data_len, u8 *mic)
--{
--	return aead_encrypt(tfm, b_0, aad + 2,
--			    be16_to_cpup((__be16 *)aad),
--			    data, data_len, mic);
--}
--
--static inline int
--ieee80211_aes_ccm_decrypt(struct crypto_aead *tfm,
--			  u8 *b_0, u8 *aad, u8 *data,
--			  size_t data_len, u8 *mic)
--{
--	return aead_decrypt(tfm, b_0, aad + 2,
--			    be16_to_cpup((__be16 *)aad),
--			    data, data_len, mic);
--}
--
--static inline void ieee80211_aes_key_free(struct crypto_aead *tfm)
--{
--	return aead_key_free(tfm);
--}
-+struct crypto_cipher *ieee80211_aes_key_setup_encrypt(const u8 key[],
-+						      size_t key_len,
-+						      size_t mic_len);
-+void ieee80211_aes_ccm_encrypt(struct crypto_cipher *tfm, u8 *b_0, u8 *aad,
-+			       u8 *data, size_t data_len, u8 *mic,
-+			       size_t mic_len);
-+int ieee80211_aes_ccm_decrypt(struct crypto_cipher *tfm, u8 *b_0, u8 *aad,
-+			      u8 *data, size_t data_len, u8 *mic,
-+			      size_t mic_len);
-+void ieee80211_aes_key_free(struct crypto_cipher *tfm);
- 
- #endif /* AES_CCM_H */
---- /dev/null
-+++ b/net/mac80211/aes_gcm.c
-@@ -0,0 +1,109 @@
-+/*
-+ * Copyright 2014-2015, Qualcomm Atheros, Inc.
-+ *
-+ * This program is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License version 2 as
-+ * published by the Free Software Foundation.
-+ */
-+
-+#include <linux/kernel.h>
-+#include <linux/types.h>
-+#include <linux/err.h>
-+#include <crypto/aead.h>
-+
-+#include <net/mac80211.h>
-+#include "key.h"
-+#include "aes_gcm.h"
-+
-+int ieee80211_aes_gcm_encrypt(struct crypto_aead *tfm, u8 *j_0, u8 *aad,
-+			      u8 *data, size_t data_len, u8 *mic)
-+{
-+	struct scatterlist sg[3];
-+	struct aead_request *aead_req;
-+	int reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
-+	u8 *__aad;
-+
-+	aead_req = kzalloc(reqsize + GCM_AAD_LEN, GFP_ATOMIC);
-+	if (!aead_req)
-+		return -ENOMEM;
-+
-+	__aad = (u8 *)aead_req + reqsize;
-+	memcpy(__aad, aad, GCM_AAD_LEN);
-+
-+	sg_init_table(sg, 3);
-+	sg_set_buf(&sg[0], &__aad[2], be16_to_cpup((__be16 *)__aad));
-+	sg_set_buf(&sg[1], data, data_len);
-+	sg_set_buf(&sg[2], mic, IEEE80211_GCMP_MIC_LEN);
-+
-+	aead_request_set_tfm(aead_req, tfm);
-+	aead_request_set_crypt(aead_req, sg, sg, data_len, j_0);
-+	aead_request_set_ad(aead_req, sg[0].length);
-+
-+	crypto_aead_encrypt(aead_req);
-+	kzfree(aead_req);
-+	return 0;
-+}
-+
-+int ieee80211_aes_gcm_decrypt(struct crypto_aead *tfm, u8 *j_0, u8 *aad,
-+			      u8 *data, size_t data_len, u8 *mic)
-+{
-+	struct scatterlist sg[3];
-+	struct aead_request *aead_req;
-+	int reqsize = sizeof(*aead_req) + crypto_aead_reqsize(tfm);
-+	u8 *__aad;
-+	int err;
-+
-+	if (data_len == 0)
-+		return -EINVAL;
-+
-+	aead_req = kzalloc(reqsize + GCM_AAD_LEN, GFP_ATOMIC);
-+	if (!aead_req)
-+		return -ENOMEM;
-+
-+	__aad = (u8 *)aead_req + reqsize;
-+	memcpy(__aad, aad, GCM_AAD_LEN);
-+
-+	sg_init_table(sg, 3);
-+	sg_set_buf(&sg[0], &__aad[2], be16_to_cpup((__be16 *)__aad));
-+	sg_set_buf(&sg[1], data, data_len);
-+	sg_set_buf(&sg[2], mic, IEEE80211_GCMP_MIC_LEN);
-+
-+	aead_request_set_tfm(aead_req, tfm);
-+	aead_request_set_crypt(aead_req, sg, sg,
-+			       data_len + IEEE80211_GCMP_MIC_LEN, j_0);
-+	aead_request_set_ad(aead_req, sg[0].length);
-+
-+	err = crypto_aead_decrypt(aead_req);
-+	kzfree(aead_req);
-+
-+	return err;
-+}
-+
-+struct crypto_aead *ieee80211_aes_gcm_key_setup_encrypt(const u8 key[],
-+							size_t key_len)
-+{
-+	struct crypto_aead *tfm;
-+	int err;
-+
-+	tfm = crypto_alloc_aead("gcm(aes)", 0, CRYPTO_ALG_ASYNC);
-+	if (IS_ERR(tfm))
-+		return tfm;
-+
-+	err = crypto_aead_setkey(tfm, key, key_len);
-+	if (err)
-+		goto free_aead;
-+	err = crypto_aead_setauthsize(tfm, IEEE80211_GCMP_MIC_LEN);
-+	if (err)
-+		goto free_aead;
-+
-+	return tfm;
-+
-+free_aead:
-+	crypto_free_aead(tfm);
-+	return ERR_PTR(err);
-+}
-+
-+void ieee80211_aes_gcm_key_free(struct crypto_aead *tfm)
-+{
-+	crypto_free_aead(tfm);
-+}
---- a/net/mac80211/aes_gcm.h
-+++ b/net/mac80211/aes_gcm.h
-@@ -6,38 +6,30 @@
- #ifndef AES_GCM_H
- #define AES_GCM_H
- 
--#include "aead_api.h"
-+#include <linux/crypto.h>
- 
--#define GCM_AAD_LEN	32
--
--static inline int ieee80211_aes_gcm_encrypt(struct crypto_aead *tfm,
--					    u8 *j_0, u8 *aad,  u8 *data,
--					    size_t data_len, u8 *mic)
-+static inline void
-+ieee80211_aes_gcm_encrypt(struct crypto_aead *tfm, u8 *j_0, u8 *aad,
-+			  u8 *data, size_t data_len, u8 *mic)
- {
--	return aead_encrypt(tfm, j_0, aad + 2,
--			    be16_to_cpup((__be16 *)aad),
--			    data, data_len, mic);
- }
- 
--static inline int ieee80211_aes_gcm_decrypt(struct crypto_aead *tfm,
--					    u8 *j_0, u8 *aad, u8 *data,
--					    size_t data_len, u8 *mic)
-+static inline int
-+ieee80211_aes_gcm_decrypt(struct crypto_aead *tfm, u8 *j_0, u8 *aad,
-+			  u8 *data, size_t data_len, u8 *mic)
- {
--	return aead_decrypt(tfm, j_0, aad + 2,
--			    be16_to_cpup((__be16 *)aad),
--			    data, data_len, mic);
-+    return -EOPNOTSUPP;
- }
- 
- static inline struct crypto_aead *
- ieee80211_aes_gcm_key_setup_encrypt(const u8 key[], size_t key_len)
- {
--	return aead_key_setup_encrypt("gcm(aes)", key,
--				      key_len, IEEE80211_GCMP_MIC_LEN);
-+    return NULL;
- }
- 
--static inline void ieee80211_aes_gcm_key_free(struct crypto_aead *tfm)
-+static inline void
-+ieee80211_aes_gcm_key_free(struct crypto_aead *tfm)
- {
--	return aead_key_free(tfm);
- }
- 
- #endif /* AES_GCM_H */
---- a/net/mac80211/wpa.c
-+++ b/net/mac80211/wpa.c
-@@ -312,7 +312,8 @@ ieee80211_crypto_tkip_decrypt(struct iee
- }
- 
- 
--static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *b_0, u8 *aad)
-+static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *b_0, u8 *aad,
-+				u16 data_len)
- {
- 	__le16 mask_fc;
- 	int a4_included, mgmt;
-@@ -342,14 +343,8 @@ static void ccmp_special_blocks(struct s
- 	else
- 		qos_tid = 0;
- 
--	/* In CCM, the initial vectors (IV) used for CTR mode encryption and CBC
--	 * mode authentication are not allowed to collide, yet both are derived
--	 * from this vector b_0. We only set L := 1 here to indicate that the
--	 * data size can be represented in (L+1) bytes. The CCM layer will take
--	 * care of storing the data length in the top (L+1) bytes and setting
--	 * and clearing the other bits as is required to derive the two IVs.
--	 */
--	b_0[0] = 0x1;
-+	/* First block, b_0 */
-+	b_0[0] = 0x59; /* flags: Adata: 1, M: 011, L: 001 */
- 
- 	/* Nonce: Nonce Flags | A2 | PN
- 	 * Nonce Flags: Priority (b0..b3) | Management (b4) | Reserved (b5..b7)
-@@ -357,6 +352,8 @@ static void ccmp_special_blocks(struct s
- 	b_0[1] = qos_tid | (mgmt << 4);
- 	memcpy(&b_0[2], hdr->addr2, ETH_ALEN);
- 	memcpy(&b_0[8], pn, IEEE80211_CCMP_PN_LEN);
-+	/* l(m) */
-+	put_unaligned_be16(data_len, &b_0[14]);
- 
- 	/* AAD (extra authenticate-only data) / masked 802.11 header
- 	 * FC | A1 | A2 | A3 | SC | [A4] | [QC] */
-@@ -413,7 +410,7 @@ static int ccmp_encrypt_skb(struct ieee8
- 	u8 *pos;
- 	u8 pn[6];
- 	u64 pn64;
--	u8 aad[CCM_AAD_LEN];
-+	u8 aad[2 * AES_BLOCK_SIZE];
- 	u8 b_0[AES_BLOCK_SIZE];
- 
- 	if (info->control.hw_key &&
-@@ -468,9 +465,11 @@ static int ccmp_encrypt_skb(struct ieee8
- 		return 0;
- 
- 	pos += IEEE80211_CCMP_HDR_LEN;
--	ccmp_special_blocks(skb, pn, b_0, aad);
--	return ieee80211_aes_ccm_encrypt(key->u.ccmp.tfm, b_0, aad, pos, len,
--					 skb_put(skb, mic_len));
-+	ccmp_special_blocks(skb, pn, b_0, aad, len);
-+	ieee80211_aes_ccm_encrypt(key->u.ccmp.tfm, b_0, aad, pos, len,
-+				  skb_put(skb, mic_len), mic_len);
-+
-+	return 0;
- }
- 
- 
-@@ -543,13 +542,13 @@ ieee80211_crypto_ccmp_decrypt(struct iee
- 			u8 aad[2 * AES_BLOCK_SIZE];
- 			u8 b_0[AES_BLOCK_SIZE];
- 			/* hardware didn't decrypt/verify MIC */
--			ccmp_special_blocks(skb, pn, b_0, aad);
-+			ccmp_special_blocks(skb, pn, b_0, aad, data_len);
- 
- 			if (ieee80211_aes_ccm_decrypt(
- 				    key->u.ccmp.tfm, b_0, aad,
- 				    skb->data + hdrlen + IEEE80211_CCMP_HDR_LEN,
- 				    data_len,
--				    skb->data + skb->len - mic_len))
-+				    skb->data + skb->len - mic_len, mic_len))
- 				return RX_DROP_UNUSABLE;
- 		}
- 
-@@ -646,7 +645,7 @@ static int gcmp_encrypt_skb(struct ieee8
- 	u8 *pos;
- 	u8 pn[6];
- 	u64 pn64;
--	u8 aad[GCM_AAD_LEN];
-+	u8 aad[2 * AES_BLOCK_SIZE];
- 	u8 j_0[AES_BLOCK_SIZE];
- 
- 	if (info->control.hw_key &&
-@@ -703,8 +702,10 @@ static int gcmp_encrypt_skb(struct ieee8
- 
- 	pos += IEEE80211_GCMP_HDR_LEN;
- 	gcmp_special_blocks(skb, pn, j_0, aad);
--	return ieee80211_aes_gcm_encrypt(key->u.gcmp.tfm, j_0, aad, pos, len,
--					 skb_put(skb, IEEE80211_GCMP_MIC_LEN));
-+	ieee80211_aes_gcm_encrypt(key->u.gcmp.tfm, j_0, aad, pos, len,
-+				  skb_put(skb, IEEE80211_GCMP_MIC_LEN));
-+
-+	return 0;
- }
- 
- ieee80211_tx_result
-@@ -1133,9 +1134,9 @@ ieee80211_crypto_aes_gmac_encrypt(struct
- 	struct ieee80211_key *key = tx->key;
- 	struct ieee80211_mmie_16 *mmie;
- 	struct ieee80211_hdr *hdr;
--	u8 aad[GMAC_AAD_LEN];
-+	u8 aad[20];
- 	u64 pn64;
--	u8 nonce[GMAC_NONCE_LEN];
-+	u8 nonce[12];
- 
- 	if (WARN_ON(skb_queue_len(&tx->skbs) != 1))
- 		return TX_DROP;
-@@ -1181,7 +1182,7 @@ ieee80211_crypto_aes_gmac_decrypt(struct
- 	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
- 	struct ieee80211_key *key = rx->key;
- 	struct ieee80211_mmie_16 *mmie;
--	u8 aad[GMAC_AAD_LEN], *mic, ipn[6], nonce[GMAC_NONCE_LEN];
-+	u8 aad[20], *mic, ipn[6], nonce[12];
- 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
- 
- 	if (!ieee80211_is_mgmt(hdr->frame_control))
---- /dev/null
-+++ b/net/mac80211/aes_ccm.c
-@@ -0,0 +1,144 @@
-+/*
-+ * Copyright 2003-2004, Instant802 Networks, Inc.
-+ * Copyright 2005-2006, Devicescape Software, Inc.
-+ *
-+ * Rewrite: Copyright (C) 2013 Linaro Ltd <ard.biesheuvel@linaro.org>
-+ *
-+ * This program is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License version 2 as
-+ * published by the Free Software Foundation.
-+ */
-+
-+#include <linux/kernel.h>
-+#include <linux/types.h>
-+#include <linux/err.h>
-+#include <crypto/aead.h>
-+#include <crypto/aes.h>
-+
-+#include <net/mac80211.h>
-+#include "key.h"
-+#include "aes_ccm.h"
-+
-+static void aes_ccm_prepare(struct crypto_cipher *tfm, u8 *b_0, u8 *aad, u8 *s_0,
-+			    u8 *a, u8 *b)
-+{
-+	int i;
-+
-+	crypto_cipher_encrypt_one(tfm, b, b_0);
-+
-+	/* Extra Authenticate-only data (always two AES blocks) */
-+	for (i = 0; i < AES_BLOCK_SIZE; i++)
-+		aad[i] ^= b[i];
-+	crypto_cipher_encrypt_one(tfm, b, aad);
-+
-+	aad += AES_BLOCK_SIZE;
-+
-+	for (i = 0; i < AES_BLOCK_SIZE; i++)
-+		aad[i] ^= b[i];
-+	crypto_cipher_encrypt_one(tfm, a, aad);
-+
-+	/* Mask out bits from auth-only-b_0 */
-+	b_0[0] &= 0x07;
-+
-+	/* S_0 is used to encrypt T (= MIC) */
-+	b_0[14] = 0;
-+	b_0[15] = 0;
-+	crypto_cipher_encrypt_one(tfm, s_0, b_0);
-+}
-+
-+
-+void ieee80211_aes_ccm_encrypt(struct crypto_cipher *tfm, u8 *b_0, u8 *aad,
-+			       u8 *data, size_t data_len, u8 *mic,
-+			       size_t mic_len)
-+{
-+	int i, j, last_len, num_blocks;
-+	u8 b[AES_BLOCK_SIZE];
-+	u8 s_0[AES_BLOCK_SIZE];
-+	u8 e[AES_BLOCK_SIZE];
-+	u8 *pos, *cpos;
-+
-+	num_blocks = DIV_ROUND_UP(data_len, AES_BLOCK_SIZE);
-+	last_len = data_len % AES_BLOCK_SIZE;
-+	aes_ccm_prepare(tfm, b_0, aad, s_0, b, b);
-+
-+	/* Process payload blocks */
-+	pos = data;
-+	cpos = data;
-+	for (j = 1; j <= num_blocks; j++) {
-+		int blen = (j == num_blocks && last_len) ?
-+			last_len : AES_BLOCK_SIZE;
-+
-+		/* Authentication followed by encryption */
-+		for (i = 0; i < blen; i++)
-+			b[i] ^= pos[i];
-+		crypto_cipher_encrypt_one(tfm, b, b);
-+
-+		b_0[14] = (j >> 8) & 0xff;
-+		b_0[15] = j & 0xff;
-+		crypto_cipher_encrypt_one(tfm, e, b_0);
-+		for (i = 0; i < blen; i++)
-+			*cpos++ = *pos++ ^ e[i];
-+	}
-+
-+	for (i = 0; i < mic_len; i++)
-+		mic[i] = b[i] ^ s_0[i];
-+}
-+
-+int ieee80211_aes_ccm_decrypt(struct crypto_cipher *tfm, u8 *b_0, u8 *aad,
-+			      u8 *data, size_t data_len, u8 *mic,
-+			      size_t mic_len)
-+{
-+	int i, j, last_len, num_blocks;
-+	u8 *pos, *cpos;
-+	u8 a[AES_BLOCK_SIZE];
-+	u8 b[AES_BLOCK_SIZE];
-+	u8 s_0[AES_BLOCK_SIZE];
-+
-+	num_blocks = DIV_ROUND_UP(data_len, AES_BLOCK_SIZE);
-+	last_len = data_len % AES_BLOCK_SIZE;
-+	aes_ccm_prepare(tfm, b_0, aad, s_0, a, b);
-+
-+	/* Process payload blocks */
-+	cpos = data;
-+	pos = data;
-+	for (j = 1; j <= num_blocks; j++) {
-+		int blen = (j == num_blocks && last_len) ?
-+			last_len : AES_BLOCK_SIZE;
-+
-+		/* Decryption followed by authentication */
-+		b_0[14] = (j >> 8) & 0xff;
-+		b_0[15] = j & 0xff;
-+		crypto_cipher_encrypt_one(tfm, b, b_0);
-+		for (i = 0; i < blen; i++) {
-+			*pos = *cpos++ ^ b[i];
-+			a[i] ^= *pos++;
-+		}
-+		crypto_cipher_encrypt_one(tfm, a, a);
-+	}
-+
-+	for (i = 0; i < mic_len; i++) {
-+		if ((mic[i] ^ s_0[i]) != a[i])
-+			return -1;
-+	}
-+
-+	return 0;
-+}
-+
-+struct crypto_cipher *ieee80211_aes_key_setup_encrypt(const u8 key[],
-+						      size_t key_len,
-+						      size_t mic_len)
-+{
-+	struct crypto_cipher *tfm;
-+
-+	tfm = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC);
-+	if (!IS_ERR(tfm))
-+		crypto_cipher_setkey(tfm, key, key_len);
-+
-+	return tfm;
-+}
-+
-+
-+void ieee80211_aes_key_free(struct crypto_cipher *tfm)
-+{
-+	crypto_free_cipher(tfm);
-+}
---- a/net/mac80211/Kconfig
-+++ b/net/mac80211/Kconfig
-@@ -6,8 +6,6 @@ config MAC80211
- 	depends on CRYPTO
- 	select BPAUTO_CRYPTO_LIB_ARC4
- 	depends on CRYPTO_AES
--	depends on CRYPTO_CCM
--	depends on CRYPTO_GCM
- 	depends on CRYPTO_CMAC
- 	depends on CRC32
- 	help
---- a/net/mac80211/aes_gmac.h
-+++ b/net/mac80211/aes_gmac.h
-@@ -12,10 +12,22 @@
- #define GMAC_MIC_LEN	16
- #define GMAC_NONCE_LEN	12
- 
--struct crypto_aead *ieee80211_aes_gmac_key_setup(const u8 key[],
--						 size_t key_len);
--int ieee80211_aes_gmac(struct crypto_aead *tfm, const u8 *aad, u8 *nonce,
--		       const u8 *data, size_t data_len, u8 *mic);
--void ieee80211_aes_gmac_key_free(struct crypto_aead *tfm);
-+static inline struct crypto_aead *
-+ieee80211_aes_gmac_key_setup(const u8 key[], size_t key_len)
-+{
-+	return NULL;
-+}
-+
-+static inline int
-+ieee80211_aes_gmac(struct crypto_aead *tfm, const u8 *aad, u8 *nonce,
-+		   const u8 *data, size_t data_len, u8 *mic)
-+{
-+	return -EOPNOTSUPP;
-+}
-+
-+static inline void
-+ieee80211_aes_gmac_key_free(struct crypto_aead *tfm)
-+{
-+}
- 
- #endif /* AES_GMAC_H */
---- a/net/mac80211/key.h
-+++ b/net/mac80211/key.h
-@@ -89,7 +89,7 @@ struct ieee80211_key {
- 			 * Management frames.
- 			 */
- 			u8 rx_pn[IEEE80211_NUM_TIDS + 1][IEEE80211_CCMP_PN_LEN];
--			struct crypto_aead *tfm;
-+			struct crypto_cipher *tfm;
- 			u32 replays; /* dot11RSNAStatsCCMPReplays */
- 		} ccmp;
- 		struct {
diff --git a/package/kernel/mac80211/patches/subsys/130-disable-fils.patch b/package/kernel/mac80211/patches/subsys/130-disable-fils.patch
deleted file mode 100644
index 9c6e971f9da5a84fc656ce1e2d80e333bb377a52..0000000000000000000000000000000000000000
--- a/package/kernel/mac80211/patches/subsys/130-disable-fils.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-Disable FILS support, since it pulls in crypto hash support
-
---- a/net/mac80211/fils_aead.h
-+++ b/net/mac80211/fils_aead.h
-@@ -7,7 +7,7 @@
- #ifndef FILS_AEAD_H
- #define FILS_AEAD_H
- 
--#if LINUX_VERSION_IS_GEQ(4,3,0)
-+#if 0 /* LINUX_VERSION_IS_GEQ(4,3,0) */
- int fils_encrypt_assoc_req(struct sk_buff *skb,
- 			   struct ieee80211_mgd_assoc_data *assoc_data);
- int fils_decrypt_assoc_resp(struct ieee80211_sub_if_data *sdata,
---- a/net/mac80211/fils_aead.c
-+++ b/net/mac80211/fils_aead.c
-@@ -1,4 +1,4 @@
--#if LINUX_VERSION_IS_GEQ(4,3,0)
-+#if 0 /* LINUX_VERSION_IS_GEQ(4,3,0) */
- // SPDX-License-Identifier: GPL-2.0-only
- /*
-  * FILS AEAD for (Re)Association Request/Response frames
---- a/net/mac80211/main.c
-+++ b/net/mac80211/main.c
-@@ -591,7 +591,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_
- 			   NL80211_FEATURE_MAC_ON_CREATE |
- 			   NL80211_FEATURE_USERSPACE_MPM |
- 			   NL80211_FEATURE_FULL_AP_CLIENT_STATE;
--#if LINUX_VERSION_IS_GEQ(4,3,0)
-+#if 0 /* LINUX_VERSION_IS_GEQ(4,3,0) */
- 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_FILS_STA);
- #endif
- 	wiphy_ext_feature_set(wiphy,
diff --git a/package/kernel/mac80211/patches/subsys/131-Revert-mac80211-aes-cmac-switch-to-shash-CMAC-driver.patch b/package/kernel/mac80211/patches/subsys/131-Revert-mac80211-aes-cmac-switch-to-shash-CMAC-driver.patch
deleted file mode 100644
index c3bf7ccc7abda2a6f48a8dcc75208ce8e08938a6..0000000000000000000000000000000000000000
--- a/package/kernel/mac80211/patches/subsys/131-Revert-mac80211-aes-cmac-switch-to-shash-CMAC-driver.patch
+++ /dev/null
@@ -1,230 +0,0 @@
-From: Felix Fietkau <nbd@nbd.name>
-Date: Sat, 7 Oct 2017 09:37:28 +0200
-Subject: [PATCH] Revert "mac80211: aes-cmac: switch to shash CMAC
- driver"
-
-This reverts commit 26717828b75dd5c46e97f7f4a9b937d038bb2852.
-Reduces mac80211 dependencies for LEDE
-
-Signed-off-by: Felix Fietkau <nbd@nbd.name>
----
-
---- a/net/mac80211/aes_cmac.c
-+++ b/net/mac80211/aes_cmac.c
-@@ -19,67 +19,151 @@
- #define CMAC_TLEN_256 16 /* CMAC TLen = 128 bits (16 octets) */
- #define AAD_LEN 20
- 
--static const u8 zero[CMAC_TLEN_256];
- 
--void ieee80211_aes_cmac(struct crypto_shash *tfm, const u8 *aad,
-+void gf_mulx(u8 *pad)
-+{
-+	int i, carry;
-+
-+	carry = pad[0] & 0x80;
-+	for (i = 0; i < AES_BLOCK_SIZE - 1; i++)
-+		pad[i] = (pad[i] << 1) | (pad[i + 1] >> 7);
-+	pad[AES_BLOCK_SIZE - 1] <<= 1;
-+	if (carry)
-+		pad[AES_BLOCK_SIZE - 1] ^= 0x87;
-+}
-+
-+void aes_cmac_vector(struct crypto_cipher *tfm, size_t num_elem,
-+		     const u8 *addr[], const size_t *len, u8 *mac,
-+		     size_t mac_len)
-+{
-+	u8 cbc[AES_BLOCK_SIZE], pad[AES_BLOCK_SIZE];
-+	const u8 *pos, *end;
-+	size_t i, e, left, total_len;
-+
-+	memset(cbc, 0, AES_BLOCK_SIZE);
-+
-+	total_len = 0;
-+	for (e = 0; e < num_elem; e++)
-+		total_len += len[e];
-+	left = total_len;
-+
-+	e = 0;
-+	pos = addr[0];
-+	end = pos + len[0];
-+
-+	while (left >= AES_BLOCK_SIZE) {
-+		for (i = 0; i < AES_BLOCK_SIZE; i++) {
-+			cbc[i] ^= *pos++;
-+			if (pos >= end) {
-+				e++;
-+				pos = addr[e];
-+				end = pos + len[e];
-+			}
-+		}
-+		if (left > AES_BLOCK_SIZE)
-+			crypto_cipher_encrypt_one(tfm, cbc, cbc);
-+		left -= AES_BLOCK_SIZE;
-+	}
-+
-+	memset(pad, 0, AES_BLOCK_SIZE);
-+	crypto_cipher_encrypt_one(tfm, pad, pad);
-+	gf_mulx(pad);
-+
-+	if (left || total_len == 0) {
-+		for (i = 0; i < left; i++) {
-+			cbc[i] ^= *pos++;
-+			if (pos >= end) {
-+				e++;
-+				pos = addr[e];
-+				end = pos + len[e];
-+			}
-+		}
-+		cbc[left] ^= 0x80;
-+		gf_mulx(pad);
-+	}
-+
-+	for (i = 0; i < AES_BLOCK_SIZE; i++)
-+		pad[i] ^= cbc[i];
-+	crypto_cipher_encrypt_one(tfm, pad, pad);
-+	memcpy(mac, pad, mac_len);
-+}
-+
-+
-+void ieee80211_aes_cmac(struct crypto_cipher *tfm, const u8 *aad,
- 			const u8 *data, size_t data_len, u8 *mic)
- {
--	SHASH_DESC_ON_STACK(desc, tfm);
--	u8 out[AES_BLOCK_SIZE];
-+	const u8 *addr[4];
-+	size_t len[4];
-+	u8 zero[CMAC_TLEN];
- 	const __le16 *fc;
- 
--	desc->tfm = tfm;
--
--	crypto_shash_init(desc);
--	crypto_shash_update(desc, aad, AAD_LEN);
-+	memset(zero, 0, CMAC_TLEN);
-+	addr[0] = aad;
-+	len[0] = AAD_LEN;
- 	fc = (const __le16 *)aad;
- 	if (ieee80211_is_beacon(*fc)) {
- 		/* mask Timestamp field to zero */
--		crypto_shash_update(desc, zero, 8);
--		crypto_shash_update(desc, data + 8, data_len - 8 - CMAC_TLEN);
-+		addr[1] = zero;
-+		len[1] = 8;
-+		addr[2] = data + 8;
-+		len[2] = data_len - 8 - CMAC_TLEN;
-+		addr[3] = zero;
-+		len[3] = CMAC_TLEN;
-+		aes_cmac_vector(tfm, 4, addr, len, mic, CMAC_TLEN);
- 	} else {
--		crypto_shash_update(desc, data, data_len - CMAC_TLEN);
-+		addr[1] = data;
-+		len[1] = data_len - CMAC_TLEN;
-+		addr[2] = zero;
-+		len[2] = CMAC_TLEN;
-+		aes_cmac_vector(tfm, 3, addr, len, mic, CMAC_TLEN);
- 	}
--	crypto_shash_finup(desc, zero, CMAC_TLEN, out);
--
--	memcpy(mic, out, CMAC_TLEN);
- }
- 
--void ieee80211_aes_cmac_256(struct crypto_shash *tfm, const u8 *aad,
-+void ieee80211_aes_cmac_256(struct crypto_cipher *tfm, const u8 *aad,
- 			    const u8 *data, size_t data_len, u8 *mic)
- {
--	SHASH_DESC_ON_STACK(desc, tfm);
-+	const u8 *addr[4];
-+	size_t len[4];
-+	u8 zero[CMAC_TLEN_256];
- 	const __le16 *fc;
- 
--	desc->tfm = tfm;
--
--	crypto_shash_init(desc);
--	crypto_shash_update(desc, aad, AAD_LEN);
-+	memset(zero, 0, CMAC_TLEN_256);
-+	addr[0] = aad;
-+	len[0] = AAD_LEN;
-+	addr[1] = data;
- 	fc = (const __le16 *)aad;
- 	if (ieee80211_is_beacon(*fc)) {
- 		/* mask Timestamp field to zero */
--		crypto_shash_update(desc, zero, 8);
--		crypto_shash_update(desc, data + 8,
--				    data_len - 8 - CMAC_TLEN_256);
-+		addr[1] = zero;
-+		len[1] = 8;
-+		addr[2] = data + 8;
-+		len[2] = data_len - 8 - CMAC_TLEN_256;
-+		addr[3] = zero;
-+		len[3] = CMAC_TLEN_256;
-+		aes_cmac_vector(tfm, 4, addr, len, mic, CMAC_TLEN_256);
- 	} else {
--		crypto_shash_update(desc, data, data_len - CMAC_TLEN_256);
-+		addr[1] = data;
-+		len[1] = data_len - CMAC_TLEN_256;
-+		addr[2] = zero;
-+		len[2] = CMAC_TLEN_256;
-+		aes_cmac_vector(tfm, 3, addr, len, mic, CMAC_TLEN_256);
- 	}
--	crypto_shash_finup(desc, zero, CMAC_TLEN_256, mic);
- }
- 
--struct crypto_shash *ieee80211_aes_cmac_key_setup(const u8 key[],
--						  size_t key_len)
-+struct crypto_cipher *ieee80211_aes_cmac_key_setup(const u8 key[],
-+						   size_t key_len)
- {
--	struct crypto_shash *tfm;
-+	struct crypto_cipher *tfm;
- 
--	tfm = crypto_alloc_shash("cmac(aes)", 0, 0);
-+	tfm = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC);
- 	if (!IS_ERR(tfm))
--		crypto_shash_setkey(tfm, key, key_len);
-+		crypto_cipher_setkey(tfm, key, key_len);
- 
- 	return tfm;
- }
- 
--void ieee80211_aes_cmac_key_free(struct crypto_shash *tfm)
-+
-+void ieee80211_aes_cmac_key_free(struct crypto_cipher *tfm)
- {
--	crypto_free_shash(tfm);
-+	crypto_free_cipher(tfm);
- }
---- a/net/mac80211/aes_cmac.h
-+++ b/net/mac80211/aes_cmac.h
-@@ -7,14 +7,13 @@
- #define AES_CMAC_H
- 
- #include <linux/crypto.h>
--#include <crypto/hash.h>
- 
--struct crypto_shash *ieee80211_aes_cmac_key_setup(const u8 key[],
--						  size_t key_len);
--void ieee80211_aes_cmac(struct crypto_shash *tfm, const u8 *aad,
-+struct crypto_cipher *ieee80211_aes_cmac_key_setup(const u8 key[],
-+						   size_t key_len);
-+void ieee80211_aes_cmac(struct crypto_cipher *tfm, const u8 *aad,
- 			const u8 *data, size_t data_len, u8 *mic);
--void ieee80211_aes_cmac_256(struct crypto_shash *tfm, const u8 *aad,
-+void ieee80211_aes_cmac_256(struct crypto_cipher *tfm, const u8 *aad,
- 			    const u8 *data, size_t data_len, u8 *mic);
--void ieee80211_aes_cmac_key_free(struct crypto_shash *tfm);
-+void ieee80211_aes_cmac_key_free(struct crypto_cipher *tfm);
- 
- #endif /* AES_CMAC_H */
---- a/net/mac80211/key.h
-+++ b/net/mac80211/key.h
-@@ -94,7 +94,7 @@ struct ieee80211_key {
- 		} ccmp;
- 		struct {
- 			u8 rx_pn[IEEE80211_CMAC_PN_LEN];
--			struct crypto_shash *tfm;
-+			struct crypto_cipher *tfm;
- 			u32 replays; /* dot11RSNAStatsCMACReplays */
- 			u32 icverrors; /* dot11RSNAStatsCMACICVErrors */
- 		} aes_cmac;
diff --git a/package/kernel/mac80211/patches/subsys/132-mac80211-remove-cmac-dependency.patch b/package/kernel/mac80211/patches/subsys/132-mac80211-remove-cmac-dependency.patch
deleted file mode 100644
index df67d2f1019ea34c6210692e8d054d16bccd40b6..0000000000000000000000000000000000000000
--- a/package/kernel/mac80211/patches/subsys/132-mac80211-remove-cmac-dependency.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/mac80211/Kconfig
-+++ b/net/mac80211/Kconfig
-@@ -6,7 +6,6 @@ config MAC80211
- 	depends on CRYPTO
- 	select BPAUTO_CRYPTO_LIB_ARC4
- 	depends on CRYPTO_AES
--	depends on CRYPTO_CMAC
- 	depends on CRC32
- 	help
- 	  This option enables the hardware independent IEEE 802.11
diff --git a/package/kernel/mac80211/patches/subsys/339-mac80211-remove-legacy-minstrel-rate-control.patch b/package/kernel/mac80211/patches/subsys/339-mac80211-remove-legacy-minstrel-rate-control.patch
index 96ee595ac1a816e45a6425900f6ecea78db857f8..1cab2eb1947dddf3016d5c1bb04d6bda75348151 100644
--- a/package/kernel/mac80211/patches/subsys/339-mac80211-remove-legacy-minstrel-rate-control.patch
+++ b/package/kernel/mac80211/patches/subsys/339-mac80211-remove-legacy-minstrel-rate-control.patch
@@ -12,7 +12,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 
 --- a/net/mac80211/Makefile
 +++ b/net/mac80211/Makefile
-@@ -55,11 +55,9 @@ mac80211-$(CONFIG_PM) += pm.o
+@@ -56,11 +56,9 @@ mac80211-$(CONFIG_PM) += pm.o
  CFLAGS_trace.o := -I$(src)
  
  rc80211_minstrel-y := \
diff --git a/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch b/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch
new file mode 100644
index 0000000000000000000000000000000000000000..ef57234bead8bf7d638218eb756fe9d2b252c9b8
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch
@@ -0,0 +1,888 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 1 Feb 2021 10:47:58 +0100
+Subject: [PATCH] mac80211: minstrel_ht: add debugfs monitoring/controlling
+ API
+
+This allows user space to monitor tx status and take over rate control
+functionality.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ create mode 100644 net/mac80211/rc80211_minstrel_ht_api.c
+
+--- a/local-symbols
++++ b/local-symbols
+@@ -49,6 +49,7 @@ LIB80211_DEBUG=
+ MAC80211=
+ MAC80211_HAS_RC=
+ MAC80211_RC_MINSTREL=
++MAC80211_RC_MINSTREL_DEBUGFS_API=
+ MAC80211_RC_DEFAULT_MINSTREL=
+ MAC80211_RC_DEFAULT=
+ MAC80211_MESH=
+--- a/net/mac80211/Kconfig
++++ b/net/mac80211/Kconfig
+@@ -29,6 +29,15 @@ config MAC80211_RC_MINSTREL
+ 	help
+ 	  This option enables the 'minstrel' TX rate control algorithm
+ 
++config MAC80211_RC_MINSTREL_DEBUGFS_API
++	bool "Minstrel debugfs userspace control API"
++	depends on MAC80211_RC_MINSTREL
++	depends on MAC80211_DEBUGFS
++	select RELAY
++	help
++	  This option creates debugfs files that allow user space to observe
++	  and/or control minstrel rate selection behavior
++
+ choice
+ 	prompt "Default rate control algorithm"
+ 	depends on MAC80211_HAS_RC
+--- a/net/mac80211/Makefile
++++ b/net/mac80211/Makefile
+@@ -61,6 +61,9 @@ rc80211_minstrel-y := \
+ rc80211_minstrel-$(CPTCFG_MAC80211_DEBUGFS) += \
+ 	rc80211_minstrel_ht_debugfs.o
+ 
++rc80211_minstrel-$(CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API) += \
++	rc80211_minstrel_ht_api.o
++
+ mac80211-$(CPTCFG_MAC80211_RC_MINSTREL) += $(rc80211_minstrel-y)
+ 
+ ccflags-y += -DDEBUG
+--- a/net/mac80211/rc80211_minstrel_ht.c
++++ b/net/mac80211/rc80211_minstrel_ht.c
+@@ -276,7 +276,8 @@ static const u8 minstrel_sample_seq[] =
+ };
+ 
+ static void
+-minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi);
++minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
++			 bool force);
+ 
+ /*
+  * Some VHT MCSes are invalid (when Ndbps / Nes is not an integer)
+@@ -346,7 +347,7 @@ minstrel_vht_get_group_idx(struct ieee80
+ 
+ static struct minstrel_rate_stats *
+ minstrel_ht_get_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
+-		      struct ieee80211_tx_rate *rate)
++		      struct ieee80211_tx_rate *rate, u16 *dest_idx)
+ {
+ 	int group, idx;
+ 
+@@ -381,6 +382,7 @@ minstrel_ht_get_stats(struct minstrel_pr
+ 
+ 	idx = 0;
+ out:
++	*dest_idx = MI_RATE(group, idx);
+ 	return &mi->groups[group].rates[idx];
+ }
+ 
+@@ -1024,6 +1026,8 @@ minstrel_ht_update_stats(struct minstrel
+ 			tp_rate = tmp_legacy_tp_rate;
+ 
+ 		for (i = MCS_GROUP_RATES - 1; i >= 0; i--) {
++			bool changed;
++
+ 			if (!(mi->supported[group] & BIT(i)))
+ 				continue;
+ 
+@@ -1031,7 +1035,11 @@ minstrel_ht_update_stats(struct minstrel
+ 
+ 			mrs = &mg->rates[i];
+ 			mrs->retry_updated = false;
++			changed = mrs->attempts > 0;
+ 			minstrel_ht_calc_rate_stats(mp, mrs);
++			if (changed)
++				minstrel_ht_report_rate_update(mp, mi, index,
++							       mrs);
+ 
+ 			if (mrs->att_hist)
+ 				last_prob = max(last_prob, mrs->prob_avg);
+@@ -1080,7 +1088,8 @@ minstrel_ht_update_stats(struct minstrel
+ 
+ 	mi->max_prob_rate = tmp_max_prob_rate;
+ 
+-	minstrel_ht_refill_sample_rates(mi);
++	if (!minstrel_ht_manual_mode(mp))
++		minstrel_ht_refill_sample_rates(mi);
+ 
+ #ifdef CPTCFG_MAC80211_DEBUGFS
+ 	/* use fixed index if set */
+@@ -1177,6 +1186,7 @@ minstrel_ht_tx_status(void *priv, struct
+ 	struct minstrel_priv *mp = priv;
+ 	u32 update_interval = mp->update_interval;
+ 	bool last, update = false;
++	u16 rate_list[IEEE80211_TX_MAX_RATES] = {};
+ 	int i;
+ 
+ 	/* This packet was aggregated but doesn't carry status info */
+@@ -1208,13 +1218,15 @@ minstrel_ht_tx_status(void *priv, struct
+ 		last = (i == IEEE80211_TX_MAX_RATES - 1) ||
+ 		       !minstrel_ht_txstat_valid(mp, mi, &ar[i + 1]);
+ 
+-		rate = minstrel_ht_get_stats(mp, mi, &ar[i]);
++		rate = minstrel_ht_get_stats(mp, mi, &ar[i], &rate_list[i]);
+ 		if (last)
+ 			rate->success += info->status.ampdu_ack_len;
+ 
+ 		rate->attempts += ar[i].count * info->status.ampdu_len;
+ 	}
+ 
++	minstrel_ht_report_tx_status(mp, mi, info, rate_list, i);
++
+ 	if (mp->hw->max_rates > 1) {
+ 		/*
+ 		 * check for sudden death of spatial multiplexing,
+@@ -1236,7 +1248,7 @@ minstrel_ht_tx_status(void *priv, struct
+ 	}
+ 
+ 	if (update)
+-		minstrel_ht_update_rates(mp, mi);
++		minstrel_ht_update_rates(mp, mi, false);
+ }
+ 
+ static void
+@@ -1299,7 +1311,7 @@ minstrel_calc_retransmit(struct minstrel
+ }
+ 
+ 
+-static void
++void
+ minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
+                      struct ieee80211_sta_rates *ratetbl, int offset, int index)
+ {
+@@ -1408,11 +1420,15 @@ minstrel_ht_get_max_amsdu_len(struct min
+ }
+ 
+ static void
+-minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
++minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
++			 bool force)
+ {
+ 	struct ieee80211_sta_rates *rates;
+ 	int i = 0;
+ 
++	if (minstrel_ht_manual_mode(mp) && !force)
++		return;
++
+ 	rates = kzalloc(sizeof(*rates), GFP_ATOMIC);
+ 	if (!rates)
+ 		return;
+@@ -1439,7 +1455,7 @@ minstrel_ht_get_sample_rate(struct minst
+ {
+ 	u8 seq;
+ 
+-	if (mp->hw->max_rates > 1) {
++	if (mp->hw->max_rates > 1 && !minstrel_ht_manual_mode(mp)) {
+ 		seq = mi->sample_seq;
+ 		mi->sample_seq = (seq + 1) % ARRAY_SIZE(minstrel_sample_seq);
+ 		seq = minstrel_sample_seq[seq];
+@@ -1689,7 +1705,9 @@ minstrel_ht_update_caps(void *priv, stru
+ 
+ 	/* create an initial rate table with the lowest supported rates */
+ 	minstrel_ht_update_stats(mp, mi);
+-	minstrel_ht_update_rates(mp, mi);
++	minstrel_ht_update_rates(mp, mi, true);
++
++	minstrel_ht_sta_update(mp, mi);
+ }
+ 
+ static void
+@@ -1725,12 +1743,18 @@ minstrel_ht_alloc_sta(void *priv, struct
+ 			max_rates = sband->n_bitrates;
+ 	}
+ 
+-	return kzalloc(sizeof(*mi), gfp);
++	mi = kzalloc(sizeof(*mi), gfp);
++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API
++	INIT_LIST_HEAD(&mi->list);
++#endif
++
++	return mi;
+ }
+ 
+ static void
+ minstrel_ht_free_sta(void *priv, struct ieee80211_sta *sta, void *priv_sta)
+ {
++	minstrel_ht_sta_remove(priv, priv_sta);
+ 	kfree(priv_sta);
+ }
+ 
+@@ -1841,12 +1865,14 @@ static void minstrel_ht_add_debugfs(stru
+ 	mp->fixed_rate_idx = (u32) -1;
+ 	debugfs_create_u32("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir,
+ 			   &mp->fixed_rate_idx);
++	minstrel_ht_add_debugfs_api(hw, priv, debugfsdir);
+ }
+ #endif
+ 
+ static void
+ minstrel_ht_free(void *priv)
+ {
++	minstrel_ht_remove_debugfs_api(priv);
+ 	kfree(priv);
+ }
+ 
+--- a/net/mac80211/rc80211_minstrel_ht.h
++++ b/net/mac80211/rc80211_minstrel_ht.h
+@@ -72,6 +72,10 @@
+ #define MINSTREL_SAMPLE_RATES		5 /* rates per sample type */
+ #define MINSTREL_SAMPLE_INTERVAL	(HZ / 50)
+ 
++#define MINSTREL_MONITOR_STA		BIT(0)
++#define MINSTREL_MONITOR_TXS		BIT(1)
++#define MINSTREL_MONITOR_STATS		BIT(2)
++
+ struct minstrel_priv {
+ 	struct ieee80211_hw *hw;
+ 	bool has_mrr;
+@@ -93,6 +97,13 @@ struct minstrel_priv {
+ 	 */
+ 	u32 fixed_rate_idx;
+ #endif
++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API
++	struct rchan *relay_ev;
++	struct list_head stations;
++	spinlock_t lock;
++	u8 monitor;
++	bool manual;
++#endif
+ };
+ 
+ 
+@@ -153,6 +164,9 @@ struct minstrel_sample_category {
+ };
+ 
+ struct minstrel_ht_sta {
++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API
++	struct list_head list;
++#endif
+ 	struct ieee80211_sta *sta;
+ 
+ 	/* ampdu length (average, per sampling interval) */
+@@ -197,6 +211,80 @@ struct minstrel_ht_sta {
+ };
+ 
+ void minstrel_ht_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir);
++
++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API
++void minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi);
++void minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi);
++void __minstrel_ht_report_tx_status(struct minstrel_priv *mp,
++				    struct minstrel_ht_sta *mi,
++				    struct ieee80211_tx_info *info,
++				    u16 *rate_list, int n_rates);
++void __minstrel_ht_report_rate_update(struct minstrel_priv *mp,
++				      struct minstrel_ht_sta *mi, u16 rate,
++				      struct minstrel_rate_stats *mrs);
++void minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv,
++				 struct dentry *dir);
++void minstrel_ht_remove_debugfs_api(void *priv);
++#else
++static inline void
++minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
++{
++}
++static inline void
++minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
++{
++}
++static inline void
++minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv,
++			    struct dentry *dir)
++{
++}
++static inline void
++minstrel_ht_remove_debugfs_api(void *priv)
++{
++}
++#endif
++
++static inline void
++minstrel_ht_report_tx_status(struct minstrel_priv *mp,
++			     struct minstrel_ht_sta *mi,
++			     struct ieee80211_tx_info *info,
++			     u16 *rate_list, int n_rates)
++{
++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API
++	if (!(mp->monitor & MINSTREL_MONITOR_TXS))
++		return;
++
++	__minstrel_ht_report_tx_status(mp, mi, info, rate_list, n_rates);
++#endif
++}
++
++static inline void
++minstrel_ht_report_rate_update(struct minstrel_priv *mp,
++			       struct minstrel_ht_sta *mi, u16 rate,
++			       struct minstrel_rate_stats *mrs)
++{
++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API
++	if (!(mp->monitor & MINSTREL_MONITOR_STATS))
++		return;
++
++	__minstrel_ht_report_rate_update(mp, mi, rate, mrs);
++#endif
++}
++
++static inline bool
++minstrel_ht_manual_mode(struct minstrel_priv *mp)
++{
++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API
++	return mp->manual;
++#else
++	return false;
++#endif
++}
++
++void minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
++						  struct ieee80211_sta_rates *ratetbl, int offset,
++						  int index);
+ int minstrel_ht_get_tp_avg(struct minstrel_ht_sta *mi, int group, int rate,
+ 			   int prob_avg);
+ 
+--- /dev/null
++++ b/net/mac80211/rc80211_minstrel_ht_api.c
+@@ -0,0 +1,540 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
++ */
++#include <linux/kernel.h>
++#include <linux/debugfs.h>
++#include <linux/relay.h>
++#include <net/mac80211.h>
++#include "rc80211_minstrel_ht.h"
++
++enum sta_cmd {
++	STA_CMD_PROBE,
++	STA_CMD_RATES,
++};
++
++static void
++minstrel_ht_print_rate_durations(struct seq_file *s, int group)
++{
++	const struct mcs_group *g = &minstrel_mcs_groups[group];
++	int n_rates;
++	int i;
++
++	if (g->flags & IEEE80211_TX_RC_VHT_MCS)
++		n_rates = 10;
++	else
++		n_rates = 8;
++
++	seq_printf(s, "%x", g->duration[0] << g->shift);
++	for (i = 1; i < n_rates; i++)
++		seq_printf(s, ",%x", g->duration[i] << g->shift);
++}
++
++static int
++minstrel_ht_read_api_info(struct seq_file *s, void *data)
++{
++	int i;
++
++	seq_printf(s, "#group;index;offset;type;nss;bw;gi;airtime\n");
++	seq_printf(s, "#sta;action;macaddr;overhead_mcs;overhead_legacy;supported\n");
++	seq_printf(s, "#txs;macaddr;num_frames;num_acked;probe;rates;counts\n");
++	seq_printf(s, "#stats;macaddr;rate;avg_prob;avg_tp;cur_success;cur_attempts;hist_success;hist_attempts\n");
++	seq_printf(s, "#rates;macaddr;rates;counts\n");
++	seq_printf(s, "#probe;macaddr;rate\n");
++	for (i = 0; i < MINSTREL_GROUPS_NB; i++) {
++		const struct mcs_group *g = &minstrel_mcs_groups[i];
++		const char *type;
++
++		if (i == MINSTREL_CCK_GROUP)
++			type = "cck";
++		else if (i == MINSTREL_OFDM_GROUP)
++			type = "ofdm";
++		else if (g->flags & IEEE80211_TX_RC_VHT_MCS)
++			type = "vht";
++		else
++			type = "ht";
++
++		seq_printf(s, "group;%x;%x;%s;%x;%x;%x;",
++			   i, (u32) MI_RATE(i, 0), type, g->streams, g->bw,
++			   !!(g->flags & IEEE80211_TX_RC_SHORT_GI));
++		minstrel_ht_print_rate_durations(s, i);
++		seq_printf(s, "\n");
++	}
++
++	return 0;
++}
++
++static struct dentry *
++create_buf_file_cb(const char *filename, struct dentry *parent, umode_t mode,
++		   struct rchan_buf *buf, int *is_global)
++{
++	struct dentry *f;
++
++	f = debugfs_create_file("api_event", mode, parent, buf,
++				&relay_file_operations);
++	if (IS_ERR(f))
++		return NULL;
++
++	*is_global = 1;
++
++	return f;
++}
++
++static int
++remove_buf_file_cb(struct dentry *f)
++{
++	debugfs_remove(f);
++
++	return 0;
++}
++
++static struct rchan_callbacks relay_ev_cb = {
++	.create_buf_file = create_buf_file_cb,
++	.remove_buf_file = remove_buf_file_cb,
++};
++
++static void
++minstrel_ht_dump_sta(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
++		     const char *type)
++{
++	char info[64 + MINSTREL_GROUPS_NB * 4];
++	int ofs = 0;
++	int i;
++
++	ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%llx;sta;%s;%pM;%x;%x;",
++			(unsigned long long)ktime_get_boottime_ns(),
++			 type, mi->sta->addr, mi->overhead, mi->overhead_legacy);
++
++	ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%x",
++			 mi->supported[0]);
++	for (i = 1; i < MINSTREL_GROUPS_NB; i++)
++		ofs += scnprintf(info + ofs, sizeof(info) - ofs, ",%x",
++				 mi->supported[i]);
++
++	ofs += scnprintf(info + ofs, sizeof(info) - ofs, "\n");
++	relay_write(mp->relay_ev, info, ofs);
++	relay_flush(mp->relay_ev);
++}
++
++static void
++__minstrel_ht_dump_stations(struct minstrel_priv *mp, const char *type)
++{
++	struct minstrel_ht_sta *mi;
++
++	list_for_each_entry(mi, &mp->stations, list)
++		minstrel_ht_dump_sta(mp, mi, type);
++}
++
++static void
++minstrel_ht_dump_stations(struct minstrel_priv *mp)
++{
++	spin_lock_bh(&mp->lock);
++	__minstrel_ht_dump_stations(mp, "dump");
++	spin_unlock_bh(&mp->lock);
++}
++
++static void
++minstrel_ht_api_start(struct minstrel_priv *mp, char *params)
++{
++	char *cur;
++	u8 mask = 0;
++
++	spin_lock_bh(&mp->lock);
++
++	while ((cur = strsep(&params, ";")) != NULL) {
++		if (!strlen(cur))
++			break;
++
++		if (!strcmp(cur, "txs"))
++			mask |= MINSTREL_MONITOR_TXS;
++		else if (!strcmp(cur, "sta"))
++			mask |= MINSTREL_MONITOR_STA;
++		else if (!strcmp(cur, "stats"))
++			mask |= MINSTREL_MONITOR_STATS;
++	}
++
++	if (!mask)
++		mask = MINSTREL_MONITOR_TXS;
++
++	if (!mp->monitor)
++		__minstrel_ht_dump_stations(mp, "add");
++	mp->monitor = mask | MINSTREL_MONITOR_STA;
++
++	spin_unlock_bh(&mp->lock);
++}
++
++static void
++minstrel_ht_api_stop(struct minstrel_priv *mp)
++{
++	spin_lock_bh(&mp->lock);
++	mp->monitor = 0;
++	relay_reset(mp->relay_ev);
++	spin_unlock_bh(&mp->lock);
++}
++
++static void
++minstrel_ht_reset_sample_table(struct minstrel_ht_sta *mi)
++{
++	memset(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates, 0,
++	       sizeof(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates));
++}
++
++static void
++minstrel_ht_api_set_manual(struct minstrel_priv *mp, bool manual)
++{
++	struct minstrel_ht_sta *mi;
++
++	mp->manual = manual;
++
++	spin_lock_bh(&mp->lock);
++	list_for_each_entry(mi, &mp->stations, list)
++		minstrel_ht_reset_sample_table(mi);
++	spin_unlock_bh(&mp->lock);
++}
++
++static struct minstrel_ht_sta *
++minstrel_ht_api_get_sta(struct minstrel_priv *mp, const u8 *macaddr)
++{
++	struct minstrel_ht_sta *mi;
++
++	list_for_each_entry(mi, &mp->stations, list) {
++		if (!memcmp(mi->sta->addr, macaddr, ETH_ALEN))
++			return mi;
++	}
++
++	return NULL;
++}
++
++static int
++minstrel_ht_get_args(char **dest, int dest_size, char *str, char *sep)
++{
++	int i, n;
++
++	for (i = 0, n = 0; i < dest_size; i++) {
++		if (!str) {
++			dest[i] = NULL;
++			continue;
++		}
++
++		dest[i] = strsep(&str, sep);
++		if (dest[i])
++			n++;
++	}
++
++	return n;
++}
++
++static bool
++minstrel_ht_valid_rate(struct minstrel_ht_sta *mi, u32 rate)
++{
++	int group, idx;
++
++	group = MI_RATE_GROUP(rate);
++	if (group >= MINSTREL_GROUPS_NB)
++		return false;
++
++	idx = MI_RATE_IDX(rate);
++
++	return !!(mi->supported[group] & BIT(idx));
++}
++
++static int
++minstrel_ht_rate_from_str(struct minstrel_ht_sta *mi, const char *str)
++{
++	unsigned int rate;
++
++	if (kstrtouint(str, 16, &rate))
++		return -EINVAL;
++
++	if (!minstrel_ht_valid_rate(mi, rate))
++		return -EINVAL;
++
++	return rate;
++}
++
++static int
++minstrel_ht_set_probe_rate(struct minstrel_ht_sta *mi, const char *rate_str)
++{
++	u16 *sample_rates;
++	int rate, i;
++
++	if (!rate_str)
++		return -EINVAL;
++
++	rate = minstrel_ht_rate_from_str(mi, rate_str);
++	if (rate < 0)
++		return rate;
++
++	sample_rates = mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates;
++	for (i = 0; i < MINSTREL_SAMPLE_RATES; i++) {
++		if (sample_rates[i])
++			continue;
++
++		sample_rates[i] = rate;
++		mi->sample_time = jiffies;
++		return 0;
++	}
++
++	return -ENOSPC;
++}
++
++static int
++minstrel_ht_set_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
++		      char *rate_str, char *count_str)
++{
++	struct ieee80211_sta_rates *ratetbl;
++	unsigned int count;
++	char *countlist[4];
++	char *ratelist[4];
++	int rate;
++	int n_rates;
++	int n_count;
++	int err = -EINVAL;
++	int i;
++
++	if (!rate_str || !count_str)
++		return -EINVAL;
++
++	ratetbl = kzalloc(sizeof(*ratetbl), GFP_ATOMIC);
++	if (!ratetbl)
++		return -ENOMEM;
++
++	n_rates = minstrel_ht_get_args(ratelist, ARRAY_SIZE(ratelist),
++				       rate_str, ",");
++	n_count = minstrel_ht_get_args(countlist, ARRAY_SIZE(countlist),
++				       count_str, ",");
++	for (i = 0; i < min(n_rates, n_count); i++) {
++		rate = minstrel_ht_rate_from_str(mi, ratelist[i]);
++		if (rate < 0)
++			goto error;
++
++		if (kstrtouint(countlist[0], 16, &count))
++			goto error;
++
++		minstrel_ht_set_rate(mp, mi, ratetbl, i, rate);
++		ratetbl->rate[i].count = count;
++		ratetbl->rate[i].count_rts = count;
++		ratetbl->rate[i].count_cts = count;
++	}
++
++	rate_control_set_rates(mp->hw, mi->sta, ratetbl);
++
++	return 0;
++
++error:
++	kfree(ratetbl);
++	return err;
++}
++
++static int
++minstrel_ht_api_sta_cmd(struct minstrel_priv *mp, enum sta_cmd cmd,
++			char *arg_str)
++{
++	struct minstrel_ht_sta *mi;
++	uint8_t macaddr[ETH_ALEN];
++	char *args[3];
++	int n_args;
++	int ret = -EINVAL;
++
++	spin_lock_bh(&mp->lock);
++	if (!mp->manual)
++		goto out;
++
++	n_args = minstrel_ht_get_args(args, ARRAY_SIZE(args), arg_str, ";");
++	if (!args[0])
++		goto out;
++
++	if (!mac_pton(args[0], macaddr))
++		goto out;
++
++	mi = minstrel_ht_api_get_sta(mp, macaddr);
++	if (!mi) {
++		ret = -ENOENT;
++		goto out;
++	}
++
++	switch (cmd) {
++	case STA_CMD_PROBE:
++		ret = minstrel_ht_set_probe_rate(mi, args[1]);
++		break;
++	case STA_CMD_RATES:
++		ret = minstrel_ht_set_rates(mp, mi, args[1], args[2]);
++		break;
++	}
++
++out:
++	spin_unlock_bh(&mp->lock);
++
++	return ret;
++}
++
++static ssize_t
++minstrel_ht_control_write(struct file *file, const char __user *userbuf,
++			  size_t count, loff_t *ppos)
++{
++	struct minstrel_priv *mp = file->private_data;
++	char *pos, *cur;
++	char buf[64];
++	size_t len = count;
++	int err;
++
++	if (len > sizeof(buf) - 1)
++		return -EINVAL;
++
++	if (copy_from_user(buf, userbuf, len))
++		return -EFAULT;
++
++	if (count > 0 && buf[len - 1] == '\n')
++		len--;
++
++	buf[len] = 0;
++	if (!len)
++		return count;
++
++	pos = buf;
++	cur = strsep(&pos, ";");
++
++	err = 0;
++	if (!strcmp(cur, "dump"))
++		minstrel_ht_dump_stations(mp);
++	else if (!strcmp(cur, "start"))
++		minstrel_ht_api_start(mp, pos);
++	else if (!strcmp(cur, "stop"))
++		minstrel_ht_api_stop(mp);
++	else if (!strcmp(cur, "manual"))
++		minstrel_ht_api_set_manual(mp, true);
++	else if (!strcmp(cur, "auto"))
++		minstrel_ht_api_set_manual(mp, false);
++	else if (!strcmp(cur, "rates"))
++		err = minstrel_ht_api_sta_cmd(mp, STA_CMD_RATES, pos);
++	else if (!strcmp(cur, "probe"))
++		err = minstrel_ht_api_sta_cmd(mp, STA_CMD_PROBE, pos);
++	else
++		err = -EINVAL;
++
++	if (err)
++		return err;
++
++	return count;
++}
++
++static const struct file_operations fops_control = {
++	.open = simple_open,
++	.llseek = generic_file_llseek,
++	.write = minstrel_ht_control_write,
++};
++
++void minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
++{
++	bool add = list_empty(&mi->list);
++
++	spin_lock_bh(&mp->lock);
++	if (add)
++		list_add(&mi->list, &mp->stations);
++	if (mp->monitor)
++		minstrel_ht_dump_sta(mp, mi, add ? "add" : "update");
++	spin_unlock_bh(&mp->lock);
++}
++
++void minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
++{
++	char info[64];
++	int ofs = 0;
++
++	spin_lock_bh(&mp->lock);
++	list_del_init(&mi->list);
++
++	if (!mp->monitor)
++		goto out;
++
++	ofs = scnprintf(info, sizeof(info), "%llx;sta;remove;%pM;;;\n",
++			(unsigned long long)ktime_get_boottime_ns(),
++			mi->sta->addr);
++	relay_write(mp->relay_ev, info, ofs);
++	relay_flush(mp->relay_ev);
++
++out:
++	spin_unlock_bh(&mp->lock);
++}
++
++void __minstrel_ht_report_tx_status(struct minstrel_priv *mp,
++				    struct minstrel_ht_sta *mi,
++				    struct ieee80211_tx_info *info,
++				    u16 *rate_list,
++				    int n_rates)
++{
++	char txs[64 + IEEE80211_TX_MAX_RATES * 8];
++	int ofs = 0;
++	int i;
++
++	if (!n_rates)
++		return;
++
++	ofs += scnprintf(txs, sizeof(txs), "%llx;txs;%pM;%x;%x;%x;",
++			 (unsigned long long)ktime_get_boottime_ns(),
++			 mi->sta->addr,
++			 info->status.ampdu_len,
++			 info->status.ampdu_ack_len,
++			 !!(info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE));
++
++	ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "%x",
++			 rate_list[0]);
++	for (i = 1; i < n_rates; i++)
++		ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x",
++				 rate_list[i]);
++
++	ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ";%x",
++			 info->status.rates[0].count);
++	for (i = 1; i < n_rates; i++)
++		ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x",
++				 info->status.rates[i].count);
++	ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "\n");
++	relay_write(mp->relay_ev, txs, ofs);
++	relay_flush(mp->relay_ev);
++}
++
++void __minstrel_ht_report_rate_update(struct minstrel_priv *mp,
++				      struct minstrel_ht_sta *mi, u16 rate,
++				      struct minstrel_rate_stats *mrs)
++{
++	char stat[100];
++	int ofs;
++	int tp;
++
++	tp = minstrel_ht_get_tp_avg(mi, MI_RATE_GROUP(rate), MI_RATE_IDX(rate),
++				    mrs->prob_avg);
++
++	ofs = scnprintf(stat, sizeof(stat),
++			"%llx;stats;%pM;%x;%x;%x;%x;%x;%x;%x\n",
++			(unsigned long long)ktime_get_boottime_ns(),
++			mi->sta->addr, rate,
++			MINSTREL_TRUNC(mrs->prob_avg * 1000), tp,
++			mrs->last_success,
++			mrs->last_attempts,
++			mrs->succ_hist, mrs->att_hist);
++
++	relay_write(mp->relay_ev, stat, ofs);
++	relay_flush(mp->relay_ev);
++}
++
++void minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv,
++				 struct dentry *dir)
++{
++	struct minstrel_priv *mp = priv;
++
++	spin_lock_init(&mp->lock);
++	INIT_LIST_HEAD(&mp->stations);
++	mp->relay_ev = relay_open("api_event", dir, 256, 512, &relay_ev_cb,
++				  NULL);
++	debugfs_create_devm_seqfile(&hw->wiphy->dev, "api_info",
++				    dir, minstrel_ht_read_api_info);
++	debugfs_create_file("api_control", 0200, dir, mp, &fops_control);
++}
++
++void minstrel_ht_remove_debugfs_api(void *priv)
++{
++	struct minstrel_priv *mp = priv;
++
++	if (mp->relay_ev)
++		relay_close(mp->relay_ev);
++}