[][MAC80211][misc][preliminary version of Filogic 680 on Filogic 880]
[Description]
Add preliminary version of Filogic 680 on Filogic 880.
Based on mt76 revision: 269df4b01f27 ("wifi: mt76: fix rx checksum offload on mt7615/mt7915/mt7921")
This series adds mt7996, a new mac80211 driver for MediaTek Wi-Fi 7
(802.11be) devices, which currently supports AP, station, mesh, and
monitor modes.
mt7996 first supports Filogic 680, which is a Wi-Fi 7 chipset supporting
concurrent tri-band operation at 6 GHz, 5 GHz, and 2.4 GHz with 4x4
antennas on each band. There are several variants that will be added in
upcoming patches. For more details, please refer to [1].
mt7996 supports only Wi-Fi 6E at the moment, whereas Wi-Fi 7 and its
specific features are work in progress. They will be introduced in
further patches.
[1] https://corp.mediatek.com/news-events/press-releases/mediatek-announces-worlds-first-complete-wi-fi-7-platforms-for-access-points-and-clients
[Release-log]
N/A
Change-Id: I7d3dea2626556751c9b0462e587743fad5287be0
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/6709775
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/CMakeLists.txt b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/CMakeLists.txt
new file mode 100644
index 0000000..3a83e34
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/CMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 2.8)
+
+PROJECT(mt76-test C)
+ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3)
+
+ADD_EXECUTABLE(mt76-test main.c fields.c eeprom.c fwlog.c)
+TARGET_LINK_LIBRARIES(mt76-test nl-tiny)
+
+SET(CMAKE_INSTALL_PREFIX /usr)
+
+INSTALL(TARGETS mt76-test
+ RUNTIME DESTINATION sbin
+)
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/README.md b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/README.md
new file mode 100644
index 0000000..d79beaa
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/README.md
@@ -0,0 +1,48 @@
+# MT76 testmode utility
+
+This utility can be used to perform some support functions required for Rx/Tx calibration, similar to the ATE command set of the SDK driver.
+Its main functionality is setting test parameters and dumping statistics. It can also be used to prepare modified EEPROM data for writing into flash.
+
+## Basic syntax
+
+- Set parameters:
+ - `mt76-test phy0 set <parameter>=<value>`[...]
+- Show current parameter set:
+ - `mt76-test phy0 dump`
+- Show statistics
+ - `mt76-test phy0 dump stats`
+
+## Running tests
+
+The test state is controlled through the `state` parameter. The following state values are supported:
+
+- `off`: Normal operation (default)
+- `idle`: Testmode enabled, but no specific test active
+- `tx_frames`: Send a number of packets with configurable rate/txpower
+- `rx_frames`: Receive packets and show RSSI and packet count/PER
+
+Setting a state activates it even if the value is the same as before. Setting it to `tx_frames` triggers sending packets immediately. Setting `rx_frames` enables receive mode and can also be used to clear rx statistics.
+
+## Notes
+
+To run tests, you first need to disable all normal interfaces, set up a monitor mode interface and configure it to the channel/bandwidth you intend to use.
+
+
+## Parameters:
+
+| Parameter name | ATE parameter | Description |
+|--|--|--|
+| `state` | `ATE` | Test state |
+| `tx_count` | `ATETXCNT` | Number of packets to send |
+| `tx_length` | `ATETXLEN` | Length of packets to send |
+| `tx_rate_mode` | `ATETXMODE` | PHY mode (possible values: `cck`, `ofdm`, `ht`, `vht`) |
+| `tx_rate_nss` | | Number of spatial streams (VHT only) |
+| `tx_rate_idx` | `ATETXMCS` | MCS or legacy rate index |
+| `tx_rate_sgi` | `ATETXGI` | Enable short guard interval |
+| `tx_rate_ldpc` | `ATETXLDPC` | Enable LDPC |
+| `tx_power_control` | `ATETXPOWERCTRL` | Firmware transmit power control feature |
+| `tx_power` | `ATETXPOW0-3` | Per-chain half-dBm transmit power, `0` means default value, e.g. `10,0,0,0` |
+| `tx_antenna` | `ATETXANT` | Transmit antenna bitmask |
+| `freq_offset` | `ATETXFREQOFFSET` | Frequency offset |
+
+
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/eeprom.c b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/eeprom.c
new file mode 100644
index 0000000..68ad8d0
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/eeprom.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: ISC
+/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+#define _GNU_SOURCE
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "mt76-test.h"
+
+static const char *mtd_part;
+static uint32_t mtd_offset;
+
+static char *eeprom_file;
+static int eeprom_fd = -1;
+unsigned char *eeprom_data;
+
+static int mt76_eeprom_dump_cb(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *tb[NUM_MT76_TM_ATTRS];
+ struct nlattr *attr;
+
+ attr = unl_find_attr(&unl, msg, NL80211_ATTR_TESTDATA);
+ if (!attr)
+ return NL_SKIP;
+
+ nla_parse_nested(tb, MT76_TM_ATTR_MAX, attr, msg_field.policy);
+ if (!tb[MT76_TM_ATTR_MTD_PART] || !tb[MT76_TM_ATTR_MTD_OFFSET])
+ return NL_SKIP;
+
+ mtd_part = strdup(nla_get_string(tb[MT76_TM_ATTR_MTD_PART]));
+ mtd_offset = nla_get_u32(tb[MT76_TM_ATTR_MTD_OFFSET]);
+
+ return NL_SKIP;
+}
+
+static FILE *mtd_open(const char *mtd)
+{
+ char line[128], name[64];
+ FILE *fp;
+ int i;
+
+ fp = fopen("/proc/mtd", "r");
+ if (!fp)
+ return NULL;
+
+ snprintf(name, sizeof(name), "\"%s\"", mtd);
+ while (fgets(line, sizeof(line), fp)) {
+ if (!sscanf(line, "mtd%d:", &i) || !strstr(line, name))
+ continue;
+
+ snprintf(line, sizeof(line), "/dev/mtd%d", i);
+ fclose(fp);
+ return fopen(line, "r");
+ }
+ fclose(fp);
+
+ return NULL;
+}
+
+
+static int
+mt76_eeprom_create_file(void)
+{
+ char buf[1024];
+ ssize_t len;
+ FILE *f;
+ int fd;
+
+ f = mtd_open(mtd_part);
+ if (!f) {
+ fprintf(stderr, "Failed to open MTD device\n");
+ return -1;
+ }
+
+ fd = open(eeprom_file, O_RDWR | O_CREAT | O_EXCL, 00644);
+ if (fd < 0)
+ goto out;
+
+ while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
+ ssize_t w;
+
+retry:
+ w = write(fd, buf, len);
+ if (w > 0)
+ continue;
+
+ if (errno == EINTR)
+ goto retry;
+
+ perror("write");
+ unlink(eeprom_file);
+ close(fd);
+ fd = -1;
+ goto out;
+ }
+
+ lseek(fd, 0, SEEK_SET);
+
+out:
+ fclose(f);
+ return fd;
+}
+
+static bool
+mt76_eeprom_file_exists(void)
+{
+ struct stat st;
+
+ return stat(eeprom_file, &st) == 0;
+}
+
+static int
+mt76_eeprom_init_file(void)
+{
+ int fd;
+
+ if (!mt76_eeprom_file_exists())
+ return mt76_eeprom_create_file();
+
+ fd = open(eeprom_file, O_RDWR);
+ if (fd < 0)
+ perror("open");
+
+ return fd;
+}
+
+int mt76_eeprom_init(int phy)
+{
+ struct nl_msg *msg;
+
+ msg = unl_genl_msg(&unl, NL80211_CMD_TESTMODE, true);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, phy);
+ unl_genl_request(&unl, msg, mt76_eeprom_dump_cb, NULL);
+
+ if (!mtd_part) {
+ fprintf(stderr, "Could not find MTD partition information\n");
+ return -1;
+ }
+
+ eeprom_file = malloc(sizeof(EEPROM_FILE_PATH_FMT) + strlen(mtd_part));
+ sprintf(eeprom_file, EEPROM_FILE_PATH_FMT, mtd_part);
+
+ eeprom_fd = mt76_eeprom_init_file();
+ if (eeprom_fd < 0)
+ return -1;
+
+ eeprom_data = mmap(NULL, EEPROM_PART_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, eeprom_fd, mtd_offset);
+ if (!eeprom_data) {
+ perror("mmap");
+ close(eeprom_fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+void mt76_eeprom_close(void)
+{
+ if (eeprom_fd < 0)
+ return;
+
+ msync(eeprom_data, EEPROM_PART_SIZE, MS_SYNC);
+ munmap(eeprom_data, EEPROM_PART_SIZE);
+ close(eeprom_fd);
+ eeprom_fd = -1;
+}
+
+static int
+mt76_eeprom_set(int argc, char **argv)
+{
+ for (; argc > 0; argc--, argv++) {
+ char *addr_str = argv[0];
+ char *val_str = strchr(addr_str, '=');
+ unsigned long addr, val;
+ char *err;
+
+ if (!val_str) {
+ fprintf(stderr, "Invalid argument: %s\n", addr_str);
+ return 1;
+ }
+
+ *(val_str++) = 0;
+
+ addr = strtoul(addr_str, &err, 0);
+ if ((err && *err) || addr >= EEPROM_PART_SIZE) {
+ fprintf(stderr, "Invalid address: %s\n", addr_str);
+ return 1;
+ }
+
+ val = strtoul(val_str, &err, 0);
+ if ((err && *err) || val >= 0xff) {
+ fprintf(stderr, "Invalid value: %s\n", val_str);
+ return 1;
+ }
+
+ eeprom_data[addr] = val;
+ }
+
+ return 0;
+}
+
+static int
+mt76_eeprom_changes(void)
+{
+ unsigned char *buf;
+ FILE *f;
+ int i;
+
+ f = mtd_open(mtd_part);
+ if (!f) {
+ fprintf(stderr, "Cannot open MTD device\n");
+ return 1;
+ }
+
+ buf = malloc(EEPROM_PART_SIZE);
+ fseek(f, mtd_offset, SEEK_SET);
+ fread(buf, 1, EEPROM_PART_SIZE, f);
+ for (i = 0; i < EEPROM_PART_SIZE; i++) {
+ if (buf[i] == eeprom_data[i])
+ continue;
+
+ printf("[%04x] %02x => %02x\n", i, buf[i], eeprom_data[i]);
+ }
+ free(buf);
+
+ return 0;
+}
+
+
+int mt76_eeprom(int phy, int argc, char **argv)
+{
+ const char *cmd;
+ int ret = 0;
+
+ if (argc < 1)
+ usage();
+
+ if (mt76_eeprom_init(phy))
+ return 1;
+
+ cmd = argv[0];
+ argv++;
+ argc--;
+
+ if (!strcmp(cmd, "file"))
+ printf("%s\n", eeprom_file);
+ else if (!strcmp(cmd, "set"))
+ ret = mt76_eeprom_set(argc, argv);
+ else if (!strcmp(cmd, "reset"))
+ unlink(eeprom_file);
+ else if (!strcmp(cmd, "changes"))
+ ret = mt76_eeprom_changes();
+
+ mt76_eeprom_close();
+
+ return ret;
+}
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/fields.c b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/fields.c
new file mode 100644
index 0000000..e3f6908
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/fields.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: ISC
+/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+#define _GNU_SOURCE
+#include "mt76-test.h"
+
+static char prefix[64];
+
+static const char * const testmode_state[] = {
+ [MT76_TM_STATE_OFF] = "off",
+ [MT76_TM_STATE_IDLE] = "idle",
+ [MT76_TM_STATE_TX_FRAMES] = "tx_frames",
+ [MT76_TM_STATE_RX_FRAMES] = "rx_frames",
+};
+
+static const char * const testmode_tx_mode[] = {
+ [MT76_TM_TX_MODE_CCK] = "cck",
+ [MT76_TM_TX_MODE_OFDM] = "ofdm",
+ [MT76_TM_TX_MODE_HT] = "ht",
+ [MT76_TM_TX_MODE_VHT] = "vht",
+ [MT76_TM_TX_MODE_HE_SU] = "he_su",
+ [MT76_TM_TX_MODE_HE_EXT_SU] = "he_ext_su",
+ [MT76_TM_TX_MODE_HE_TB] = "he_tb",
+ [MT76_TM_TX_MODE_HE_MU] = "he_mu",
+};
+
+static void print_enum(const struct tm_field *field, struct nlattr *attr)
+{
+ unsigned int i = nla_get_u8(attr);
+
+ if (i < field->enum_len && field->enum_str[i])
+ printf("%s", field->enum_str[i]);
+ else
+ printf("unknown (%d)", i);
+}
+
+static bool parse_enum(const struct tm_field *field, int idx,
+ struct nl_msg *msg, const char *val)
+{
+ int i;
+
+ for (i = 0; i < field->enum_len; i++) {
+ if (strcmp(field->enum_str[i], val) != 0)
+ continue;
+
+ if (nla_put_u8(msg, idx, i))
+ return false;
+
+ return true;
+ }
+
+ printf("Invalid value for parameter '%s': %s\n", field->name, val);
+ printf("Possible values:");
+ for (i = 0; i < field->enum_len; i++)
+ printf(" %s", field->enum_str[i]);
+ printf("\n");
+
+ return false;
+}
+
+static bool parse_u8(const struct tm_field *field, int idx,
+ struct nl_msg *msg, const char *val)
+{
+ return !nla_put_u8(msg, idx, strtoul(val, NULL, 0));
+}
+
+static void print_u8(const struct tm_field *field, struct nlattr *attr)
+{
+ printf("%d", nla_get_u8(attr));
+}
+
+static void print_s8(const struct tm_field *field, struct nlattr *attr)
+{
+ printf("%d", (int8_t)nla_get_u8(attr));
+}
+
+static bool parse_u32(const struct tm_field *field, int idx,
+ struct nl_msg *msg, const char *val)
+{
+ return !nla_put_u32(msg, idx, strtoul(val, NULL, 0));
+}
+
+static void print_s32(const struct tm_field *field, struct nlattr *attr)
+{
+ printf("%d", (int32_t)nla_get_u32(attr));
+}
+
+static void print_u32(const struct tm_field *field, struct nlattr *attr)
+{
+ printf("%d", nla_get_u32(attr));
+}
+
+static void print_u64(const struct tm_field *field, struct nlattr *attr)
+{
+ printf("%lld", (unsigned long long)nla_get_u64(attr));
+}
+
+static bool parse_flag(const struct tm_field *field, int idx,
+ struct nl_msg *msg, const char *val)
+{
+ bool set = strtoul(val, NULL, 0);
+
+ if (!set)
+ return true;
+
+ return !nla_put_flag(msg, idx);
+}
+
+static void print_string(const struct tm_field *field, struct nlattr *attr)
+{
+ printf("%s", nla_get_string(attr));
+}
+
+static bool parse_array(const struct tm_field *field, int idx,
+ struct nl_msg *msg, const char *val)
+{
+ bool ret = true;
+ char *str, *cur, *ap;
+ void *a;
+
+ ap = str = strdup(val);
+
+ a = nla_nest_start(msg, idx);
+
+ idx = 0;
+ while ((cur = strsep(&ap, ",")) != NULL) {
+ ret = field->parse2(field, idx++, msg, cur);
+ if (!ret)
+ break;
+ }
+
+ nla_nest_end(msg, a);
+
+ free(str);
+
+ return ret;
+}
+
+static void print_array(const struct tm_field *field, struct nlattr *attr)
+{
+ struct nlattr *cur;
+ int idx = 0;
+ int rem;
+
+ nla_for_each_nested(cur, attr, rem) {
+ if (idx++ > 0)
+ printf(",");
+ if (nla_len(cur) != 1)
+ continue;
+ field->print2(field, cur);
+ }
+}
+
+static void print_nested(const struct tm_field *field, struct nlattr *attr)
+{
+ struct nlattr **tb = alloca(field->len * sizeof(struct nlattr *));
+ const struct tm_field *fields = field->fields;
+ int i;
+
+ nla_parse_nested(tb, field->len - 1, attr, field->policy);
+ for (i = 0; i < field->len; i++) {
+ int prefix_len = 0;
+
+ if (!tb[i])
+ continue;
+
+ if (!fields[i].print)
+ continue;
+
+ if (fields[i].name)
+ printf("%s%s=", prefix, fields[i].name);
+
+ if (fields[i].prefix) {
+ prefix_len = strlen(prefix);
+ strncat(prefix + prefix_len, fields[i].prefix,
+ sizeof(prefix) - prefix_len - 1);
+ }
+
+ fields[i].print(&fields[i], tb[i]);
+ if (fields[i].prefix)
+ prefix[prefix_len] = 0;
+
+ if (fields[i].name)
+ printf("\n");
+ }
+
+ if (field->print_extra)
+ field->print_extra(field, tb);
+}
+
+static void print_extra_stats(const struct tm_field *field, struct nlattr **tb)
+{
+ float total, failed;
+
+ if (!tb[MT76_TM_STATS_ATTR_RX_PACKETS] ||
+ !tb[MT76_TM_STATS_ATTR_RX_FCS_ERROR])
+ return;
+
+ total = nla_get_u64(tb[MT76_TM_STATS_ATTR_RX_PACKETS]);
+ failed = nla_get_u64(tb[MT76_TM_STATS_ATTR_RX_FCS_ERROR]);
+
+ printf("%srx_per=%.02f%%\n", prefix, 100 * failed / total);
+}
+
+
+#define FIELD_GENERIC(_field, _name, ...) \
+ [FIELD_NAME(_field)] = { \
+ .name = _name, \
+ ##__VA_ARGS__ \
+ }
+
+#define FIELD_RO(_type, _field, _name, ...) \
+ FIELD_GENERIC(_field, _name, \
+ .print = print_##_type, \
+ ##__VA_ARGS__ \
+ )
+
+#define FIELD(_type, _field, _name, ...) \
+ FIELD_RO(_type, _field, _name, \
+ .parse = parse_##_type, \
+ ##__VA_ARGS__ \
+ )
+
+#define FIELD_FLAG(_field, _name) \
+ FIELD_GENERIC(_field, _name, .parse = parse_flag)
+
+#define FIELD_ENUM(_field, _name, _vals) \
+ FIELD(enum, _field, _name, \
+ .enum_str = _vals, \
+ .enum_len = ARRAY_SIZE(_vals) \
+ )
+
+#define FIELD_ARRAY_RO(_type, _field, _name, ...) \
+ FIELD(array, _field, _name, \
+ .print2 = print_##_type, \
+ ##__VA_ARGS__ \
+ )
+
+#define FIELD_ARRAY(_type, _field, _name, ...) \
+ FIELD_ARRAY_RO(_type, _field, _name, \
+ .parse2 = parse_##_type, \
+ ##__VA_ARGS__ \
+ )
+
+#define FIELD_NESTED_RO(_field, _data, _prefix, ...) \
+ FIELD_RO(nested, _field, NULL, \
+ .prefix = _prefix, \
+ .fields = _data ## _fields, \
+ .policy = _data ## _policy, \
+ .len = ARRAY_SIZE(_data ## _fields), \
+ ##__VA_ARGS__ \
+ )
+
+#define FIELD_NAME(_field) MT76_TM_RX_ATTR_##_field
+static const struct tm_field rx_fields[NUM_MT76_TM_RX_ATTRS] = {
+ FIELD_RO(s32, FREQ_OFFSET, "freq_offset"),
+ FIELD_ARRAY_RO(u8, RCPI, "rcpi"),
+ FIELD_ARRAY_RO(s8, IB_RSSI, "ib_rssi"),
+ FIELD_ARRAY_RO(s8, WB_RSSI, "wb_rssi"),
+ FIELD_RO(s8, SNR, "snr"),
+};
+static struct nla_policy rx_policy[NUM_MT76_TM_RX_ATTRS] = {
+ [MT76_TM_RX_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+ [MT76_TM_RX_ATTR_RCPI] = { .type = NLA_NESTED },
+ [MT76_TM_RX_ATTR_IB_RSSI] = { .type = NLA_NESTED },
+ [MT76_TM_RX_ATTR_WB_RSSI] = { .type = NLA_NESTED },
+ [MT76_TM_RX_ATTR_SNR] = { .type = NLA_U8 },
+};
+#undef FIELD_NAME
+
+#define FIELD_NAME(_field) MT76_TM_STATS_ATTR_##_field
+static const struct tm_field stats_fields[NUM_MT76_TM_STATS_ATTRS] = {
+ FIELD_RO(u32, TX_PENDING, "tx_pending"),
+ FIELD_RO(u32, TX_QUEUED, "tx_queued"),
+ FIELD_RO(u32, TX_DONE, "tx_done"),
+ FIELD_RO(u64, RX_PACKETS, "rx_packets"),
+ FIELD_RO(u64, RX_FCS_ERROR, "rx_fcs_error"),
+ FIELD_NESTED_RO(LAST_RX, rx, "last_"),
+};
+static struct nla_policy stats_policy[NUM_MT76_TM_STATS_ATTRS] = {
+ [MT76_TM_STATS_ATTR_TX_PENDING] = { .type = NLA_U32 },
+ [MT76_TM_STATS_ATTR_TX_QUEUED] = { .type = NLA_U32 },
+ [MT76_TM_STATS_ATTR_TX_DONE] = { .type = NLA_U32 },
+ [MT76_TM_STATS_ATTR_RX_PACKETS] = { .type = NLA_U64 },
+ [MT76_TM_STATS_ATTR_RX_FCS_ERROR] = { .type = NLA_U64 },
+};
+#undef FIELD_NAME
+
+#define FIELD_NAME(_field) MT76_TM_ATTR_##_field
+static const struct tm_field testdata_fields[NUM_MT76_TM_ATTRS] = {
+ FIELD_FLAG(RESET, "reset"),
+ FIELD_ENUM(STATE, "state", testmode_state),
+ FIELD_RO(string, MTD_PART, "mtd_part"),
+ FIELD_RO(u32, MTD_OFFSET, "mtd_offset"),
+ FIELD(u32, TX_COUNT, "tx_count"),
+ FIELD(u32, TX_LENGTH, "tx_length"),
+ FIELD_ENUM(TX_RATE_MODE, "tx_rate_mode", testmode_tx_mode),
+ FIELD(u8, TX_RATE_NSS, "tx_rate_nss"),
+ FIELD(u8, TX_RATE_IDX, "tx_rate_idx"),
+ FIELD(u8, TX_RATE_SGI, "tx_rate_sgi"),
+ FIELD(u8, TX_RATE_LDPC, "tx_rate_ldpc"),
+ FIELD(u8, TX_RATE_STBC, "tx_rate_stbc"),
+ FIELD(u8, TX_LTF, "tx_ltf"),
+ FIELD(u8, TX_POWER_CONTROL, "tx_power_control"),
+ FIELD_ARRAY(u8, TX_POWER, "tx_power"),
+ FIELD(u8, TX_ANTENNA, "tx_antenna"),
+ FIELD(u32, FREQ_OFFSET, "freq_offset"),
+ FIELD_NESTED_RO(STATS, stats, "",
+ .print_extra = print_extra_stats),
+};
+#undef FIELD_NAME
+
+static struct nla_policy testdata_policy[NUM_MT76_TM_ATTRS] = {
+ [MT76_TM_ATTR_STATE] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_MTD_PART] = { .type = NLA_STRING },
+ [MT76_TM_ATTR_MTD_OFFSET] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_TX_COUNT] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_TX_LENGTH] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_TX_RATE_MODE] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_NSS] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_IDX] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_SGI] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_LDPC] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_STBC] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_LTF] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_POWER_CONTROL] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_ANTENNA] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_STATS] = { .type = NLA_NESTED },
+};
+
+const struct tm_field msg_field = {
+ .print = print_nested,
+ .fields = testdata_fields,
+ .policy = testdata_policy,
+ .len = ARRAY_SIZE(testdata_fields)
+};
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/fwlog.c b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/fwlog.c
new file mode 100644
index 0000000..e5d4a10
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/fwlog.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: ISC
+/* Copyright (C) 2022 Felix Fietkau <nbd@nbd.name> */
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <signal.h>
+#include "mt76-test.h"
+
+bool done = false;
+
+static const char *debugfs_path(const char *phyname, const char *file)
+{
+ static char path[256];
+
+ snprintf(path, sizeof(path), "/sys/kernel/debug/ieee80211/%s/mt76/%s", phyname, file);
+
+ return path;
+}
+
+static int mt76_set_fwlog_en(const char *phyname, bool en)
+{
+ FILE *f = fopen(debugfs_path(phyname, "fw_debug_bin"), "w");
+
+ if (!f) {
+ fprintf(stderr, "Could not open fw_debug_bin file\n");
+ return 1;
+ }
+
+ fprintf(f, "7");
+ fclose(f);
+
+ return 0;
+}
+
+int read_retry(int fd, void *buf, int len)
+{
+ int out_len = 0;
+ int r;
+
+ while (len > 0) {
+ if (done)
+ return -1;
+
+ r = read(fd, buf, len);
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+
+ return -1;
+ }
+
+ if (!r)
+ return 0;
+
+ out_len += r;
+ len -= r;
+ buf += r;
+ }
+
+ return out_len;
+}
+
+static void handle_signal(int sig)
+{
+ done = true;
+}
+
+int mt76_fwlog(const char *phyname, int argc, char **argv)
+{
+ struct sockaddr_in local = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = INADDR_ANY,
+ };
+ struct sockaddr_in remote = {
+ .sin_family = AF_INET,
+ .sin_port = htons(55688),
+ };
+ char buf[1504];
+ int ret = 0;
+ int yes = 1;
+ int s, fd;
+
+ if (argc < 1) {
+ fprintf(stderr, "need destination address\n");
+ return 1;
+ }
+
+ if (!inet_aton(argv[0], &remote.sin_addr)) {
+ fprintf(stderr, "invalid destination address\n");
+ return 1;
+ }
+
+ s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (s < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ setsockopt(s, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
+ if (bind(s, (struct sockaddr *)&local, sizeof(local)) < 0) {
+ perror("bind");
+ return 1;
+ }
+
+ if (mt76_set_fwlog_en(phyname, true))
+ return 1;
+
+ fd = open(debugfs_path(phyname, "fwlog_data"), O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Could not open fwlog_data file: %s\n", strerror(errno));
+ ret = 1;
+ goto out;
+ }
+
+ signal(SIGTERM, handle_signal);
+ signal(SIGINT, handle_signal);
+ signal(SIGQUIT, handle_signal);
+
+ while (1) {
+ struct pollfd pfd = {
+ .fd = fd,
+ .events = POLLIN | POLLHUP | POLLERR,
+ };
+ uint32_t len;
+ int r;
+
+ if (done)
+ break;
+
+ poll(&pfd, 1, -1);
+
+ r = read_retry(fd, &len, sizeof(len));
+ if (r < 0)
+ break;
+
+ if (!r)
+ continue;
+
+ if (len > sizeof(buf)) {
+ fprintf(stderr, "Length error: %d > %d\n", len, (int)sizeof(buf));
+ ret = 1;
+ break;
+ }
+
+ if (done)
+ break;
+
+ r = read_retry(fd, buf, len);
+ if (done)
+ break;
+
+ if (r != len) {
+ fprintf(stderr, "Short read: %d < %d\n", r, len);
+ ret = 1;
+ break;
+ }
+
+ /* send buf */
+ sendto(s, buf, len, 0, (struct sockaddr *)&remote, sizeof(remote));
+ }
+
+ close(fd);
+
+out:
+ mt76_set_fwlog_en(phyname, false);
+
+ return ret;
+}
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/main.c b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/main.c
new file mode 100644
index 0000000..699a9ee
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/main.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: ISC
+/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <signal.h>
+#include "mt76-test.h"
+
+struct unl unl;
+static uint32_t tm_changed[DIV_ROUND_UP(NUM_MT76_TM_ATTRS, 32)];
+static const char *progname;
+
+static int phy_lookup_idx(const char *name)
+{
+ char buf[128];
+ FILE *f;
+ int len;
+
+ snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", name);
+ f = fopen(buf, "r");
+ if (!f)
+ return -1;
+
+ len = fread(buf, 1, sizeof(buf) - 1, f);
+ fclose(f);
+
+ if (!len)
+ return -1;
+
+ buf[len] = 0;
+ return atoi(buf);
+}
+
+void usage(void)
+{
+ static const char *const commands[] = {
+ "set <var>=<val> [...]",
+ "dump [stats]",
+ "eeprom file",
+ "eeprom set <addr>=<val> [...]",
+ "eeprom changes",
+ "eeprom reset",
+ };
+ int i;
+
+ fprintf(stderr, "Usage:\n");
+ for (i = 0; i < ARRAY_SIZE(commands); i++)
+ printf(" %s phyX %s\n", progname, commands[i]);
+
+ exit(1);
+}
+
+static int mt76_dump_cb(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *attr;
+
+ attr = unl_find_attr(&unl, msg, NL80211_ATTR_TESTDATA);
+ if (!attr) {
+ fprintf(stderr, "Testdata attribute not found\n");
+ return NL_SKIP;
+ }
+
+ msg_field.print(&msg_field, attr);
+
+ return NL_SKIP;
+}
+
+static int mt76_dump(int phy, int argc, char **argv)
+{
+ struct nl_msg *msg;
+ void *data;
+
+ msg = unl_genl_msg(&unl, NL80211_CMD_TESTMODE, true);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, phy);
+
+ data = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+
+ for (; argc > 0; argc--, argv++) {
+ if (!strcmp(argv[0], "stats"))
+ nla_put_flag(msg, MT76_TM_ATTR_STATS);
+ }
+
+ nla_nest_end(msg, data);
+
+ unl_genl_request(&unl, msg, mt76_dump_cb, NULL);
+
+ return 0;
+}
+
+static inline void tm_set_changed(uint32_t id)
+{
+ tm_changed[id / 32] |= (1U << (id % 32));
+}
+
+static inline bool tm_is_changed(uint32_t id)
+{
+ return tm_changed[id / 32] & (1U << (id % 32));
+}
+
+static int mt76_set(int phy, int argc, char **argv)
+{
+ const struct tm_field *fields = msg_field.fields;
+ struct nl_msg *msg;
+ void *data;
+ int i, ret;
+
+ if (argc < 1)
+ return 1;
+
+ msg = unl_genl_msg(&unl, NL80211_CMD_TESTMODE, false);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, phy);
+
+ data = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ for (; argc > 0; argc--, argv++) {
+ char *name = argv[0];
+ char *val = strchr(name, '=');
+
+ if (!val) {
+ fprintf(stderr, "Invalid argument: %s\n", name);
+ return 1;
+ }
+
+ *(val++) = 0;
+
+ for (i = 0; i < msg_field.len; i++) {
+ if (!fields[i].parse)
+ continue;
+
+ if (!strcmp(fields[i].name, name))
+ break;
+ }
+
+ if (i == msg_field.len) {
+ fprintf(stderr, "Unknown field: %s\n", name);
+ return 1;
+ }
+
+ if (tm_is_changed(i)) {
+ fprintf(stderr, "Duplicate field '%s'\n", name);
+ return 1;
+ }
+
+ if (!fields[i].parse(&fields[i], i, msg, val))
+ return 1;
+
+ tm_set_changed(i);
+ }
+
+ nla_nest_end(msg, data);
+
+ ret = unl_genl_request(&unl, msg, NULL, NULL);
+ if (ret)
+ fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ const char *cmd, *phyname;
+ int phy;
+ int ret = 0;
+
+ progname = argv[0];
+ if (argc < 3)
+ usage();
+
+ if (unl_genl_init(&unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ phyname = argv[1];
+ phy = phy_lookup_idx(phyname);
+ if (phy < 0) {
+ fprintf(stderr, "Could not find phy '%s'\n", argv[1]);
+ return 2;
+ }
+
+ cmd = argv[2];
+ argv += 3;
+ argc -= 3;
+
+ if (!strcmp(cmd, "dump"))
+ ret = mt76_dump(phy, argc, argv);
+ else if (!strcmp(cmd, "set"))
+ ret = mt76_set(phy, argc, argv);
+ else if (!strcmp(cmd, "eeprom"))
+ ret = mt76_eeprom(phy, argc, argv);
+ else if (!strcmp(cmd, "fwlog"))
+ ret = mt76_fwlog(phyname, argc, argv);
+ else
+ usage();
+
+ unl_free(&unl);
+
+ return ret;
+}
diff --git a/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/mt76-test.h b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/mt76-test.h
new file mode 100644
index 0000000..d2fafa8
--- /dev/null
+++ b/autobuild_mac80211_release/mt7988_mt7996_mac80211/package/kernel/mt76/src/tools/mt76-test.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: ISC
+/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
+#ifndef __MT76_TEST_H
+#define __MT76_TEST_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <linux/nl80211.h>
+#include <unl.h>
+
+#include "../testmode.h"
+
+#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
+
+#define EEPROM_FILE_PATH_FMT "/tmp/mt76-test-%s"
+#define EEPROM_PART_SIZE 20480
+
+struct nl_msg;
+struct nlattr;
+
+struct tm_field {
+ const char *name;
+ const char *prefix;
+
+ bool (*parse)(const struct tm_field *field, int idx, struct nl_msg *msg,
+ const char *val);
+ void (*print)(const struct tm_field *field, struct nlattr *attr);
+
+ union {
+ struct {
+ const char * const *enum_str;
+ int enum_len;
+ };
+ struct {
+ bool (*parse2)(const struct tm_field *field, int idx,
+ struct nl_msg *msg, const char *val);
+ void (*print2)(const struct tm_field *field,
+ struct nlattr *attr);
+ };
+ struct {
+ void (*print_extra)(const struct tm_field *field,
+ struct nlattr **tb);
+ const struct tm_field *fields;
+ struct nla_policy *policy;
+ int len;
+ };
+ };
+};
+
+extern struct unl unl;
+extern const struct tm_field msg_field;
+extern unsigned char *eeprom_data;
+
+void usage(void);
+int mt76_eeprom(int phy, int argc, char **argv);
+int mt76_fwlog(const char *phyname, int argc, char **argv);
+
+#endif