[rdkb][common][app][Add mt76-vendor for hal]
[Description]
Add mt76-vendor for nl80211 vendor command for hal.
The tool may be deprecated in future, so it is a temporary workaround for vendor command.
[Release-log]
N/A
diff --git a/recipes-devtools/mt76-vendor/files/COPYING b/recipes-devtools/mt76-vendor/files/COPYING
new file mode 100644
index 0000000..7975bdb
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/COPYING
@@ -0,0 +1 @@
+Copyright (c) Mediatek 2020
diff --git a/recipes-devtools/mt76-vendor/files/src/CMakeLists.txt b/recipes-devtools/mt76-vendor/files/src/CMakeLists.txt
new file mode 100644
index 0000000..dd6ef76
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/CMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 2.8)
+
+PROJECT(mt76-vendor C)
+ADD_DEFINITIONS(-Os -Wall --std=gnu99 -g3)
+
+ADD_EXECUTABLE(mt76-vendor main.c csi.c amnt.c capi.c hemu.c phy_capa.c)
+TARGET_LINK_LIBRARIES(mt76-vendor nl-tiny)
+
+SET(CMAKE_INSTALL_PREFIX /usr)
+
+INSTALL(TARGETS mt76-vendor
+ RUNTIME DESTINATION sbin
+)
diff --git a/recipes-devtools/mt76-vendor/files/src/Makefile b/recipes-devtools/mt76-vendor/files/src/Makefile
new file mode 100644
index 0000000..6b4da99
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/Makefile
@@ -0,0 +1,30 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=mt76-vendor
+PKG_RELEASE=1
+
+PKG_LICENSE:=GPLv2
+PKG_LICENSE_FILES:=
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+
+CMAKE_SOURCE_DIR:=$(PKG_BUILD_DIR)
+CMAKE_BINARY_DIR:=$(PKG_BUILD_DIR)
+
+define Package/mt76-vendor
+ SECTION:=devel
+ CATEGORY:=Development
+ TITLE:=vendor cmd for mt76
+ DEPENDS:=+libnl-tiny
+endef
+
+TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny
+
+define Package/mt76-vendor/install
+ mkdir -p $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/mt76-vendor $(1)/usr/sbin
+ ln -s /usr/sbin/mt76-vendor $(1)/usr/sbin/mwctl
+endef
+
+$(eval $(call BuildPackage,mt76-vendor))
diff --git a/recipes-devtools/mt76-vendor/files/src/amnt.c b/recipes-devtools/mt76-vendor/files/src/amnt.c
new file mode 100644
index 0000000..4a98c31
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/amnt.c
@@ -0,0 +1,184 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mt76-vendor.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(int idx, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data;
+ int ret;
+
+ if (argc < 1)
+ 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, 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_AMNT_CTRL))
+ return false;
+
+ 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);
+
+ ret = unl_genl_request(&unl, msg, NULL, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+ unl_free(&unl);
+
+ 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(int idx, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data, *tb1;
+ int ret = -EINVAL;
+ u8 amnt_idx;
+
+ if (argc < 1)
+ 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, true);
+
+ if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, 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_AMNT_CTRL))
+ return false;
+
+ data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+ if (!data)
+ goto out;
+
+ tb1 = nla_nest_start(msg, MTK_VENDOR_ATTR_AMNT_CTRL_DUMP | NLA_F_NESTED);
+ if (!tb1)
+ goto out;
+
+ 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);
+
+ ret = unl_genl_request(&unl, msg, mt76_amnt_dump_cb, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+out:
+ unl_free(&unl);
+
+ return ret;
+}
diff --git a/recipes-devtools/mt76-vendor/files/src/capi.c b/recipes-devtools/mt76-vendor/files/src/capi.c
new file mode 100644
index 0000000..99a2d4d
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/capi.c
@@ -0,0 +1,150 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mt76-vendor.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(int idx, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data;
+ int ret;
+
+ if (argc < 1)
+ 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, 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_RFEATURE_CTRL))
+ return false;
+
+ 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);
+
+ ret = unl_genl_request(&unl, msg, NULL, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+ unl_free(&unl);
+
+ 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(int idx, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data;
+ int ret;
+
+ if (argc < 1)
+ 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, 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_WIRELESS_CTRL))
+ return false;
+
+ 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);
+
+ ret = unl_genl_request(&unl, msg, NULL, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+ unl_free(&unl);
+
+ return ret;
+}
diff --git a/recipes-devtools/mt76-vendor/files/src/csi.c b/recipes-devtools/mt76-vendor/files/src/csi.c
new file mode 100644
index 0000000..d99eb58
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/csi.c
@@ -0,0 +1,351 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mt76-vendor.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;
+}
+
+int mt76_csi_dump(int idx, int argc, char **argv)
+{
+ int pkt_num, ret = 0, i;
+ struct nl_msg *msg;
+ void *data;
+
+ 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));
+
+ for (i = 0; i < pkt_num / CSI_DUMP_PER_NUM; i++) {
+ 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, true);
+
+ if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, 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, msg, mt76_csi_dump_cb, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+ unl_free(&unl);
+ }
+
+ 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(int idx, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data;
+ int ret;
+
+ if (argc < 1)
+ 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, 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;
+
+ mt76_csi_set_attr(msg, argc, argv);
+
+ nla_nest_end(msg, data);
+
+ ret = unl_genl_request(&unl, msg, NULL, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+ unl_free(&unl);
+
+ return ret;
+}
diff --git a/recipes-devtools/mt76-vendor/files/src/hemu.c b/recipes-devtools/mt76-vendor/files/src/hemu.c
new file mode 100755
index 0000000..ec1cea0
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/hemu.c
@@ -0,0 +1,58 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mt76-vendor.h"
+
+static int mt76_hemu_onoff_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], "onoff", 5))
+ nla_put_u8(msg, MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF, strtoul(val, NULL, 0));
+
+ return 0;
+}
+
+int mt76_hemu_onoff_set(int idx, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data;
+ int ret;
+
+ if (argc < 1)
+ 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, 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_HEMU_CTRL))
+ return false;
+
+ data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+ if (!data)
+ return -ENOMEM;
+
+ mt76_hemu_onoff_set_attr(msg, argc, argv);
+
+ nla_nest_end(msg, data);
+
+ ret = unl_genl_request(&unl, msg, NULL, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+ unl_free(&unl);
+
+ return ret;
+}
\ No newline at end of file
diff --git a/recipes-devtools/mt76-vendor/files/src/main.c b/recipes-devtools/mt76-vendor/files/src/main.c
new file mode 100644
index 0000000..27913a7
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/main.c
@@ -0,0 +1,91 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include <net/if.h>
+
+#include "mt76-vendor.h"
+
+static const char *progname;
+struct unl unl;
+
+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>",
+
+ "set hemu onoff=<val> (bitmap- UL MU-MIMO(bit3), DL MU-MIMO(bit2), UL OFDMA(bit1), DL OFDMA(bit0))",
+
+ "dump phy_capa",
+ };
+ int i;
+
+ fprintf(stderr, "Usage:\n");
+ for (i = 0; i < ARRAY_SIZE(commands); i++)
+ printf(" %s wifiX %s\n", progname, commands[i]);
+
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ const char *cmd, *subcmd;
+ int if_idx, ret = 0;
+
+ progname = argv[0];
+ if (argc < 4)
+ usage();
+
+ if_idx = if_nametoindex(argv[1]);
+ if (!if_idx) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return 2;
+ }
+
+ cmd = argv[2];
+ subcmd = argv[3];
+ argv += 4;
+ argc -= 4;
+
+ if (!strncmp(cmd, "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(subcmd, "phy_capa", 4))
+ ret = mt76_phy_capa_dump(if_idx, argc, argv);
+ } else if (!strncmp(cmd, "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 if (!strncmp(subcmd, "hemu", 4))
+ ret = mt76_hemu_onoff_set(if_idx, argc, argv);
+ } else {
+ usage();
+ }
+
+ return ret;
+}
diff --git a/recipes-devtools/mt76-vendor/files/src/mt76-vendor.h b/recipes-devtools/mt76-vendor/files/src/mt76-vendor.h
new file mode 100644
index 0000000..2d39cb4
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/mt76-vendor.h
@@ -0,0 +1,241 @@
+// 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_nl80211_vendor_subcmds {
+ MTK_NL80211_VENDOR_SUBCMD_AMNT_CTRL = 0xae,
+ MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL = 0xc2,
+ MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL = 0xc3,
+ MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL = 0xc4,
+ MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL = 0xc5,
+ MTK_NL80211_VENDOR_SUBCMD_PHY_CAPA_CTRL = 0xc6,
+};
+
+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_hemu_ctrl {
+ MTK_VENDOR_ATTR_HEMU_CTRL_UNSPEC,
+
+ MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF,
+
+ /* keep last */
+ NUM_MTK_VENDOR_ATTRS_HEMU_CTRL,
+ MTK_VENDOR_ATTR_HEMU_CTRL_MAX =
+ NUM_MTK_VENDOR_ATTRS_HEMU_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
+};
+
+enum mtk_vendor_attr_phy_capa_ctrl {
+ MTK_VENDOR_ATTR_PHY_CAPA_CTRL_UNSPEC,
+
+ MTK_VENDOR_ATTR_PHY_CAPA_CTRL_SET,
+ MTK_VENDOR_ATTR_PHY_CAPA_CTRL_DUMP,
+
+ /* keep last */
+ NUM_MTK_VENDOR_ATTRS_PHY_CAPA_CTRL,
+ MTK_VENDOR_ATTR_PHY_CAPA_CTRL_MAX =
+ NUM_MTK_VENDOR_ATTRS_PHY_CAPA_CTRL - 1
+};
+
+enum mtk_vendor_attr_phy_capa_dump {
+ MTK_VENDOR_ATTR_PHY_CAPA_DUMP_UNSPEC,
+
+ MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX_SUPPORTED_BSS,
+ MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX_SUPPORTED_STA,
+
+ /* keep last */
+ NUM_MTK_VENDOR_ATTRS_PHY_CAPA_DUMP,
+ MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX =
+ NUM_MTK_VENDOR_ATTRS_PHY_CAPA_DUMP - 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(int idx, int argc, char **argv);
+int mt76_csi_dump(int idx, int argc, char **argv);
+
+int mt76_amnt_set(int idx, int argc, char **argv);
+int mt76_amnt_dump(int idx, int argc, char **argv);
+
+int mt76_ap_rfeatures_set(int idx, int argc, char **argv);
+int mt76_ap_wireless_set(int idx, int argc, char **argv);
+
+int mt76_hemu_onoff_set(int idx, int argc, char **argv);
+
+int mt76_phy_capa_dump(int idx, int argc, char **argv);
+#endif
diff --git a/recipes-devtools/mt76-vendor/files/src/phy_capa.c b/recipes-devtools/mt76-vendor/files/src/phy_capa.c
new file mode 100644
index 0000000..6bb1e18
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/files/src/phy_capa.c
@@ -0,0 +1,81 @@
+/* Copyright (C) 2021 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include "mt76-vendor.h"
+
+static struct nla_policy
+phy_capa_ctrl_policy[NUM_MTK_VENDOR_ATTRS_PHY_CAPA_CTRL] = {
+ [MTK_VENDOR_ATTR_PHY_CAPA_CTRL_SET] = { .type = NLA_NESTED },
+ [MTK_VENDOR_ATTR_PHY_CAPA_CTRL_DUMP] = { .type = NLA_NESTED },
+};
+
+static struct nla_policy
+phy_capa_dump_policy[NUM_MTK_VENDOR_ATTRS_PHY_CAPA_DUMP] = {
+ [MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX_SUPPORTED_BSS] = { .type = NLA_U16 },
+ [MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX_SUPPORTED_STA] = { .type = NLA_U16 },
+};
+
+static int mt76_phy_capa_dump_cb(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_PHY_CAPA_CTRL];
+ struct nlattr *tb_dump[NUM_MTK_VENDOR_ATTRS_PHY_CAPA_DUMP];
+ struct nlattr *attr;
+ u16 max_bss, max_sta;
+
+ 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_PHY_CAPA_CTRL_MAX,
+ attr, phy_capa_ctrl_policy);
+
+ if (!tb[MTK_VENDOR_ATTR_PHY_CAPA_CTRL_DUMP])
+ return NL_SKIP;
+
+ nla_parse_nested(tb_dump, MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX,
+ tb[MTK_VENDOR_ATTR_PHY_CAPA_CTRL_DUMP], phy_capa_dump_policy);
+
+ max_bss = nla_get_u16(tb_dump[MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX_SUPPORTED_BSS]);
+ max_sta = nla_get_u16(tb_dump[MTK_VENDOR_ATTR_PHY_CAPA_DUMP_MAX_SUPPORTED_STA]);
+
+ printf("[vendor] Max Supported BSS=%u "
+ " Max Supported STA=%u\n", max_bss, max_sta);
+
+ return 0;
+}
+
+int mt76_phy_capa_dump(int idx, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data;
+ int ret = -EINVAL;
+
+ 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, true);
+
+ if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, 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_PHY_CAPA_CTRL))
+ return false;
+
+ data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
+ if (!data)
+ goto out;
+
+ nla_nest_end(msg, data);
+
+ ret = unl_genl_request(&unl, msg, mt76_phy_capa_dump_cb, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+out:
+ unl_free(&unl);
+
+ return ret;
+}
diff --git a/recipes-devtools/mt76-vendor/mt76-vendor.bb b/recipes-devtools/mt76-vendor/mt76-vendor.bb
new file mode 100644
index 0000000..a3fc7f2
--- /dev/null
+++ b/recipes-devtools/mt76-vendor/mt76-vendor.bb
@@ -0,0 +1,18 @@
+DESCRIPTION = "mt76-vendor"
+SECTION = "applications"
+LICENSE = "GPLv2"
+LIC_FILES_CHKSUM = "file://COPYING;md5=c188eeeb69c0a05d0545816f1458a0c9"
+
+DEPENDS += "libnl-tiny"
+
+inherit pkgconfig cmake
+
+SRC_URI = " \
+ file://COPYING;subdir=git/src \
+ file://src;subdir=git \
+ "
+
+S = "${WORKDIR}/git/src"
+
+CFLAGS_append = " -I=${includedir}/libnl-tiny "
+