[][Add mwctl support for mtk opensource/proprietary driver]

[Description]
Add mwctl support for mtk opensource/proprietary driver, mwctl is used
to replace iwpriv and mt76-vendor tools, mwctl uses nl80211 to
communicate with driver.

[Release-log]

Change-Id: If6deb6bfb9582212fa6ea8e5e589b478d206a383
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/5648065
diff --git a/feed/mwctl/src/CMakeLists.txt b/feed/mwctl/src/CMakeLists.txt
new file mode 100755
index 0000000..a7015e9
--- /dev/null
+++ b/feed/mwctl/src/CMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 2.8)
+
+PROJECT(mwctl C)
+ADD_DEFINITIONS(-Os -Wall --std=gnu99 -g3)
+
+ADD_EXECUTABLE(mwctl main.c csi.c amnt.c capi.c iwpriv_compat.c ap_security.c)
+TARGET_LINK_LIBRARIES(mwctl nl-tiny)
+
+SET(CMAKE_INSTALL_PREFIX /usr)
+
+INSTALL(TARGETS mwctl
+	RUNTIME DESTINATION sbin
+)
diff --git a/feed/mwctl/src/amnt.c b/feed/mwctl/src/amnt.c
new file mode 100755
index 0000000..485b882
--- /dev/null
+++ b/feed/mwctl/src/amnt.c
@@ -0,0 +1,164 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mtk_vendor_nl80211.h"
+#include "mt76-vendor.h"
+#include "mwctl.h"
+
+static struct nla_policy
+amnt_ctrl_policy[NUM_MTK_VENDOR_ATTRS_AMNT_CTRL] = {
+	[MTK_VENDOR_ATTR_AMNT_CTRL_SET] = {.type = NLA_NESTED },
+	[MTK_VENDOR_ATTR_AMNT_CTRL_DUMP] = { .type = NLA_NESTED },
+};
+
+static struct nla_policy
+amnt_dump_policy[NUM_MTK_VENDOR_ATTRS_AMNT_DUMP] = {
+	[MTK_VENDOR_ATTR_AMNT_DUMP_INDEX] = {.type = NLA_U8 },
+	[MTK_VENDOR_ATTR_AMNT_DUMP_LEN] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_AMNT_DUMP_RESULT] = { .type = NLA_NESTED },
+};
+
+static int mt76_amnt_set_attr(struct nl_msg *msg, int argc, char **argv)
+{
+	void *tb1, *tb2;
+	u8 a[ETH_ALEN], idx;
+	int i = 0, matches;
+
+	idx = strtoul(argv[0], NULL, 0);
+	matches = sscanf(argv[1], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+			a, a+1, a+2, a+3, a+4, a+5);
+
+	if (matches != ETH_ALEN)
+		return -EINVAL;
+
+	tb1 = nla_nest_start(msg, MTK_VENDOR_ATTR_AMNT_CTRL_SET | NLA_F_NESTED);
+	if (!tb1)
+		return -ENOMEM;
+
+	nla_put_u8(msg, MTK_VENDOR_ATTR_AMNT_SET_INDEX, idx);
+
+	tb2 = nla_nest_start(msg, MTK_VENDOR_ATTR_AMNT_SET_MACADDR | NLA_F_NESTED);
+	if (!tb2) {
+		nla_nest_end(msg, tb1);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < ETH_ALEN; i++)
+		nla_put_u8(msg, i, a[i]);
+
+	nla_nest_end(msg, tb2);
+	nla_nest_end(msg, tb1);
+
+	return 0;
+}
+
+int mt76_amnt_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx)
+{
+	void *data;
+	int ret = 0;
+
+	if (argc < 1)
+		return 1;
+
+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+	if (!data)
+		return -ENOMEM;
+
+	mt76_amnt_set_attr(msg, argc, argv);
+
+	nla_nest_end(msg, data);
+
+	return ret;
+}
+
+static int mt76_amnt_dump_cb(struct nl_msg *msg, void *arg)
+{
+	struct nlattr *tb1[NUM_MTK_VENDOR_ATTRS_AMNT_CTRL];
+	struct nlattr *tb2[NUM_MTK_VENDOR_ATTRS_AMNT_DUMP];
+	struct nlattr *attr;
+	struct nlattr *data;
+	struct nlattr *cur;
+	struct amnt_data *res;
+	int len = 0, rem;
+
+	attr = unl_find_attr(&unl, msg, NL80211_ATTR_VENDOR_DATA);
+	if (!attr) {
+		fprintf(stderr, "Testdata attribute not found\n");
+		return NL_SKIP;
+	}
+
+	nla_parse_nested(tb1, MTK_VENDOR_ATTR_AMNT_CTRL_MAX,
+			 attr, amnt_ctrl_policy);
+
+	if (!tb1[MTK_VENDOR_ATTR_AMNT_CTRL_DUMP])
+		return NL_SKIP;
+
+	nla_parse_nested(tb2, NUM_MTK_VENDOR_ATTRS_AMNT_DUMP,
+			 tb1[MTK_VENDOR_ATTR_AMNT_CTRL_DUMP], amnt_dump_policy);
+
+	if (!tb2[MTK_VENDOR_ATTR_AMNT_DUMP_LEN])
+		return NL_SKIP;
+
+	len = nla_get_u8(tb2[MTK_VENDOR_ATTR_AMNT_DUMP_LEN]);
+	if (!len)
+		return 0;
+
+	if (!tb2[MTK_VENDOR_ATTR_AMNT_DUMP_RESULT])
+		return NL_SKIP;
+
+	data = tb2[MTK_VENDOR_ATTR_AMNT_DUMP_RESULT];
+	nla_for_each_nested(cur,data, rem) {
+		res = (struct amnt_data *) nla_data(cur);
+		printf("[vendor] amnt_idx: %d, addr=%x:%x:%x:%x:%x:%x, rssi=%d/%d/%d/%d, last_seen=%u\n",
+			res->idx,
+			res->addr[0], res->addr[1], res->addr[2],
+			res->addr[3], res->addr[4], res->addr[5],
+			res->rssi[0], res->rssi[1], res->rssi[2],
+			res->rssi[3], res->last_seen);
+	}
+	return 0;
+}
+
+int mt76_amnt_dump	(struct nl_msg *msg, int argc,
+	char **argv, void *ctx)
+{
+	void *data, *tb1;
+	u8 amnt_idx;
+
+	if (argc < 1)
+		return 1;
+
+	register_handler(mt76_amnt_dump_cb, NULL);
+	
+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+	if (!data)
+		return -EINVAL;
+
+	tb1 = nla_nest_start(msg, MTK_VENDOR_ATTR_AMNT_CTRL_DUMP | NLA_F_NESTED);
+	if (!tb1)
+		return -EINVAL;
+
+	amnt_idx = strtoul(argv[0], NULL, 0);
+	nla_put_u8(msg, MTK_VENDOR_ATTR_AMNT_DUMP_INDEX, amnt_idx);
+
+	nla_nest_end(msg, tb1);
+
+	nla_nest_end(msg, data);
+
+	return 0;
+}
+
+DECLARE_SECTION(dump);
+
+COMMAND(dump, amnt, "",
+	MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL, 0, CIB_NETDEV, mt76_amnt_dump,
+	"dump amnt <index> (0x0~0xf or 0xff)");
+
+DECLARE_SECTION(set);
+
+COMMAND(set, amnt, "<index>(0x0~0xf) <mac addr>(xx:xx:xx:xx:xx:xx)",
+	MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL, 0, CIB_NETDEV, mt76_amnt_set,
+	"set amnt <index>(0x0~0xf) <mac addr>(xx:xx:xx:xx:xx:xx)");
+
+
diff --git a/feed/mwctl/src/ap_security.c b/feed/mwctl/src/ap_security.c
new file mode 100755
index 0000000..3db575e
--- /dev/null
+++ b/feed/mwctl/src/ap_security.c
@@ -0,0 +1,384 @@
+/* Copyright (C) 2021 Mediatek Inc. */

+#define _GNU_SOURCE

+

+#include "mtk_vendor_nl80211.h"

+#include "mt76-vendor.h"

+#include "mwctl.h"

+

+DECLARE_SECTION(set);

+

+#define AP_SECURITY_OPTIONS "[authmode=[open|shared|wepauto|wpa|wpapsk|wpanone|wpa2|wpa2mix|wpa2psk|wpa3|"	\

+	"wpa3-192|wpa3psk|wpa2pskwpa3psk|wpa2pskmixwpa3psk|wpa1wpa2|wpapskwpa2psk|wpa_aes_wpa2_tkipaes|wpa"	\

+	"_aes_wpa2_tkip|wpa_tkip_wpa2_aes|wpa_tkip_wpa2_tkipaes|wpa_tkipaes_wpa2_aes|wpa_tkipaes_wpa2_tkip"	\

+	"aes|wpa_tkipaes_wpa2_tkip|owe|files_sha256|files_sha384|waicert|waipsk|dpp|dppwpa2psk|dppwap3psk|"	\

