mirror of
https://github.com/outbackdingo/mt76.git
synced 2026-10-24 21:36:52 +00:00
327 lines
8.4 KiB
C
327 lines
8.4 KiB
C
// 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)
|
|
};
|