From 3ea5da1639fe18a18295e381cd4fcd84faa82579 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Thu, 18 Jun 2020 21:19:12 +0200 Subject: [PATCH] add utility for using testmode support Signed-off-by: Felix Fietkau --- .gitignore | 9 +- tools/CMakeLists.txt | 13 ++ tools/README.md | 48 +++++++ tools/eeprom.c | 263 ++++++++++++++++++++++++++++++++++ tools/fields.c | 326 +++++++++++++++++++++++++++++++++++++++++++ tools/main.c | 194 +++++++++++++++++++++++++ tools/mt76-test.h | 64 +++++++++ 7 files changed, 916 insertions(+), 1 deletion(-) create mode 100644 tools/CMakeLists.txt create mode 100644 tools/README.md create mode 100644 tools/eeprom.c create mode 100644 tools/fields.c create mode 100644 tools/main.c create mode 100644 tools/mt76-test.h diff --git a/.gitignore b/.gitignore index 13aa53e1..6bd2c574 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,14 @@ .* *.o *.ko +*.mod *.mod.c -Module.symvers +*.symvers +*.symvers.tmp modules.order ipkg-* +CMakeFiles +CMakeCache.txt +cmake_install.cmake +tools/Makefile +tools/mt76-test diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 00000000..2e979f22 --- /dev/null +++ b/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) +TARGET_LINK_LIBRARIES(mt76-test nl-tiny) + +SET(CMAKE_INSTALL_PREFIX /usr) + +INSTALL(TARGETS mt76-test + RUNTIME DESTINATION sbin +) diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 00000000..d79beaa3 --- /dev/null +++ b/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 =`[...] +- 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/tools/eeprom.c b/tools/eeprom.c new file mode 100644 index 00000000..a57a9b09 --- /dev/null +++ b/tools/eeprom.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: ISC +/* Copyright (C) 2020 Felix Fietkau */ +#define _GNU_SOURCE +#include +#include + +#include +#include +#include +#include +#include + +#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); + 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/tools/fields.c b/tools/fields.c new file mode 100644 index 00000000..8f7f7997 --- /dev/null +++ b/tools/fields.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: ISC +/* Copyright (C) 2020 Felix Fietkau */ +#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", +}; + +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(_field, _name, ...) \ + FIELD_ARRAY(_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_RO(s8, IB_RSSI, "ib_rssi"), + FIELD_RO(s8, WB_RSSI, "wb_rssi"), +}; +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_U8 }, + [MT76_TM_RX_ATTR_WB_RSSI] = { .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_POWER_CONTROL, "tx_power_control"), + FIELD(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_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/tools/main.c b/tools/main.c new file mode 100644 index 00000000..a76bf5d9 --- /dev/null +++ b/tools/main.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: ISC +/* Copyright (C) 2020 Felix Fietkau */ +#define _GNU_SOURCE + +#include +#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 = [...]", + "dump [stats]", + "eeprom file", + "eeprom set = [...]", + "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; + 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; + } + + phy = phy_lookup_idx(argv[1]); + 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 + usage(); + + unl_free(&unl); + + return ret; +} diff --git a/tools/mt76-test.h b/tools/mt76-test.h new file mode 100644 index 00000000..e2668643 --- /dev/null +++ b/tools/mt76-test.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: ISC +/* Copyright (C) 2020 Felix Fietkau */ +#ifndef __MT76_TEST_H +#define __MT76_TEST_H + +#include +#include + +#include +#include + +#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); + +#endif