[][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