mirror of
https://github.com/outbackdingo/mt76.git
synced 2026-10-24 05:16:57 +00:00
add utility for using testmode support
Signed-off-by: Felix Fietkau <[email protected]>
This commit is contained in:
+8
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user