[][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/Makefile b/feed/mwctl/Makefile
new file mode 100755
index 0000000..266c583
--- /dev/null
+++ b/feed/mwctl/Makefile
@@ -0,0 +1,31 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=mwctl
+PKG_RELEASE=1
+
+PKG_LICENSE:=GPLv2
+PKG_LICENSE_FILES:=
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+include $(INCLUDE_DIR)/kernel.mk
+
+CMAKE_SOURCE_DIR:=$(PKG_BUILD_DIR)
+CMAKE_BINARY_DIR:=$(PKG_BUILD_DIR)
+
+define Package/mwctl
+ SECTION:=devel
+ CATEGORY:=Development
+ TITLE:=vendor cmd for logan/mt76 driver
+ DEPENDS:=+libnl-tiny
+endef
+
+TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny
+TARGET_CFLAGS += -I$(LINUX_DIR)/include/uapi/linux/mtk_nl80211_inc
+
+define Package/mwctl/install
+ mkdir -p $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/mwctl $(1)/usr/sbin
+endef
+
+$(eval $(call BuildPackage,mwctl))
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(¶m, 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), ¶m))
+ return -EMSGSIZE;
+ } else {
+ if (nla_put(msg, MTK_NL80211_VENDOR_ATTR_MAC_SHOW_PARAM, sizeof(param), ¶m))
+ 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