add utility for using testmode support

Signed-off-by: Felix Fietkau <[email protected]>
This commit is contained in:
Felix Fietkau
2020-06-22 21:32:26 +02:00
parent d7467bc018
commit 3ea5da1639
7 changed files with 916 additions and 1 deletions
+8 -1
View File
@@ -1,7 +1,14 @@
.* .*
*.o *.o
*.ko *.ko
*.mod
*.mod.c *.mod.c
Module.symvers *.symvers
*.symvers.tmp
modules.order modules.order
ipkg-* ipkg-*
CMakeFiles
CMakeCache.txt
cmake_install.cmake
tools/Makefile
tools/mt76-test
+13
View File
@@ -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
)
+48
View File
@@ -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 |
+263
View File
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: ISC
/* Copyright (C) 2020 Felix Fietkau <[email protected]> */
#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);
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;
}
+326
View File
@@ -0,0 +1,326 @@
// SPDX-License-Identifier: ISC
/* Copyright (C) 2020 Felix Fietkau <[email protected]> */
#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)
};
+194
View File
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: ISC
/* Copyright (C) 2020 Felix Fietkau <[email protected]> */
#define _GNU_SOURCE
#include <stdio.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;
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;
}
+64
View File
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: ISC
/* Copyright (C) 2020 Felix Fietkau <[email protected]> */
#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);
#endif