developer | b11a539 | 2022-03-31 00:34:47 +0800 | [diff] [blame] | 1 | // SPDX-License-Identifier: ISC |
| 2 | /* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */ |
| 3 | #define _GNU_SOURCE |
| 4 | #include "mt76-test.h" |
| 5 | |
| 6 | static char prefix[64]; |
| 7 | |
| 8 | static const char * const testmode_state[] = { |
| 9 | [MT76_TM_STATE_OFF] = "off", |
| 10 | [MT76_TM_STATE_IDLE] = "idle", |
| 11 | [MT76_TM_STATE_TX_FRAMES] = "tx_frames", |
| 12 | [MT76_TM_STATE_RX_FRAMES] = "rx_frames", |
| 13 | }; |
| 14 | |
| 15 | static const char * const testmode_tx_mode[] = { |
| 16 | [MT76_TM_TX_MODE_CCK] = "cck", |
| 17 | [MT76_TM_TX_MODE_OFDM] = "ofdm", |
| 18 | [MT76_TM_TX_MODE_HT] = "ht", |
| 19 | [MT76_TM_TX_MODE_VHT] = "vht", |
| 20 | [MT76_TM_TX_MODE_HE_SU] = "he_su", |
| 21 | [MT76_TM_TX_MODE_HE_EXT_SU] = "he_ext_su", |
| 22 | [MT76_TM_TX_MODE_HE_TB] = "he_tb", |
| 23 | [MT76_TM_TX_MODE_HE_MU] = "he_mu", |
| 24 | }; |
| 25 | |
| 26 | static void print_enum(const struct tm_field *field, struct nlattr *attr) |
| 27 | { |
| 28 | unsigned int i = nla_get_u8(attr); |
| 29 | |
| 30 | if (i < field->enum_len && field->enum_str[i]) |
| 31 | printf("%s", field->enum_str[i]); |
| 32 | else |
| 33 | printf("unknown (%d)", i); |
| 34 | } |
| 35 | |
| 36 | static bool parse_enum(const struct tm_field *field, int idx, |
| 37 | struct nl_msg *msg, const char *val) |
| 38 | { |
| 39 | int i; |
| 40 | |
| 41 | for (i = 0; i < field->enum_len; i++) { |
| 42 | if (strcmp(field->enum_str[i], val) != 0) |
| 43 | continue; |
| 44 | |
| 45 | if (nla_put_u8(msg, idx, i)) |
| 46 | return false; |
| 47 | |
| 48 | return true; |
| 49 | } |
| 50 | |
| 51 | printf("Invalid value for parameter '%s': %s\n", field->name, val); |
| 52 | printf("Possible values:"); |
| 53 | for (i = 0; i < field->enum_len; i++) |
| 54 | printf(" %s", field->enum_str[i]); |
| 55 | printf("\n"); |
| 56 | |
| 57 | return false; |
| 58 | } |
| 59 | |
| 60 | static bool parse_u8(const struct tm_field *field, int idx, |
| 61 | struct nl_msg *msg, const char *val) |
| 62 | { |
| 63 | return !nla_put_u8(msg, idx, strtoul(val, NULL, 0)); |
| 64 | } |
| 65 | |
| 66 | static void print_u8(const struct tm_field *field, struct nlattr *attr) |
| 67 | { |
| 68 | printf("%d", nla_get_u8(attr)); |
| 69 | } |
| 70 | |
| 71 | static void print_s8(const struct tm_field *field, struct nlattr *attr) |
| 72 | { |
| 73 | printf("%d", (int8_t)nla_get_u8(attr)); |
| 74 | } |
| 75 | |
| 76 | static bool parse_u32(const struct tm_field *field, int idx, |
| 77 | struct nl_msg *msg, const char *val) |
| 78 | { |
| 79 | return !nla_put_u32(msg, idx, strtoul(val, NULL, 0)); |
| 80 | } |
| 81 | |
| 82 | static void print_s32(const struct tm_field *field, struct nlattr *attr) |
| 83 | { |
| 84 | printf("%d", (int32_t)nla_get_u32(attr)); |
| 85 | } |
| 86 | |
| 87 | static void print_u32(const struct tm_field *field, struct nlattr *attr) |
| 88 | { |
| 89 | printf("%d", nla_get_u32(attr)); |
| 90 | } |
| 91 | |
| 92 | static void print_u64(const struct tm_field *field, struct nlattr *attr) |
| 93 | { |
| 94 | printf("%lld", (unsigned long long)nla_get_u64(attr)); |
| 95 | } |
| 96 | |
| 97 | static bool parse_flag(const struct tm_field *field, int idx, |
| 98 | struct nl_msg *msg, const char *val) |
| 99 | { |
| 100 | bool set = strtoul(val, NULL, 0); |
| 101 | |
| 102 | if (!set) |
| 103 | return true; |
| 104 | |
| 105 | return !nla_put_flag(msg, idx); |
| 106 | } |
| 107 | |
| 108 | static void print_string(const struct tm_field *field, struct nlattr *attr) |
| 109 | { |
| 110 | printf("%s", nla_get_string(attr)); |
| 111 | } |
| 112 | |
| 113 | static bool parse_array(const struct tm_field *field, int idx, |
| 114 | struct nl_msg *msg, const char *val) |
| 115 | { |
| 116 | bool ret = true; |
| 117 | char *str, *cur, *ap; |
| 118 | void *a; |
| 119 | |
| 120 | ap = str = strdup(val); |
| 121 | |
| 122 | a = nla_nest_start(msg, idx); |
| 123 | |
| 124 | idx = 0; |
| 125 | while ((cur = strsep(&ap, ",")) != NULL) { |
| 126 | ret = field->parse2(field, idx++, msg, cur); |
| 127 | if (!ret) |
| 128 | break; |
| 129 | } |
| 130 | |
| 131 | nla_nest_end(msg, a); |
| 132 | |
| 133 | free(str); |
| 134 | |
| 135 | return ret; |
| 136 | } |
| 137 | |
| 138 | static void print_array(const struct tm_field *field, struct nlattr *attr) |
| 139 | { |
| 140 | struct nlattr *cur; |
| 141 | int idx = 0; |
| 142 | int rem; |
| 143 | |
| 144 | nla_for_each_nested(cur, attr, rem) { |
| 145 | if (idx++ > 0) |
| 146 | printf(","); |
| 147 | if (nla_len(cur) != 1) |
| 148 | continue; |
| 149 | field->print2(field, cur); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | static void print_nested(const struct tm_field *field, struct nlattr *attr) |
| 154 | { |
| 155 | struct nlattr **tb = alloca(field->len * sizeof(struct nlattr *)); |
| 156 | const struct tm_field *fields = field->fields; |
| 157 | int i; |
| 158 | |
| 159 | nla_parse_nested(tb, field->len - 1, attr, field->policy); |
| 160 | for (i = 0; i < field->len; i++) { |
| 161 | int prefix_len = 0; |
| 162 | |
| 163 | if (!tb[i]) |
| 164 | continue; |
| 165 | |
| 166 | if (!fields[i].print) |
| 167 | continue; |
| 168 | |
| 169 | if (fields[i].name) |
| 170 | printf("%s%s=", prefix, fields[i].name); |
| 171 | |
| 172 | if (fields[i].prefix) { |
| 173 | prefix_len = strlen(prefix); |
| 174 | strncat(prefix + prefix_len, fields[i].prefix, |
| 175 | sizeof(prefix) - prefix_len - 1); |
| 176 | } |
| 177 | |
| 178 | fields[i].print(&fields[i], tb[i]); |
| 179 | if (fields[i].prefix) |
| 180 | prefix[prefix_len] = 0; |
| 181 | |
| 182 | if (fields[i].name) |
| 183 | printf("\n"); |
| 184 | } |
| 185 | |
| 186 | if (field->print_extra) |
| 187 | field->print_extra(field, tb); |
| 188 | } |
| 189 | |
| 190 | static void print_extra_stats(const struct tm_field *field, struct nlattr **tb) |
| 191 | { |
| 192 | float total, failed; |
| 193 | |
| 194 | if (!tb[MT76_TM_STATS_ATTR_RX_PACKETS] || |
| 195 | !tb[MT76_TM_STATS_ATTR_RX_FCS_ERROR]) |
| 196 | return; |
| 197 | |
| 198 | total = nla_get_u64(tb[MT76_TM_STATS_ATTR_RX_PACKETS]); |
| 199 | failed = nla_get_u64(tb[MT76_TM_STATS_ATTR_RX_FCS_ERROR]); |
| 200 | |
| 201 | printf("%srx_per=%.02f%%\n", prefix, 100 * failed / total); |
| 202 | } |
| 203 | |
| 204 | |
| 205 | #define FIELD_GENERIC(_field, _name, ...) \ |
| 206 | [FIELD_NAME(_field)] = { \ |
| 207 | .name = _name, \ |
| 208 | ##__VA_ARGS__ \ |
| 209 | } |
| 210 | |
| 211 | #define FIELD_RO(_type, _field, _name, ...) \ |
| 212 | FIELD_GENERIC(_field, _name, \ |
| 213 | .print = print_##_type, \ |
| 214 | ##__VA_ARGS__ \ |
| 215 | ) |
| 216 | |
| 217 | #define FIELD(_type, _field, _name, ...) \ |
| 218 | FIELD_RO(_type, _field, _name, \ |
| 219 | .parse = parse_##_type, \ |
| 220 | ##__VA_ARGS__ \ |
| 221 | ) |
| 222 | |
| 223 | #define FIELD_FLAG(_field, _name) \ |
| 224 | FIELD_GENERIC(_field, _name, .parse = parse_flag) |
| 225 | |
| 226 | #define FIELD_ENUM(_field, _name, _vals) \ |
| 227 | FIELD(enum, _field, _name, \ |
| 228 | .enum_str = _vals, \ |
| 229 | .enum_len = ARRAY_SIZE(_vals) \ |
| 230 | ) |
| 231 | |
| 232 | #define FIELD_ARRAY_RO(_type, _field, _name, ...) \ |
| 233 | FIELD(array, _field, _name, \ |
| 234 | .print2 = print_##_type, \ |
| 235 | ##__VA_ARGS__ \ |
| 236 | ) |
| 237 | |
| 238 | #define FIELD_ARRAY(_type, _field, _name, ...) \ |
| 239 | FIELD_ARRAY_RO(_type, _field, _name, \ |
| 240 | .parse2 = parse_##_type, \ |
| 241 | ##__VA_ARGS__ \ |
| 242 | ) |
| 243 | |
| 244 | #define FIELD_NESTED_RO(_field, _data, _prefix, ...) \ |
| 245 | FIELD_RO(nested, _field, NULL, \ |
| 246 | .prefix = _prefix, \ |
| 247 | .fields = _data ## _fields, \ |
| 248 | .policy = _data ## _policy, \ |
| 249 | .len = ARRAY_SIZE(_data ## _fields), \ |
| 250 | ##__VA_ARGS__ \ |
| 251 | ) |
| 252 | |
| 253 | #define FIELD_NAME(_field) MT76_TM_RX_ATTR_##_field |
| 254 | static const struct tm_field rx_fields[NUM_MT76_TM_RX_ATTRS] = { |
| 255 | FIELD_RO(s32, FREQ_OFFSET, "freq_offset"), |
| 256 | FIELD_ARRAY_RO(u8, RCPI, "rcpi"), |
| 257 | FIELD_ARRAY_RO(s8, IB_RSSI, "ib_rssi"), |
| 258 | FIELD_ARRAY_RO(s8, WB_RSSI, "wb_rssi"), |
| 259 | FIELD_RO(s8, SNR, "snr"), |
| 260 | }; |
| 261 | static struct nla_policy rx_policy[NUM_MT76_TM_RX_ATTRS] = { |
| 262 | [MT76_TM_RX_ATTR_FREQ_OFFSET] = { .type = NLA_U32 }, |
| 263 | [MT76_TM_RX_ATTR_RCPI] = { .type = NLA_NESTED }, |
| 264 | [MT76_TM_RX_ATTR_IB_RSSI] = { .type = NLA_NESTED }, |
| 265 | [MT76_TM_RX_ATTR_WB_RSSI] = { .type = NLA_NESTED }, |
| 266 | [MT76_TM_RX_ATTR_SNR] = { .type = NLA_U8 }, |
| 267 | }; |
| 268 | #undef FIELD_NAME |
| 269 | |
| 270 | #define FIELD_NAME(_field) MT76_TM_STATS_ATTR_##_field |
| 271 | static const struct tm_field stats_fields[NUM_MT76_TM_STATS_ATTRS] = { |
| 272 | FIELD_RO(u32, TX_PENDING, "tx_pending"), |
| 273 | FIELD_RO(u32, TX_QUEUED, "tx_queued"), |
| 274 | FIELD_RO(u32, TX_DONE, "tx_done"), |
| 275 | FIELD_RO(u64, RX_PACKETS, "rx_packets"), |
| 276 | FIELD_RO(u64, RX_FCS_ERROR, "rx_fcs_error"), |
| 277 | FIELD_NESTED_RO(LAST_RX, rx, "last_"), |
| 278 | }; |
| 279 | static struct nla_policy stats_policy[NUM_MT76_TM_STATS_ATTRS] = { |
| 280 | [MT76_TM_STATS_ATTR_TX_PENDING] = { .type = NLA_U32 }, |
| 281 | [MT76_TM_STATS_ATTR_TX_QUEUED] = { .type = NLA_U32 }, |
| 282 | [MT76_TM_STATS_ATTR_TX_DONE] = { .type = NLA_U32 }, |
| 283 | [MT76_TM_STATS_ATTR_RX_PACKETS] = { .type = NLA_U64 }, |
| 284 | [MT76_TM_STATS_ATTR_RX_FCS_ERROR] = { .type = NLA_U64 }, |
| 285 | }; |
| 286 | #undef FIELD_NAME |
| 287 | |
| 288 | #define FIELD_NAME(_field) MT76_TM_ATTR_##_field |
| 289 | static const struct tm_field testdata_fields[NUM_MT76_TM_ATTRS] = { |
| 290 | FIELD_FLAG(RESET, "reset"), |
| 291 | FIELD_ENUM(STATE, "state", testmode_state), |
| 292 | FIELD_RO(string, MTD_PART, "mtd_part"), |
| 293 | FIELD_RO(u32, MTD_OFFSET, "mtd_offset"), |
| 294 | FIELD(u32, TX_COUNT, "tx_count"), |
| 295 | FIELD(u32, TX_LENGTH, "tx_length"), |
| 296 | FIELD_ENUM(TX_RATE_MODE, "tx_rate_mode", testmode_tx_mode), |
| 297 | FIELD(u8, TX_RATE_NSS, "tx_rate_nss"), |
| 298 | FIELD(u8, TX_RATE_IDX, "tx_rate_idx"), |
| 299 | FIELD(u8, TX_RATE_SGI, "tx_rate_sgi"), |
| 300 | FIELD(u8, TX_RATE_LDPC, "tx_rate_ldpc"), |
| 301 | FIELD(u8, TX_RATE_STBC, "tx_rate_stbc"), |
| 302 | FIELD(u8, TX_LTF, "tx_ltf"), |
| 303 | FIELD(u8, TX_POWER_CONTROL, "tx_power_control"), |
| 304 | FIELD_ARRAY(u8, TX_POWER, "tx_power"), |
| 305 | FIELD(u8, TX_ANTENNA, "tx_antenna"), |
| 306 | FIELD(u32, FREQ_OFFSET, "freq_offset"), |
| 307 | FIELD_NESTED_RO(STATS, stats, "", |
| 308 | .print_extra = print_extra_stats), |
| 309 | }; |
| 310 | #undef FIELD_NAME |
| 311 | |
| 312 | static struct nla_policy testdata_policy[NUM_MT76_TM_ATTRS] = { |
| 313 | [MT76_TM_ATTR_STATE] = { .type = NLA_U8 }, |
| 314 | [MT76_TM_ATTR_MTD_PART] = { .type = NLA_STRING }, |
| 315 | [MT76_TM_ATTR_MTD_OFFSET] = { .type = NLA_U32 }, |
| 316 | [MT76_TM_ATTR_TX_COUNT] = { .type = NLA_U32 }, |
| 317 | [MT76_TM_ATTR_TX_LENGTH] = { .type = NLA_U32 }, |
| 318 | [MT76_TM_ATTR_TX_RATE_MODE] = { .type = NLA_U8 }, |
| 319 | [MT76_TM_ATTR_TX_RATE_NSS] = { .type = NLA_U8 }, |
| 320 | [MT76_TM_ATTR_TX_RATE_IDX] = { .type = NLA_U8 }, |
| 321 | [MT76_TM_ATTR_TX_RATE_SGI] = { .type = NLA_U8 }, |
| 322 | [MT76_TM_ATTR_TX_RATE_LDPC] = { .type = NLA_U8 }, |
| 323 | [MT76_TM_ATTR_TX_RATE_STBC] = { .type = NLA_U8 }, |
| 324 | [MT76_TM_ATTR_TX_LTF] = { .type = NLA_U8 }, |
| 325 | [MT76_TM_ATTR_TX_POWER_CONTROL] = { .type = NLA_U8 }, |
| 326 | [MT76_TM_ATTR_TX_ANTENNA] = { .type = NLA_U8 }, |
| 327 | [MT76_TM_ATTR_FREQ_OFFSET] = { .type = NLA_U32 }, |
| 328 | [MT76_TM_ATTR_STATS] = { .type = NLA_NESTED }, |
| 329 | }; |
| 330 | |
| 331 | const struct tm_field msg_field = { |
| 332 | .print = print_nested, |
| 333 | .fields = testdata_fields, |
| 334 | .policy = testdata_policy, |
| 335 | .len = ARRAY_SIZE(testdata_fields) |
| 336 | }; |