+	"dppwpa3pskwpa2psk|wpa2-ent-osen]] [encryptype=[none|wep|tkip|aes|ccmp128|ccmp256|gcmp128|gcmp25" \

+	"6|tkipaes|tkipcmp128|wpa_aes_wpa2_tkipaes|wpa_aes_wpa2_tkip|wpa_tkip_wpa2_aes|wpa_tkip_wpa2_tkipa"	\

+	"es|wpa_tkipaes_wpa2_aes|wpa_tkipaes_wpa2_tkipaes|wpa_tkipaes_wpa2_tkip|sms4]]"	\

+	" [rekeyinterval=<seconds>] [rekeymethod=[time|pkt]] [defaultkeyid=<pairwise key id>]"	\

+	"[wep_key1=<key>] [wep_key2=<key>] [wep_key3=<key>] [wep_key4=<key>] [passphrase=<passphrase>]"	\

+	" [pmf_capable=<0 or 1>] [pmf_require=<0 or 1>] [pmf_sha256=<0 or 1>]"

+

+#define MAX_SEURITY_PARAM_LEN 128

+

+struct security_option {

+	char option_name[MAX_SEURITY_PARAM_LEN];

+	int (* attr_put)(struct nl_msg *msg, char *value);

+};

+

+int auth_attr_put(struct nl_msg *msg, char *value);

+int encryptype_attr_put(struct nl_msg *msg, char *value);

+

+int rekeyinterval_attr_put(struct nl_msg *msg, char *value)

+{

+	unsigned long interval;

+

+	interval = strtoul(value, NULL, 10);

+

+	if (interval < 10 || interval >= 0x3ffffff)

+		return -EINVAL;

+

+	if (nla_put_u32(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_REKEYINTERVAL, interval))

+		return -EMSGSIZE;

+

+	return 0;

+}

+

+int rekeymethod_attr_put(struct nl_msg *msg, char *value)

+{

+	unsigned char method;

+

+	if (strlen(value) == strlen("time") && !strncmp(value, "time", strlen("time")))

+		method = 0;

+	else if (strlen(value) == strlen("pkt") && !strncmp(value, "pkt", strlen("pkt")))

+		method = 1;

+	else

+		return -EINVAL;

+

+	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_REKEYMETHOD, method))

+		return -EMSGSIZE;

+

+	return 0;

+}

+

+int defaultkeyid_attr_put(struct nl_msg *msg, char *value)

+{

+	unsigned long key_id;

+

+	key_id = strtoul(value, NULL, 10);

+

+	if (key_id < 1 || key_id > 4 )

+		return -EINVAL;

+

+	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_DEFAULTKEYID, key_id))

+		return -EMSGSIZE;

+

+	return 0;

+}

+

+#define isxdigit(_char)  \

+	(('0' <= (_char) && (_char) <= '9') ||\

+	 ('a' <= (_char) && (_char) <= 'f') || \

+	 ('A' <= (_char) && (_char) <= 'F') \

+	)

+

+int wep_key_attr_put(struct nl_msg *msg, char *value, unsigned char key_idx)

+{

+	struct wep_key_param k;

+	int len, i;

+	char k_v[2];

+	unsigned long tmp_key;

+

+	memset(&k, 0, sizeof(k));

+

+	k.key_idx = key_idx;

+	len = strlen(value);

+

+	switch (len) {

+	case 5: /*wep 40 Ascii type*/

+	case 13: /*wep 104 Ascii type*/

+	case 16: /*wep 128 Ascii type*/

+		k.key_len = len;

+		memcpy(k.key, value, len);

+		break;

+	case 10: /*wep 40 Hex type*/

+	case 26: /*wep 104 Hex type*/

+	case 32: /*wep 128 Hex type*/

+		for (i = 0; i < len; i++) {

+			if (!isxdigit(*(value + i)))

+				return -EINVAL; /*Not Hex value;*/

+		}

+		k.key_len = len / 2;

+		for (i = 0; i < (len / 2); i++) {

+			memcpy(k_v, value + i * 2, 2);

+			tmp_key = strtoul(k_v, NULL, 10);

+			k.key[i] = (unsigned char)tmp_key;

+		}

+		k.key[k.key_len] = '\0';

+		break;

+	default:

+		return -EINVAL;

+	}

+

+	if (nla_put(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_WEPKEY, sizeof(k), &k))

+		return -EMSGSIZE;

+

+	return 0;	

+}

+

+int wep_key1_attr_put(struct nl_msg *msg, char *value)

+{

+	return wep_key_attr_put(msg, value, 0);

+}

+

+int wep_key2_attr_put(struct nl_msg *msg, char *value)

+{

+	return wep_key_attr_put(msg, value, 1);

+}

+

+int wep_key3_attr_put(struct nl_msg *msg, char *value)

+{

+	return wep_key_attr_put(msg, value, 2);

+}

+

+int wep_key4_attr_put(struct nl_msg *msg, char *value)

+{

+	return wep_key_attr_put(msg, value, 3);

+}

+

+int passphrase_attr_put(struct nl_msg *msg, char *value)

+{

+	int len;

+

+	len = strlen(value);

+

+	if (len >= 65)

+		return -EINVAL;

+

+	if (nla_put(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_PASSPHRASE, len, value))

+		return -EMSGSIZE;

+

+	return 0;

+}

+

+int pmf_capable_attr_put(struct nl_msg *msg, char *value)

+{

+	unsigned char pmf_capable;

+

+	if (!value)

+		return -EINVAL;

+

+	if (*value == '0')

+		pmf_capable = 0;

+	else if (*value == '1')

+		pmf_capable = 1;

+	else

+		return -EINVAL;

+

+	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_PMF_CAPABLE, pmf_capable))

+		return -EMSGSIZE;

+

+	return 0;

+}

+

+int pmf_require_attr_put(struct nl_msg *msg, char *value)

+{

+	unsigned char pmf_require;

+

+	if (!value)

+		return -EINVAL;

+

+	if (*value == '0')

+		pmf_require = 0;

+	else if (*value == '1')

+		pmf_require = 1;

+	else

+		return -EINVAL;

+

+	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_PMF_REQUIRE, pmf_require))

+		return -EMSGSIZE;

+

+	return 0;

+}

+

+int pmf_sha256_attr_put(struct nl_msg *msg, char *value)

+{

+	unsigned char pmf_sha256;

+

+	if (!value)

+		return -EINVAL;

+

+	if (*value == '0')

+		pmf_sha256 = 0;

+	else if (*value == '1')

+		pmf_sha256 = 1;

+	else

+		return -EINVAL;

+

+	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_PMF_SHA256, pmf_sha256))

+		return -EMSGSIZE;

+

+	return 0;

+}

+

+struct security_option sec_opt[] = {

+	{"authmode", auth_attr_put},

+	{"encryptype", encryptype_attr_put},

+	{"rekeyinterval", rekeyinterval_attr_put},

+	{"rekeymethod", rekeymethod_attr_put},

+	{"defaultkeyid", defaultkeyid_attr_put},

+	{"wep_key1", wep_key1_attr_put},

+	{"wep_key2", wep_key2_attr_put},

+	{"wep_key3", wep_key3_attr_put},

+	{"wep_key4", wep_key4_attr_put},

+	{"passphrase", passphrase_attr_put},

+	{"pmf_capable", pmf_capable_attr_put},

+	{"pmf_require", pmf_require_attr_put},

+	{"pmf_sha256", pmf_sha256_attr_put},

+};

+

+struct auth_mode_option  {

+	char option_name[MAX_SEURITY_PARAM_LEN];

+	enum mtk_vendor_attr_authmode mode;

+};

+

+struct auth_mode_option auth_opt[] = {

+	{"open", NL80211_AUTH_OPEN},

+	{"shared", NL80211_AUTH_SHARED},

+	{"wepauto", NL80211_AUTH_WEPAUTO},

+	{"wpa", NL80211_AUTH_WPA},

+	{"wpapsk", NL80211_AUTH_WPAPSK},

+	{"wpanone", NL80211_AUTH_WPANONE},

+	{"wpa2", NL80211_AUTH_WPA2},

+	{"wpa2mix", NL80211_AUTH_WPA2MIX},

+	{"wpa2psk", NL80211_AUTH_WPA2PSK},

+	{"wpa3", NL80211_AUTH_WPA3},

+	{"wpa3-192", NL80211_AUTH_WPA3_192},

+	{"wpa3psk", NL80211_AUTH_WPA3PSK},

+	{"wpa2pskwpa3psk", NL80211_AUTH_WPA2PSKWPA3PSK},

+	{"wpa2pskmixwpa3psk", NL80211_AUTH_WPA2PSKMIXWPA3PSK},

+	{"wpa1wpa2", NL80211_AUTH_WPA1WPA2},

+	{"wpapskwpa2psk", NL80211_AUTH_WPAPSKWPA2PSK},

+	{"wpa_aes_wpa2_tkipaes", NL80211_AUTH_WPA_AES_WPA2_TKIPAES},

+	{"wpa_aes_wpa2_tkip", NL80211_AUTH_WPA_AES_WPA2_TKIP},

+	{"wpa_tkip_wpa2_aes", NL80211_AUTH_WPA_TKIP_WPA2_AES},

+	{"wpa_tkip_wpa2_tkipaes", NL80211_AUTH_WPA_TKIP_WPA2_TKIPAES},

+	{"wpa_tkipaes_wpa2_aes", NL80211_AUTH_WPA_TKIPAES_WPA2_AES},

+	{"wpa_tkipaes_wpa2_tkipaes", NL80211_AUTH_WPA_TKIPAES_WPA2_TKIPAES},

+	{"wpa_tkipaes_wpa2_tkip", NL80211_AUTH_WPA_TKIPAES_WPA2_TKIP},

+	{"owe", NL80211_AUTH_OWE},

+	{"files_sha256", NL80211_AUTH_FILS_SHA256},

+	{"files_sha384", NL80211_AUTH_FILS_SHA384},

+	{"waicert", NL80211_AUTH_WAICERT},

+	{"waipsk", NL80211_AUTH_WAIPSK},

+	{"dpp", NL80211_AUTH_DPP},

+	{"dppwpa2psk", NL80211_AUTH_DPPWPA2PSK},

+	{"dppwap3psk", NL80211_AUTH_DPPWPA3PSK},

+	{"dppwpa3pskwpa2psk", NL80211_AUTH_DPPWPA3PSKWPA2PSK},

+	{"wpa2-ent-osen", NL80211_AUTH_WPA2_ENT_OSEN},

+};

+

+int auth_attr_put(struct nl_msg *msg, char *value)

+{

+	int i;

+

+	for (i = 0; i < (sizeof(auth_opt)/sizeof(auth_opt[0])); i++) {

+		if (strlen(auth_opt[i].option_name) == strlen(value) &&

+			!strncmp(auth_opt[i].option_name, value, strlen(value))) {

+			if (nla_put_u32(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_AUTHMODE, auth_opt[i].mode))

+				return -EMSGSIZE;

+		}

+	}

+

+	return 0;

+}

+

+struct encryptype_option  {

+	char option_name[MAX_SEURITY_PARAM_LEN];

+	enum mtk_vendor_attr_encryptype type;

+};

+

+struct encryptype_option encryp_opt[] = {

+	{"none", NL80211_ENCRYPTYPE_NONE},

+	{"wep", NL80211_ENCRYPTYPE_WEP},

+	{"tkip", NL80211_ENCRYPTYPE_TKIP},

+	{"aes", NL80211_ENCRYPTYPE_AES},

+	{"ccmp128", NL80211_ENCRYPTYPE_CCMP128},

+	{"ccmp256", NL80211_ENCRYPTYPE_CCMP256},

+	{"gcmp128|gcmp256", NL80211_ENCRYPTYPE_GCMP128},

+	{"tkipaes", NL80211_ENCRYPTYPE_GCMP256},

+	{"tkipcmp128", NL80211_ENCRYPTYPE_TKIPCCMP128},

+	{"wpa_aes_wpa2_tkipaes", NL80211_ENCRYPTYPE_WPA_AES_WPA2_TKIPAES},

+	{"wpa_aes_wpa2_tkip", NL80211_ENCRYPTYPE_WPA_AES_WPA2_TKIP},

+	{"wpa_tkip_wpa2_aes", NL80211_ENCRYPTYPE_WPA_TKIP_WPA2_AES},

+	{"wpa_tkip_wpa2_tkipaes", NL80211_ENCRYPTYPE_WPA_TKIP_WPA2_TKIPAES},

+	{"wpa_tkipaes_wpa2_aes", NL80211_ENCRYPTYPE_WPA_TKIPAES_WPA2_AES},

+	{"wpa_tkipaes_wpa2_tkipaes", NL80211_ENCRYPTYPE_WPA_TKIPAES_WPA2_TKIPAES},

+	{"wpa_tkipaes_wpa2_tkip", NL80211_ENCRYPTYPE_WPA_TKIPAES_WPA2_TKIP},

+	{"sms4", NL80211_ENCRYPTYPE_SMS4},

+};

+

+int encryptype_attr_put(struct nl_msg *msg, char *value)

+{

+	int i;

+

+	for (i = 0; i < (sizeof(encryp_opt)/sizeof(encryp_opt[0])); i++) {

+		if (strlen(encryp_opt[i].option_name) == strlen(value) &&

+			!strncmp(encryp_opt[i].option_name, value, strlen(value))) {

+			if (nla_put_u32(msg, MTK_NL80211_VENDOR_ATTR_AP_SECURITY_ENCRYPTYPE, encryp_opt[i].type))

+				return -EMSGSIZE;

+		}

+	}

+

+	return 0;

+}

+

+int handle_ap_security_set(struct nl_msg *msg, int argc,

+	char **argv, void *ctx)

+{

+	void *data;

+	char *ptr, *param_str, *val_str, invalide = 0;

+	int i, j;

+

+	if (!argc)

+		return -EINVAL;

+

+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);

+	if (!data)

+		return -ENOMEM;

+

+	for (i = 0; i < argc; i++) {

+		ptr = argv[i];

+		param_str = ptr;

+		val_str = strchr(ptr, '=');

+

+		if (!val_str)

+			continue;

+

+		*val_str++ = 0;

+

+		for (j = 0; j < (sizeof(sec_opt) / sizeof(sec_opt[0])); j++) {

+			if (strlen(sec_opt[j].option_name) == strlen(param_str) &&

+				!strncmp(sec_opt[j].option_name, param_str, strlen(param_str)))

+				break;

+		}

+

+		if (j != (sizeof(sec_opt) / sizeof(sec_opt[0]))) {

+			if (sec_opt[j].attr_put(msg, val_str) < 0)

+				printf("invalide argument %s=%s, ignore it\n", param_str, val_str);

+			else

+				invalide = 1;

+		}

+	}

+

+	nla_nest_end(msg, data);

+

+	if (!invalide)

+		return -EINVAL;

+

+	return 0;

+}

+

+COMMAND(set, ap_security,

+	AP_SECURITY_OPTIONS,

+	MTK_NL80211_VENDOR_SUBCMD_SET_AP_SECURITY, 0, CIB_NETDEV, handle_ap_security_set,

+	"Set the security information to specific bss\n");

diff --git a/feed/mwctl/src/capi.c b/feed/mwctl/src/capi.c
new file mode 100755
index 0000000..45a50a1
--- /dev/null
+++ b/feed/mwctl/src/capi.c
@@ -0,0 +1,139 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mtk_vendor_nl80211.h"
+#include "mt76-vendor.h"
+#include "mwctl.h"
+
+static int mt76_ap_rfeatures_set_attr(struct nl_msg *msg, int argc, char **argv)
+{
+	char *val, *s1, *s2, *cur;
+	void *data;
+	int idx = MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_EN;
+
+	val = strchr(argv[0], '=');
+	if (!val)
+		return -EINVAL;
+
+	*(val++) = 0;
+
+	if (!strncmp(argv[0], "he_gi", 5)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_GI, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "he_ltf", 6)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_LTF, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "trig_type", 9)) {
+		data = nla_nest_start(msg,
+				      MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_CFG | NLA_F_NESTED);
+		if (!data)
+			return -ENOMEM;
+
+		s1 = s2 = strdup(val);
+		while ((cur = strsep(&s1, ",")) != NULL)
+			nla_put_u8(msg, idx++, strtoul(cur, NULL, 0));
+
+		nla_nest_end(msg, data);
+
+		free(s2);
+	} else if (!strncmp(argv[0], "ack_policy", 10)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_RFEATURE_CTRL_ACK_PLCY, strtoul(val, NULL, 0));
+	}
+
+	return 0;
+}
+
+int mt76_ap_rfeatures_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx)
+{
+	void *data;
+	int ret = 0;
+
+	if (argc < 1)
+		return 1;
+
+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+	if (!data)
+		return -ENOMEM;
+
+	mt76_ap_rfeatures_set_attr(msg, argc, argv);
+
+	nla_nest_end(msg, data);
+
+	return ret;
+}
+
+static int mt76_ap_wireless_set_attr(struct nl_msg *msg, int argc, char **argv)
+{
+	char *val;
+
+	val = strchr(argv[0], '=');
+	if (!val)
+		return -EINVAL;
+
+	*(val++) = 0;
+
+	if (!strncmp(argv[0], "fixed_mcs", 9)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_FIXED_MCS, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "ofdma", 5)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_FIXED_OFDMA, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "ppdu_type", 9)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_PPDU_TX_TYPE, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "nusers_ofdma", 12)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_NUSERS_OFDMA, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "add_ba_req_bufsize", 18)) {
+		nla_put_u16(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_BA_BUFFER_SIZE,
+			    strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "mimo", 4)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_MIMO, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "ampdu", 5)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_AMPDU, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "amsdu", 5)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_AMSDU, strtoul(val, NULL, 0));
+	} else if (!strncmp(argv[0], "cert", 4)) {
+		nla_put_u8(msg, MTK_VENDOR_ATTR_WIRELESS_CTRL_CERT, strtoul(val, NULL, 0));
+	}
+
+	return 0;
+}
+
+int mt76_ap_wireless_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx)
+{
+	void *data;
+	int ret = 0;
+
+	if (argc < 1)
+		return 1;
+
+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+	if (!data)
+		return -ENOMEM;
+
+	mt76_ap_wireless_set_attr(msg, argc, argv);
+
+	nla_nest_end(msg, data);
+
+	return ret;
+}
+
+DECLARE_SECTION(set);
+
+COMMAND(set, ap_rfeatures, "he_gi=<val>",
+	MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL, 0, CIB_NETDEV, mt76_ap_rfeatures_set,
+	"set ap_rfeatures he_gi=<val>"
+	"set ap_rfeatures he_ltf=<val>"
+	"set ap_rfeatures trig_type=<enable>,<val> (val: 0-7)"
+	"set ap_rfeatures ack_policy=<val> (val: 0-4)");
+
+DECLARE_SECTION(set);
+
+COMMAND(set, ap_wireless, "he_gi=<val>",
+	MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL, 0, CIB_NETDEV, mt76_ap_wireless_set,
+	"set ap_wireless fixed_mcs=<val>"
+	"set ap_wireless ofdma=<val> (0: disable, 1: DL, 2: UL)"
+	"set ap_wireless nusers_ofdma=<val>"
+	"set ap_wireless ppdu_type=<val> (0: SU, 1: MU, 4: LEGACY)"
+	"set ap_wireless add_ba_req_bufsize=<val>"
+	"set ap_wireless mimo=<val> (0: DL, 1: UL)"
+	"set ap_wireless ampdu=<enable>"
+	"set ap_wireless amsdu=<enable>"
+	"set ap_wireless cert=<enable>");
diff --git a/feed/mwctl/src/csi.c b/feed/mwctl/src/csi.c
new file mode 100755
index 0000000..abbc277
--- /dev/null
+++ b/feed/mwctl/src/csi.c
@@ -0,0 +1,353 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mtk_vendor_nl80211.h"
+#include "mt76-vendor.h"
+#include "mwctl.h"
+
+struct csi_data *csi;
+int csi_idx;
+
+static struct nla_policy csi_ctrl_policy[NUM_MTK_VENDOR_ATTRS_CSI_CTRL] = {
+	[MTK_VENDOR_ATTR_CSI_CTRL_CFG] = { .type = NLA_NESTED },
+	[MTK_VENDOR_ATTR_CSI_CTRL_CFG_MODE] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_CTRL_CFG_TYPE] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_CTRL_CFG_VAL1] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_CTRL_CFG_VAL2] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_CTRL_MAC_ADDR] = { .type = NLA_NESTED },
+	[MTK_VENDOR_ATTR_CSI_CTRL_INTERVAL] = { .type = NLA_U32 },
+	[MTK_VENDOR_ATTR_CSI_CTRL_DUMP_NUM] = { .type = NLA_U16 },
+	[MTK_VENDOR_ATTR_CSI_CTRL_DATA] = { .type = NLA_NESTED },
+};
+
+static struct nla_policy csi_data_policy[NUM_MTK_VENDOR_ATTRS_CSI_DATA] = {
+	[MTK_VENDOR_ATTR_CSI_DATA_VER] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_TS] = { .type = NLA_U32 },
+	[MTK_VENDOR_ATTR_CSI_DATA_RSSI] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_SNR] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_BW] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_CH_IDX] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_TA] = { .type = NLA_NESTED },
+	[MTK_VENDOR_ATTR_CSI_DATA_I] = { .type = NLA_NESTED },
+	[MTK_VENDOR_ATTR_CSI_DATA_Q] = { .type = NLA_NESTED },
+	[MTK_VENDOR_ATTR_CSI_DATA_INFO] = { .type = NLA_U32 },
+	[MTK_VENDOR_ATTR_CSI_DATA_TX_ANT] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_RX_ANT] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_MODE] = { .type = NLA_U8 },
+	[MTK_VENDOR_ATTR_CSI_DATA_H_IDX] = { .type = NLA_U32 },
+};
+
+static int mt76_csi_dump_cb(struct nl_msg *msg, void *arg)
+{
+	struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_CSI_CTRL];
+	struct nlattr *tb_data[NUM_MTK_VENDOR_ATTRS_CSI_DATA];
+	struct nlattr *attr;
+	struct nlattr *cur;
+	size_t idx;
+	int rem;
+	struct csi_data *c = &csi[csi_idx];
+
+	attr = unl_find_attr(&unl, msg, NL80211_ATTR_VENDOR_DATA);
+	if (!attr) {
+		fprintf(stderr, "Testdata attribute not found\n");
+		return NL_SKIP;
+	}
+
+	nla_parse_nested(tb, MTK_VENDOR_ATTR_CSI_CTRL_MAX,
+			 attr, csi_ctrl_policy);
+
+	if (!tb[MTK_VENDOR_ATTR_CSI_CTRL_DATA])
+		return NL_SKIP;
+
+	nla_parse_nested(tb_data, MTK_VENDOR_ATTR_CSI_DATA_MAX,
+			 tb[MTK_VENDOR_ATTR_CSI_CTRL_DATA], csi_data_policy);
+
+	if (!(tb_data[MTK_VENDOR_ATTR_CSI_DATA_VER] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_TS] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_RSSI] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_SNR] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_BW] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_CH_IDX] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_TA] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_I] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_Q] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_INFO] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_MODE] &&
+	      tb_data[MTK_VENDOR_ATTR_CSI_DATA_H_IDX])) {
+		fprintf(stderr, "Attributes error for CSI data\n");
+		return NL_SKIP;
+	}
+
+	c->rssi = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_RSSI]);
+	c->snr = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_SNR]);
+	c->data_bw = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_BW]);
+	c->pri_ch_idx = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_CH_IDX]);
+	c->rx_mode = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_MODE]);
+
+	c->tx_idx = nla_get_u16(tb_data[MTK_VENDOR_ATTR_CSI_DATA_TX_ANT]);
+	c->rx_idx = nla_get_u16(tb_data[MTK_VENDOR_ATTR_CSI_DATA_RX_ANT]);
+
+	c->info = nla_get_u32(tb_data[MTK_VENDOR_ATTR_CSI_DATA_INFO]);
+	c->h_idx = nla_get_u32(tb_data[MTK_VENDOR_ATTR_CSI_DATA_H_IDX]);
+
+	c->ts = nla_get_u32(tb_data[MTK_VENDOR_ATTR_CSI_DATA_TS]);
+
+	idx = 0;
+	nla_for_each_nested(cur, tb_data[MTK_VENDOR_ATTR_CSI_DATA_TA], rem) {
+		if (idx < ETH_ALEN)
+			c->ta[idx++] = nla_get_u8(cur);
+	}
+
+	idx = 0;
+	nla_for_each_nested(cur, tb_data[MTK_VENDOR_ATTR_CSI_DATA_I], rem) {
+		if (idx < CSI_MAX_COUNT)
+			c->data_i[idx++] = nla_get_u16(cur);
+	}
+
+	idx = 0;
+	nla_for_each_nested(cur, tb_data[MTK_VENDOR_ATTR_CSI_DATA_Q], rem) {
+		if (idx < CSI_MAX_COUNT)
+			c->data_q[idx++] = nla_get_u16(cur);
+	}
+
+	csi_idx++;
+
+	return NL_SKIP;
+}
+
+static int mt76_csi_to_json(const char *name)
+{
+#define MAX_BUF_SIZE	6000
+	FILE *f;
+	int i, ret = -ENOMEM;
+
+	f = fopen(name, "a+");
+	if (!f) {
+		printf("open failure");
+		return 1;
+	}
+
+	if (fwrite("[", 1, 1, f) != 1) {
+		perror("fwrite");
+		goto out;
+	}
+
+	for (i = 0; i < csi_idx; i++) {
+		struct csi_data *c = &csi[i];
+		char *pos, *buf;
+		int j;
+
+		buf = malloc(MAX_BUF_SIZE);
+		if (!buf)
+			goto out;
+
+		pos = buf;
+		pos += snprintf(pos, MAX_BUF_SIZE, "%c", '[');
+
+		pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->ts);
+		pos += snprintf(pos, MAX_BUF_SIZE, "\"%02x%02x%02x%02x%02x%02x\",", c->ta[0], c->ta[1], c->ta[2], c->ta[3], c->ta[4], c->ta[5]);
+
+		pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->rssi);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->snr);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->data_bw);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->pri_ch_idx);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->rx_mode);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->tx_idx);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->rx_idx);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->h_idx);
+		pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->info);
+
+		pos += snprintf(pos, MAX_BUF_SIZE, "%c", '[');
+		for (j = 0; j < 256; j++) {
+			pos += snprintf(pos, MAX_BUF_SIZE, "%d", c->data_i[j]);
+			if (j != 255)
+				pos += snprintf(pos, MAX_BUF_SIZE, ",");
+		}
+		pos += snprintf(pos, MAX_BUF_SIZE, "%c,", ']');
+
+		pos += snprintf(pos, MAX_BUF_SIZE, "%c", '[');
+		for (j = 0; j < 256; j++) {
+			pos += snprintf(pos, MAX_BUF_SIZE, "%d", c->data_q[j]);
+			if (j != 255)
+				pos += snprintf(pos, MAX_BUF_SIZE, ",");
+		}
+		pos += snprintf(pos, MAX_BUF_SIZE, "%c", ']');
+
+		pos += snprintf(pos, MAX_BUF_SIZE, "%c", ']');
+		if (i != csi_idx - 1)
+			pos += snprintf(pos, MAX_BUF_SIZE, ",");
+
+		if (fwrite(buf, 1, pos - buf, f) != (pos - buf)) {
+			perror("fwrite");
+			free(buf);
+			goto out;
+		}
+
+		free(buf);
+	}
+
+	if (fwrite("]", 1, 1, f) != 1) {
+		perror("fwrite");
+		goto out;
+	}
+
+	ret = 0;
+out:
+	if (fclose(f))
+		perror("fclose");
+
+	return ret;
+}
+
+struct unl unl_mt76;
+
+int mt76_csi_dump(struct nl_msg *nlmsg, int argc,
+	char **argv, void *ctx)
+{
+	int pkt_num, ret = 0, i;
+	struct nl_msg *msg;
+	void *data;
+	int if_idx = 0;
+
+	if (argc < 2)
+		return 1;
+
+	pkt_num = strtol(argv[0], NULL, 10);
+	if (pkt_num < 0 || pkt_num > 30000)
+		return -EINVAL;
+
+#define CSI_DUMP_PER_NUM	3
+	csi_idx = 0;
+	csi = (struct csi_data *)calloc(pkt_num, sizeof(*csi));
+	if_idx = *((int*)ctx);
+
+	for (i = 0; i < pkt_num / CSI_DUMP_PER_NUM; i++) {
+		if (unl_genl_init(&unl_mt76, "nl80211") < 0) {
+			fprintf(stderr, "Failed to connect to nl80211\n");
+			return 2;
+		}
+
+		msg = unl_genl_msg(&unl_mt76, NL80211_CMD_VENDOR, true);
+
+		if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_idx) ||
+		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
+		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL))
+			return false;
+
+		data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+		if (!data)
+			return -ENOMEM;
+
+		if (nla_put_u16(msg, MTK_VENDOR_ATTR_CSI_CTRL_DUMP_NUM, CSI_DUMP_PER_NUM))
+			return false;
+
+		nla_nest_end(msg, data);
+
+		ret = unl_genl_request(&unl_mt76, msg, mt76_csi_dump_cb, NULL);
+		if (ret)
+			fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+		unl_free(&unl_mt76);
+	}
+
+	mt76_csi_to_json(argv[1]);
+	free(csi);
+
+	return ret;
+}
+
+static int mt76_csi_set_attr(struct nl_msg *msg, int argc, char **argv)
+{
+	int idx = MTK_VENDOR_ATTR_CSI_CTRL_CFG_MODE;
+	char *val, *s1, *s2, *cur;
+	void *data;
+
+	val = strchr(argv[0], '=');
+	if (!val)
+		return -EINVAL;
+
+	*(val++) = 0;
+
+	if (!strncmp(argv[0], "ctrl", 4)) {
+		data = nla_nest_start(msg, MTK_VENDOR_ATTR_CSI_CTRL_CFG | NLA_F_NESTED);
+		if (!data)
+			return -ENOMEM;
+
+		s1 = s2 = strdup(val);
+
+		while ((cur = strsep(&s1, ",")) != NULL) {
+			u8 param = strtoul(cur, NULL, 0);
+
+			nla_put_u8(msg, idx++, param);
+		}
+
+		nla_nest_end(msg, data);
+
+		free(s2);
+
+		if (argc == 2 &&
+		!strncmp(argv[1], "mac_addr", strlen("mac_addr"))) {
+			u8 a[ETH_ALEN];
+			int matches, i;
+
+			val = strchr(argv[1], '=');
+			if (!val)
+				return -EINVAL;
+
+			*(val++) = 0;
+			matches = sscanf(val, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+					a, a+1, a+2, a+3, a+4, a+5);
+
+			if (matches != ETH_ALEN)
+				return -EINVAL;
+
+			data = nla_nest_start(msg, MTK_VENDOR_ATTR_CSI_CTRL_MAC_ADDR | NLA_F_NESTED);
+			if (!data)
+				return -ENOMEM;
+
+			for (i = 0; i < ETH_ALEN; i++)
+				nla_put_u8(msg, i, a[i]);
+
+			nla_nest_end(msg, data);
+		}
+	} else if (!strncmp(argv[0], "interval", 8)) {
+		u32 interval = strtoul(val, NULL, 0);
+
+		nla_put_u32(msg, MTK_VENDOR_ATTR_CSI_CTRL_INTERVAL, interval);
+	}
+
+	return 0;
+}
+
+int mt76_csi_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx)
+{
+	void *data;
+
+	if (argc < 1)
+		return 1;
+
+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+	if (!data)
+		return -ENOMEM;
+
+	mt76_csi_set_attr(msg, argc, argv);
+
+	nla_nest_end(msg, data);
+
+	return 0;
+}
+
+DECLARE_SECTION(dump);
+
+COMMAND(dump, csi, "",
+	MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL, 0, CIB_NETDEV, mt76_csi_dump,
+	"dump csi <packet num> <filename>");
+
+DECLARE_SECTION(set);
+
+COMMAND(set, csi, "ctrl=<opt1>,<opt2>,<opt3>,<opt4> (macaddr=<macaddr>)",
+	MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL, 0, CIB_NETDEV, mt76_amnt_dump,
+	"set csi ctrl=<opt1>,<opt2>,<opt3>,<opt4> (macaddr=<macaddr>)\n"
+	"set csi interval=<interval (us)>");
+
diff --git a/feed/mwctl/src/iwpriv_compat.c b/feed/mwctl/src/iwpriv_compat.c
new file mode 100755
index 0000000..22a8dcc
--- /dev/null
+++ b/feed/mwctl/src/iwpriv_compat.c
@@ -0,0 +1,270 @@
+/* Copyright (C) 2021 Mediatek Inc. */

+#define _GNU_SOURCE

+

+#include "mtk_vendor_nl80211.h"

+#include "mt76-vendor.h"

+#include "mwctl.h"

+

+static unsigned char ascii2hex(char *in, unsigned int *out)

+{

+	unsigned int hex_val, val;

+	char *p, asc_val;

+

+	hex_val = 0;

+	p = (char *)in;

+

+	while ((*p) != 0) {

+		val = 0;

+		asc_val = *p;

+

+		if ((asc_val >= 'a') && (asc_val <= 'f'))

+			val = asc_val - 87;

+		else if ((*p >= 'A') && (asc_val <= 'F'))

+			val = asc_val - 55;

+		else if ((asc_val >= '0') && (asc_val <= '9'))

+			val = asc_val - 48;

+		else

+			return 0;

+

+		hex_val = (hex_val << 4) + val;

+		p++;

+	}

+

+	*out = hex_val;

+	return 1;

+}

+

+static int handle_common_command(struct nl_msg *msg,

+			   int argc, char **argv, int attr)

+{

+	void *data;

+	size_t len = 0;

+	char *cmd_str;

+	

+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);

+	if (!data)

+		return -ENOMEM;

+

+	if (argc > 0) {

+		cmd_str = argv[0];

+

+		len = strlen(cmd_str);

+		if (len) {

+			if (nla_put(msg, attr, len, (unsigned char*)cmd_str))

+				return -EMSGSIZE;

+		}

+	}

+	nla_nest_end(msg, data);

+

+	return 0;

+}

+

+int handle_set_command(struct nl_msg *msg, int argc,

+	char **argv, void *ctx)

+{	

+	return handle_common_command(msg, argc, argv, MTK_NL80211_VENDOR_ATTR_VENDOR_SET_CMD_STR);

+}

+

+TOPLEVEL(set, "[param]=[value]", MTK_NL80211_VENDOR_SUBCMD_VENDOR_SET, 0, CIB_NETDEV, handle_set_command,

+	 "this command is used to be compatible with old iwpriv set command, e.g iwpriv ra0 set channel=36");

+

+int show_callback(struct nl_msg *msg, void *cb)

+{

+	struct nlattr *tb[NL80211_ATTR_MAX + 1];

+	struct nlattr *vndr_tb[MTK_NL80211_VENDOR_ATTR_VENDOR_SHOW_ATTR_MAX + 1];

+	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

+	char *show_str = NULL;

+	int err = 0;

+

+	err = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),

+			  genlmsg_attrlen(gnlh, 0), NULL);

+	if (err < 0)

+		return err;

+

+	if (tb[NL80211_ATTR_VENDOR_DATA]) {

+		err = nla_parse_nested(vndr_tb, MTK_NL80211_VENDOR_ATTR_VENDOR_SHOW_ATTR_MAX,

+			tb[NL80211_ATTR_VENDOR_DATA], NULL);

+		if (err < 0)

+			return err;

+

+		if (vndr_tb[MTK_NL80211_VENDOR_ATTR_VENDOR_SHOW_RSP_STR]) {

+			show_str = nla_data(vndr_tb[MTK_NL80211_VENDOR_ATTR_VENDOR_SHOW_RSP_STR]);

+			printf("%s\n", show_str);

+		}		

+	} else

+		printf("no any show rsp string from driver\n");

+

+	return 0;

+}

+

+static int handle_show_command(struct nl_msg *msg, int argc,

+	char **argv, void *ctx)

+{	

+	register_handler(show_callback, NULL);

+	return handle_common_command(msg, argc, argv, MTK_NL80211_VENDOR_ATTR_VENDOR_SHOW_CMD_STR);

+}

+

+TOPLEVEL(show, "param", MTK_NL80211_VENDOR_SUBCMD_VENDOR_SHOW, 0, CIB_NETDEV, handle_show_command,

+	 "this command is used to be compatible with old iwpriv show command, e.g iwpriv ra0 show stainfo");

+

+int stat_callback(struct nl_msg *msg, void *cb)

+{

+	struct nlattr *tb[NL80211_ATTR_MAX + 1];

+	struct nlattr *vndr_tb[MTK_NL80211_VENDOR_ATTR_STATISTICS_ATTR_MAX + 1];

+	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

+	char *show_str = NULL;

+	int err = 0;

+

+	err = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),

+			  genlmsg_attrlen(gnlh, 0), NULL);

+	if (err < 0)

+		return err;

+

+	if (tb[NL80211_ATTR_VENDOR_DATA]) {

+		err = nla_parse_nested(vndr_tb, MTK_NL80211_VENDOR_ATTR_STATISTICS_ATTR_MAX,

+			tb[NL80211_ATTR_VENDOR_DATA], NULL);

+		if (err < 0)

+			return err;

+

+		if (vndr_tb[MTK_NL80211_VENDOR_ATTR_STATISTICS_STR]) {

+			show_str = nla_data(vndr_tb[MTK_NL80211_VENDOR_ATTR_STATISTICS_STR]);

+			printf("%s\n", show_str);

+		}		

+	} else

+		printf("no any statistic string from driver\n");

+

+	return 0;

+}

+

+static int handle_stat_command(struct nl_msg *msg, int argc,

+	char **argv, void *ctx)

+{	

+	register_handler(stat_callback, NULL);

+	return handle_common_command(msg, 0, NULL, 0);

+}

+

+TOPLEVEL(stat, NULL, MTK_NL80211_VENDOR_SUBCMD_STATISTICS, 0, CIB_NETDEV, handle_stat_command,

+	 "this command is used to be compatible with old iwpriv stat command, e.g iwpriv ra0 stat");

+

+

+int mac_callback(struct nl_msg *msg, void *cb)

+{

+	struct nlattr *tb[NL80211_ATTR_MAX + 1];

+	struct nlattr *vndr_tb[MTK_NL80211_VENDOR_ATTR_MAC_ATTR_MAX + 1];

+	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

+	char *show_str = NULL;

+	int err = 0;

+

+	err = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),

+			  genlmsg_attrlen(gnlh, 0), NULL);

+	if (err < 0)

+		return err;

+

+	if (tb[NL80211_ATTR_VENDOR_DATA]) {

+		err = nla_parse_nested(vndr_tb, MTK_NL80211_VENDOR_ATTR_MAC_ATTR_MAX,

+			tb[NL80211_ATTR_VENDOR_DATA], NULL);

+		if (err < 0)

+			return err;

+

+		if (vndr_tb[MTK_NL80211_VENDOR_ATTR_MAC_RSP_STR]) {

+			show_str = nla_data(vndr_tb[MTK_NL80211_VENDOR_ATTR_MAC_RSP_STR]);

+			printf("%s\n", show_str);

+		}		

+	} else

+		printf("no any statistic string from driver\n");

+

+	return 0;

+}

+

+

+static int handle_mac_command(struct nl_msg *msg, int argc,

+	char **argv, void *ctx)

+{

+	void *data;

+	char *ptr, *seg_str, *addr_str, *val_str, *range_str;

+	unsigned char is_write, is_range;

+	unsigned int mac_s = 0, mac_e = 0;

+	unsigned int macVal = 0;

+	struct mac_param param;

+

+	if (!argc)

+		return -EINVAL;

+	

+	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);

+	if (!data)

+		return -ENOMEM;

+

+	ptr = argv[0];

+

+	while ((seg_str = strsep((char **)&ptr, ",")) != NULL) {

+		is_write = 0;

+		addr_str = seg_str;

+		val_str = NULL;

+		val_str = strchr(seg_str, '=');

+

+		if (val_str != NULL) {

+			*val_str++ = 0;

+			is_write = 1;

+		} else

+			is_write = 0;

+

+		if (addr_str) {

+			range_str = strchr(addr_str, '-');

+

+			if (range_str != NULL) {

+				*range_str++ = 0;

+				is_range = 1;

+			} else

+				is_range = 0;

+

+			if ((ascii2hex(addr_str, &mac_s) == 0)) {

+				printf("Invalid MAC CR Addr, str=%s\n", addr_str);

+				break;

+			}

+

+			if (is_range) {

+				if (ascii2hex(range_str, &mac_e) == 0) {

+					printf("Invalid Range End MAC CR Addr[0x%x], str=%s\n",

+							 mac_e, range_str);

+					break;

+				}

+

+				if (mac_e < mac_s) {

+					printf("Invalid Range MAC Addr[%s - %s] => [0x%x - 0x%x]\n",

+							 addr_str, range_str, mac_s, mac_e);

+					break;

+				}

+			} else

+				mac_e = mac_s;

+		}

+

+		if (val_str) {

+			if ((strlen(val_str) == 0) || ascii2hex(val_str, &macVal) == 0) {

+				printf("Invalid MAC value[0x%s]\n", val_str);

+				break;

+			}

+		}

+

+		memset(&param, 0, sizeof(param));

+		param.start = mac_s;

+		param.value = macVal;

+		param.end = mac_e;

+		if (is_write) {

+			if (nla_put(msg, MTK_NL80211_VENDOR_ATTR_MAC_WRITE_PARAM, sizeof(param), &param))

+				return -EMSGSIZE;

+		} else {

+			if (nla_put(msg, MTK_NL80211_VENDOR_ATTR_MAC_SHOW_PARAM, sizeof(param), &param))

+				return -EMSGSIZE;

+		}

+	}

+

+	nla_nest_end(msg, data);

+	register_handler(mac_callback, NULL);

+	return 0;

+}

+

+

+TOPLEVEL(mac, "addr=hex/addr-addr/addr=hex,addr=hex", MTK_NL80211_VENDOR_SUBCMD_MAC, 0,

+	CIB_NETDEV, handle_mac_command,

+	"this command is used to be compatible with old iwpriv mac, e.g iwpriv ra0 mac 2345");

+

diff --git a/feed/mwctl/src/iwpriv_compat.h b/feed/mwctl/src/iwpriv_compat.h
new file mode 100755
index 0000000..c376594
--- /dev/null
+++ b/feed/mwctl/src/iwpriv_compat.h
@@ -0,0 +1,7 @@
+#ifndef __IWPRIV_COMPAT_H

+#define __IWPRIV_COMPAT_H

+

+int handle_set_command(struct nl_msg *msg, int argc,

+	char **argv, void *ctx);

+

+#endif

diff --git a/feed/mwctl/src/main.c b/feed/mwctl/src/main.c
new file mode 100755
index 0000000..e2421f2
--- /dev/null
+++ b/feed/mwctl/src/main.c
@@ -0,0 +1,204 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include <net/if.h>
+
+#include "mtk_vendor_nl80211.h"
+#include "mt76-vendor.h"
+#include "iwpriv_compat.h"
+#include "mwctl.h"
+
+static const char *progname;
+struct unl unl;
+
+int (*registered_handler)(struct nl_msg *, void *);
+void *registered_handler_data;
+
+void register_handler(int (*handler)(struct nl_msg *, void *), void *data)
+{
+	registered_handler = handler;
+	registered_handler_data = data;
+}
+
+int valid_handler(struct nl_msg *msg, void *arg)
+{
+	if (registered_handler)
+		return registered_handler(msg, registered_handler_data);
+
+	return NL_OK;
+}
+
+extern struct cmd *__start___cmd[];
+extern struct cmd *__stop___cmd;
+
+#define for_each_cmd(_cmd, i)					\
+	for (i = 0; i < &__stop___cmd - __start___cmd; i++)	\
+		if ((_cmd = __start___cmd[i]))
+
+void usage(void)
+{
+	static const char *const commands[] = {
+		"set csi ctrl=<opt1>,<opt2>,<opt3>,<opt4> (macaddr=<macaddr>)",
+		"set csi interval=<interval (us)>",
+		"dump csi <packet num> <filename>",
+
+		"set amnt <index>(0x0~0xf) <mac addr>(xx:xx:xx:xx:xx:xx)",
+		"dump amnt <index> (0x0~0xf or 0xff)",
+
+		"set ap_rfeatures he_gi=<val>",
+		"set ap_rfeatures he_ltf=<val>",
+		"set ap_rfeatures trig_type=<enable>,<val> (val: 0-7)",
+		"set ap_rfeatures ack_policy=<val> (val: 0-4)",
+		"set ap_wireless fixed_mcs=<val>",
+		"set ap_wireless ofdma=<val> (0: disable, 1: DL, 2: UL)",
+		"set ap_wireless nusers_ofdma=<val>",
+		"set ap_wireless ppdu_type=<val> (0: SU, 1: MU, 4: LEGACY)",
+		"set ap_wireless add_ba_req_bufsize=<val>",
+		"set ap_wireless mimo=<val> (0: DL, 1: UL)",
+		"set ap_wireless ampdu=<enable>",
+		"set ap_wireless amsdu=<enable>",
+		"set ap_wireless cert=<enable>",
+	};
+	int i;
+
+	fprintf(stderr, "Usage:\n");
+	for (i = 0; i < ARRAY_SIZE(commands); i++)
+		printf("  %s wlanX %s\n", progname, commands[i]);
+
+	exit(1);
+}
+SECTION(dump);
+
+int main(int argc, char **argv)
+{
+	int if_idx, ret = 0;
+	const struct cmd *cmd, *match = NULL, *sectcmd;
+	const char *command, *section;
+	enum command_identify_by command_idby = CIB_NONE;
+	int err, i;
+	struct nl_msg *msg;
+
+	progname = argv[0];
+
+	if_idx = if_nametoindex(argv[1]);
+	if (!if_idx) {
+		fprintf(stderr, "%s\n", strerror(errno));
+		return 2;
+	}
+
+	argc -= 2;
+	argv += 2;
+
+#if 0
+	if (!strncmp(cmd_str, "dump", 4)) {
+		if (!strncmp(subcmd, "csi", 3))
+			ret = mt76_csi_dump(if_idx, argc, argv);
+		else if (!strncmp(subcmd, "amnt", 4))
+			ret = mt76_amnt_dump(if_idx, argc, argv);
+	} else if (!strncmp(cmd_str, "set", 3)) {
+		if (!strncmp(subcmd, "csi", 3))
+			ret = mt76_csi_set(if_idx, argc, argv);
+		else if (!strncmp(subcmd, "amnt", 4))
+			ret = mt76_amnt_set(if_idx, argc, argv);
+		else if (!strncmp(subcmd, "ap_rfeatures", 12))
+			ret = mt76_ap_rfeatures_set(if_idx, argc, argv);
+		else if (!strncmp(subcmd, "ap_wireless", 11))
+			ret = mt76_ap_wireless_set(if_idx, argc, argv);
+	} else {
+		usage();
+	}
+#endif
+
+	command_idby = CIB_NETDEV;
+	section = *argv;
+	argc--;
+	argv++;
+
+	for_each_cmd(sectcmd, i) {
+		if (sectcmd->parent)
+			continue;
+		/* ok ... bit of a hack for the dupe 'info' section */
+		if (match && sectcmd->idby != command_idby)
+			continue;
+		if (strcmp(sectcmd->name, section) == 0)
+			match = sectcmd;
+	}
+
+	sectcmd = match;
+	match = NULL;
+	if (!sectcmd)
+		return 1;
+
+	if (argc > 0) {
+		command = *argv;
+
+		for_each_cmd(cmd, i) {
+			if (!cmd->handler)
+				continue;
+			if (cmd->parent != sectcmd)
+				continue;
+			/*
+			 * ignore mismatch id by, but allow WDEV
+			 * in place of NETDEV
+			 */
+			if (cmd->idby != command_idby &&
+			    !(cmd->idby == CIB_NETDEV &&
+			      command_idby == CIB_WDEV))
+				continue;
+			if (strcmp(cmd->name, command))
+				continue;
+			if (argc > 1 && !cmd->args)
+				continue;
+			match = cmd;
+			break;
+		}
+
+		if (match) {
+			argc--;
+			argv++;
+		}
+	}
+
+	
+	if (match)
+		cmd = match;
+	else {
+		/* Use the section itself, if possible. */
+		cmd = sectcmd;
+		if (argc && !cmd->args)
+			return 1;
+		if (cmd->idby != command_idby &&
+			!(cmd->idby == CIB_NETDEV && command_idby == CIB_WDEV))
+			return 1;
+		if (!cmd->handler)
+			return 1;
+	}
+
+	if (unl_genl_init(&unl, "nl80211") < 0) {
+		fprintf(stderr, "Failed to connect to nl80211\n");
+		return 2;
+	}
+
+	msg = unl_genl_msg(&unl, NL80211_CMD_VENDOR, false);
+
+	if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_idx) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, cmd->cmd)) {
+		nlmsg_free(msg);
+		goto out;
+	}
+	
+	err = cmd->handler(msg, argc, argv, (void*)&if_idx);
+	if (err) {
+		nlmsg_free(msg);
+		goto out;
+	}
+
+	ret = unl_genl_request(&unl, msg, valid_handler, NULL);
+	if (ret)
+		fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+out:
+	unl_free(&unl);
+
+	return ret;
+}
diff --git a/feed/mwctl/src/mt76-vendor.h b/feed/mwctl/src/mt76-vendor.h
new file mode 100755
index 0000000..6be1056
--- /dev/null
+++ b/feed/mwctl/src/mt76-vendor.h
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: ISC
+/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+#ifndef __MT76_VENDOR_H
+#define __MT76_VENDOR_H
+
+#include <errno.h>
+#include <linux/nl80211.h>
+#include <netlink/attr.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unl.h>
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64, ktime_t;
+
+#define MTK_NL80211_VENDOR_ID	0x0ce7
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+#ifndef DIV_ROUND_UP
+#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+#endif
+
+struct nl_msg;
+struct nlattr;
+
+enum mtk_vendor_attr_csi_ctrl {
+	MTK_VENDOR_ATTR_CSI_CTRL_UNSPEC,
+
+	MTK_VENDOR_ATTR_CSI_CTRL_CFG,
+	MTK_VENDOR_ATTR_CSI_CTRL_CFG_MODE,
+	MTK_VENDOR_ATTR_CSI_CTRL_CFG_TYPE,
+	MTK_VENDOR_ATTR_CSI_CTRL_CFG_VAL1,
+	MTK_VENDOR_ATTR_CSI_CTRL_CFG_VAL2,
+	MTK_VENDOR_ATTR_CSI_CTRL_MAC_ADDR,
+	MTK_VENDOR_ATTR_CSI_CTRL_INTERVAL,
+
+	MTK_VENDOR_ATTR_CSI_CTRL_DUMP_NUM,
+
+	MTK_VENDOR_ATTR_CSI_CTRL_DATA,
+
+	/* keep last */
+	NUM_MTK_VENDOR_ATTRS_CSI_CTRL,
+	MTK_VENDOR_ATTR_CSI_CTRL_MAX =
+		NUM_MTK_VENDOR_ATTRS_CSI_CTRL - 1
+};
+
+enum mtk_vendor_attr_csi_data {
+	MTK_VENDOR_ATTR_CSI_DATA_UNSPEC,
+	MTK_VENDOR_ATTR_CSI_DATA_PAD,
+
+	MTK_VENDOR_ATTR_CSI_DATA_VER,
+	MTK_VENDOR_ATTR_CSI_DATA_TS,
+	MTK_VENDOR_ATTR_CSI_DATA_RSSI,
+	MTK_VENDOR_ATTR_CSI_DATA_SNR,
+	MTK_VENDOR_ATTR_CSI_DATA_BW,
+	MTK_VENDOR_ATTR_CSI_DATA_CH_IDX,
+	MTK_VENDOR_ATTR_CSI_DATA_TA,
+	MTK_VENDOR_ATTR_CSI_DATA_I,
+	MTK_VENDOR_ATTR_CSI_DATA_Q,
+	MTK_VENDOR_ATTR_CSI_DATA_INFO,
+	MTK_VENDOR_ATTR_CSI_DATA_RSVD1,
+	MTK_VENDOR_ATTR_CSI_DATA_RSVD2,
+	MTK_VENDOR_ATTR_CSI_DATA_RSVD3,
+	MTK_VENDOR_ATTR_CSI_DATA_RSVD4,
+	MTK_VENDOR_ATTR_CSI_DATA_TX_ANT,
+	MTK_VENDOR_ATTR_CSI_DATA_RX_ANT,
+	MTK_VENDOR_ATTR_CSI_DATA_MODE,
+	MTK_VENDOR_ATTR_CSI_DATA_H_IDX,
+
+	/* keep last */
+	NUM_MTK_VENDOR_ATTRS_CSI_DATA,
+	MTK_VENDOR_ATTR_CSI_DATA_MAX =
+		NUM_MTK_VENDOR_ATTRS_CSI_DATA - 1
+};
+
+enum mtk_vendor_attr_mnt_ctrl {
+	MTK_VENDOR_ATTR_AMNT_CTRL_UNSPEC,
+
+	MTK_VENDOR_ATTR_AMNT_CTRL_SET,
+	MTK_VENDOR_ATTR_AMNT_CTRL_DUMP,
+	/* keep last */
+	NUM_MTK_VENDOR_ATTRS_AMNT_CTRL,
+	MTK_VENDOR_ATTR_AMNT_CTRL_MAX =
+		NUM_MTK_VENDOR_ATTRS_AMNT_CTRL - 1
+};
+
+enum mtk_vendor_attr_mnt_set {
+	MTK_VENDOR_ATTR_AMNT_SET_UNSPEC,
+
+	MTK_VENDOR_ATTR_AMNT_SET_INDEX,
+	MTK_VENDOR_ATTR_AMNT_SET_MACADDR,
+
+	/* keep last */
+	NUM_MTK_VENDOR_ATTRS_AMNT_SET,
+	MTK_VENDOR_ATTR_AMNT_SET_MAX =
+		NUM_MTK_VENDOR_ATTRS_AMNT_SET - 1
+};
+
+enum mtk_vendor_attr_mnt_dump {
+	MTK_VENDOR_ATTR_AMNT_DUMP_UNSPEC,
+
+	MTK_VENDOR_ATTR_AMNT_DUMP_INDEX,
+	MTK_VENDOR_ATTR_AMNT_DUMP_LEN,
+	MTK_VENDOR_ATTR_AMNT_DUMP_RESULT,
+
+	/* keep last */
+	NUM_MTK_VENDOR_ATTRS_AMNT_DUMP,
+	MTK_VENDOR_ATTR_AMNT_DUMP_MAX =
+		NUM_MTK_VENDOR_ATTRS_AMNT_DUMP - 1
+};
+
+enum mtk_vendor_attr_wireless_ctrl {
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_UNSPEC,
+
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_FIXED_MCS,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_FIXED_OFDMA,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_PPDU_TX_TYPE,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_NUSERS_OFDMA,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_BA_BUFFER_SIZE,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_MIMO,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_AMPDU,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_AMSDU,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_CERT,
+
+	/* keep last */
+	NUM_MTK_VENDOR_ATTRS_WIRELESS_CTRL,
+	MTK_VENDOR_ATTR_WIRELESS_CTRL_MAX =
+		NUM_MTK_VENDOR_ATTRS_WIRELESS_CTRL - 1
+};
+
+enum mtk_vendor_attr_rfeature_ctrl {
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_UNSPEC,
+
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_GI,
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_HE_LTF,
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_CFG,
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE_EN,
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_TRIG_TYPE,
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_ACK_PLCY,
+
+	/* keep last */
+	NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL,
+	MTK_VENDOR_ATTR_RFEATURE_CTRL_MAX =
+		NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL - 1
+};
+
+#define CSI_MAX_COUNT 256
+#define ETH_ALEN 6
+
+struct csi_data {
+	s16 data_i[CSI_MAX_COUNT];
+	s16 data_q[CSI_MAX_COUNT];
+	s8 rssi;
+	u8 snr;
+	u32 ts;
+	u8 data_bw;
+	u8 pri_ch_idx;
+	u8 ta[ETH_ALEN];
+	u32 info;
+	u8 rx_mode;
+	u32 h_idx;
+	u16 tx_idx;
+	u16 rx_idx;
+};
+
+struct amnt_data {
+	u8 idx;
+	u8 addr[ETH_ALEN];
+	s8 rssi[4];
+	u32 last_seen;
+};
+
+extern struct unl unl;
+
+int mt76_csi_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx);
+int mt76_csi_dump(struct nl_msg *nlmsg, int argc,
+	char **argv, void *ctx);
+
+int mt76_amnt_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx);
+int mt76_amnt_dump(struct nl_msg *msg, int argc,
+	char **argv, void *ctx);
+
+int mt76_ap_rfeatures_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx);
+int mt76_ap_wireless_set(struct nl_msg *msg, int argc,
+	char **argv, void *ctx);
+
+int handle_set_command(struct nl_msg *msg, int argc,
+	char **argv, void *ctx);
+
+#endif
diff --git a/feed/mwctl/src/mwctl.h b/feed/mwctl/src/mwctl.h
new file mode 100755
index 0000000..15bf92e
--- /dev/null
+++ b/feed/mwctl/src/mwctl.h
@@ -0,0 +1,112 @@
+#ifndef __WM_H

+#define __WM_H

+

+#include <stdbool.h>

+#include <netlink/netlink.h>

+#include <netlink/genl/genl.h>

+#include <netlink/genl/family.h>

+#include <netlink/genl/ctrl.h>

+#include <endian.h>

+

+enum command_identify_by {

+	CIB_NONE,

+	CIB_PHY,

+	CIB_NETDEV,

+	CIB_WDEV,

+};

+

+/* libnl 1.x compatibility code */

+#if !defined(CONFIG_LIBNL20) && !defined(CONFIG_LIBNL30)

+#  define nl_sock nl_handle

+#endif

+

+struct nl80211_state {

+	struct nl_sock *nl_sock;

+	int nl80211_id;

+};

+

+enum id_input {

+	II_NONE,

+	II_NETDEV,

+	II_PHY_NAME,

+	II_PHY_IDX,

+	II_WDEV,

+};

+

+struct cmd {

+	const char *name;

+	const char *args;

+	const char *help;

+	const enum mtk_nl80211_vendor_commands cmd;

+	int nl_msg_flags;

+	int hidden;

+	const enum command_identify_by idby;

+	/*

+	 * The handler should return a negative error code,

+	 * zero on success, 1 if the arguments were wrong.

+	 * Return 2 iff you provide the error message yourself.

+	 */

+	int (*handler)(struct nl_msg *msg, int argc, 

+					char **argv, void *ctx);

+	const struct cmd *(*selector)(int argc, char **argv);

+	const struct cmd *parent;

+};

+#ifndef ARRAY_SIZE

+#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof(ar[0]))

+#endif

+#ifndef DIV_ROUND_UP

+#define DIV_ROUND_UP(x, y) (((x) + (y - 1)) / (y))

+#endif

+#define __COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel)\

+	static struct cmd						\

+	__cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden = {\

+		.name = (_name),					\

+		.args = (_args),					\

+		.cmd = (_nlcmd),					\

+		.nl_msg_flags = (_flags),				\

+		.hidden = (_hidden),					\

+		.idby = (_idby),					\

+		.handler = (_handler),					\

+		.help = (_help),					\

+		.parent = _section,					\

+		.selector = (_sel),					\

+	};								\

+	static struct cmd *__cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden ## _p \

+	__attribute__((used,section("__cmd"))) =			\

+	&__cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden

+#define __ACMD(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel, _alias)\

+	__COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel);\

+	static const struct cmd *_alias = &__cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden

+#define COMMAND(section, name, args, cmd, flags, idby, handler, help)	\

+	__COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, NULL)

+#define COMMAND_ALIAS(section, name, args, cmd, flags, idby, handler, help, selector, alias)\

+	__ACMD(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, selector, alias)

+#define HIDDEN(section, name, args, cmd, flags, idby, handler)		\

+	__COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 1, idby, handler, NULL, NULL)

+

+#define TOPLEVEL(_name, _args, _nlcmd, _flags, _idby, _handler, _help)	\

+	struct cmd __section ## _ ## _name = {				\

+		.name = (#_name),					\

+		.args = (_args),					\

+		.cmd = (_nlcmd),					\

+		.nl_msg_flags = (_flags),				\

+		.idby = (_idby),					\

+		.handler = (_handler),					\

+		.help = (_help),					\

+	 };								\

+	static struct cmd *__section ## _ ## _name ## _p		\

+	__attribute__((used,section("__cmd"))) = &__section ## _ ## _name

+

+#define SECTION(_name)							\

+	struct cmd __section ## _ ## _name = {				\

+		.name = (#_name),					\

+		.hidden = 1,						\

+	};								\

+	static struct cmd *__section ## _ ## _name ## _p		\

+	__attribute__((used,section("__cmd"))) = &__section ## _ ## _name

+

+#define DECLARE_SECTION(_name)						\

+	extern struct cmd __section ## _ ## _name;

+

+void register_handler(int (*handler)(struct nl_msg *, void *), void *data);

+#endif