diff --git a/README.md b/README.md index 2b799cd..5847478 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,13 @@ The Realtek 11ac driver that simply devours its competitors. Devourer is a userspace re-implementation of Realtek's RTL88xxAU Wi-Fi driver (Jaguar family: RTL8812AU shipping on every band/channel, -RTL8821AU shipping at 2.4 GHz and 5 GHz UNII-1 RX, RTL8811AU supported -via the 8812 code path, RTL8814AU RX-only), speaking to the chip -directly through libusb. No kernel module, no `rtl8812au` DKMS tree — -just a C++20 static library (`WiFiDriver`) plus two demo executables -for RX and TX. It is the OpenIPC project's driver of choice for -long-range video links built on top of cheap Realtek 11ac USB radios. +RTL8821AU shipping on every band with partial-receiver-dependent 5 GHz +UNII-2/3 TX, RTL8811AU supported via the 8812 code path, RTL8814AU +RX-only), speaking to the chip directly through libusb. No kernel +module, no `rtl8812au` DKMS tree — just a C++20 static library +(`WiFiDriver`) plus two demo executables for RX and TX. It is the +OpenIPC project's driver of choice for long-range video links built on +top of cheap Realtek 11ac USB radios. ## Hardware landscape @@ -26,7 +27,7 @@ layered on top. | **RTL8812AU** | 2T2R | TX + RX | TX + RX | TX + RX | VID/PID `0bda:8812`; reference part — works on every channel/band combo | | **RTL8811AU** | 1T1R | TX + RX | TX + RX | TX + RX | 1T1R cut of 8812 silicon; rides 8812 code path with `RFType=RF_TYPE_1T1R` selected from `REG_SYS_CFG` bit 27. Status mirrored from 8812 — no 8811AU DUT in the test rig | | **RTL8814AU** | 4T4R, 3-SS max | RX only | RX only | RX only | VID/PID `0bda:8813`; 2-SS effective on USB-2. TX submits succeed on the bulk pipe but nothing reaches the air at any band — see issue #36 | -| **RTL8821AU** | 1T1R AC + BT | TX + RX | RX only | none | OEM-rebadged as TP-Link Archer T2U Plus (`2357:0120`) etc; Android hotplug works end-to-end. 5 GHz UNII-1 RX works on this chip but TX is silently gated — sweep `--channel 36/40/44/48` to verify on your hardware. UNII-2 / UNII-3 (ch100, 149, 161, …) are completely broken — both TX and RX. Five hypotheses tested + refuted to date (IQK port, BB-divergence overrides, missing init writes, post-channel-set delay, TX power cap) — see issue #59 | +| **RTL8821AU** | 1T1R AC + BT | TX + RX | TX + RX | TX (receiver-dependent) + RX (receiver-dependent) | OEM-rebadged as TP-Link Archer T2U Plus (`2357:0120`) etc; Android hotplug works end-to-end. 5 GHz UNII-1 TX validated against both 8812AU and 8814AU receivers post-T1 EFUSE per-rate TX-power port. UNII-2/3 (ch100+): TX reaches the air at full rate when received by an 8814AU peer (matrix at ch100 shows 4160/4500 hits) but is dropped by an 8812AU peer; RX path on 8821AU itself is broken at UNII-2/3 (kernel-TX → 8821-devourer-RX = 0 hits at ch100+). Six hypotheses tested + refuted for the residual UNII-2/3 RX gate and 8812-RX-side asymmetry — see issue #59 | Successor families (`Jaguar2` / `Jaguar+` — 8812BU, 8822BU/BE, etc., and the later `Kestrel` 11ax generation) are **out of scope**: they share diff --git a/hal/Hal8812a_PhyRegPg.h b/hal/Hal8812a_PhyRegPg.h new file mode 100644 index 0000000..df75220 --- /dev/null +++ b/hal/Hal8812a_PhyRegPg.h @@ -0,0 +1,71 @@ +#ifndef HAL_8812A_PHY_REG_PG_H +#define HAL_8812A_PHY_REG_PG_H + +#include + +/* Verbatim copy of `array_mp_8812a_phy_reg_pg` from + * aircrack-ng/rtl8812au@v5.6.4.2_35491.20191025 + * hal/phydm/rtl8812a/halhwimg8812a_bb.c:1012-1058. + * + * Per-rate TX-power table. Each row is 6 entries: + * {band, rfpath, tx_num, addr, bitmask, data} + * - band: 0 = 2.4G, 1 = 5G + * - rfpath: 0 = A, 1 = B + * - tx_num: 0 = 1T, 1 = 2T + * - addr: target BB register (e.g. 0xc24 = OFDM 18/6 power), + * each byte of `data` is the raw power index for one rate + * - bitmask: 0xffffffff for the full 4-byte target + * - data: 4 packed bytes — per-rate target value, parsed by + * `EepromManager::LoadTxPowerByRate` into + * `TxPwrByRateOffset[band][path][rate]`. */ + +static const uint32_t kHal8812aPhyRegPg[] = { + 0, 0, 0, 0x00000c20, 0xffffffff, 0x34363840, + 0, 0, 0, 0x00000c24, 0xffffffff, 0x42424444, + 0, 0, 0, 0x00000c28, 0xffffffff, 0x30323638, + 0, 0, 0, 0x00000c2c, 0xffffffff, 0x40424444, + 0, 0, 0, 0x00000c30, 0xffffffff, 0x28303236, + 0, 0, 1, 0x00000c34, 0xffffffff, 0x38404242, + 0, 0, 1, 0x00000c38, 0xffffffff, 0x26283034, + 0, 0, 0, 0x00000c3c, 0xffffffff, 0x40424444, + 0, 0, 0, 0x00000c40, 0xffffffff, 0x28303236, + 0, 0, 0, 0x00000c44, 0xffffffff, 0x42422426, + 0, 0, 1, 0x00000c48, 0xffffffff, 0x30343840, + 0, 0, 1, 0x00000c4c, 0xffffffff, 0x22242628, + 0, 1, 0, 0x00000e20, 0xffffffff, 0x34363840, + 0, 1, 0, 0x00000e24, 0xffffffff, 0x42424444, + 0, 1, 0, 0x00000e28, 0xffffffff, 0x30323638, + 0, 1, 0, 0x00000e2c, 0xffffffff, 0x40424444, + 0, 1, 0, 0x00000e30, 0xffffffff, 0x28303236, + 0, 1, 1, 0x00000e34, 0xffffffff, 0x38404242, + 0, 1, 1, 0x00000e38, 0xffffffff, 0x26283034, + 0, 1, 0, 0x00000e3c, 0xffffffff, 0x40424444, + 0, 1, 0, 0x00000e40, 0xffffffff, 0x28303236, + 0, 1, 0, 0x00000e44, 0xffffffff, 0x42422426, + 0, 1, 1, 0x00000e48, 0xffffffff, 0x30343840, + 0, 1, 1, 0x00000e4c, 0xffffffff, 0x22242628, + 1, 0, 0, 0x00000c24, 0xffffffff, 0x42424444, + 1, 0, 0, 0x00000c28, 0xffffffff, 0x30323640, + 1, 0, 0, 0x00000c2c, 0xffffffff, 0x40424444, + 1, 0, 0, 0x00000c30, 0xffffffff, 0x28303236, + 1, 0, 1, 0x00000c34, 0xffffffff, 0x38404242, + 1, 0, 1, 0x00000c38, 0xffffffff, 0x26283034, + 1, 0, 0, 0x00000c3c, 0xffffffff, 0x40424444, + 1, 0, 0, 0x00000c40, 0xffffffff, 0x28303236, + 1, 0, 0, 0x00000c44, 0xffffffff, 0x42422426, + 1, 0, 1, 0x00000c48, 0xffffffff, 0x30343840, + 1, 0, 1, 0x00000c4c, 0xffffffff, 0x22242628, + 1, 1, 0, 0x00000e24, 0xffffffff, 0x42424444, + 1, 1, 0, 0x00000e28, 0xffffffff, 0x30323640, + 1, 1, 0, 0x00000e2c, 0xffffffff, 0x40424444, + 1, 1, 0, 0x00000e30, 0xffffffff, 0x28303236, + 1, 1, 1, 0x00000e34, 0xffffffff, 0x38404242, + 1, 1, 1, 0x00000e38, 0xffffffff, 0x26283034, + 1, 1, 0, 0x00000e3c, 0xffffffff, 0x40424444, + 1, 1, 0, 0x00000e40, 0xffffffff, 0x28303236, + 1, 1, 0, 0x00000e44, 0xffffffff, 0x42422426, + 1, 1, 1, 0x00000e48, 0xffffffff, 0x30343840, + 1, 1, 1, 0x00000e4c, 0xffffffff, 0x22242628 +}; + +#endif /* HAL_8812A_PHY_REG_PG_H */ diff --git a/hal/Hal8812a_TxpwrLmt.h b/hal/Hal8812a_TxpwrLmt.h new file mode 100644 index 0000000..b0a4dd0 --- /dev/null +++ b/hal/Hal8812a_TxpwrLmt.h @@ -0,0 +1,585 @@ +#ifndef HAL_8812A_TXPWR_LMT_H +#define HAL_8812A_TXPWR_LMT_H + +/* Verbatim copy of `array_mp_8812a_txpwr_lmt` from + * aircrack-ng/rtl8812au@v5.6.4.2_35491.20191025 + * hal/phydm/rtl8812a/halhwimg8812a_rf.c:1371-1937. + * + * Per-region (FCC/ETSI/MKK) regulatory TX-power limit table. Each row is + * 7 entries: regulation, band, bandwidth, rate_section, ntx, channel, + * power_index_limit. Parsed by `EepromManager::LoadTxPowerLimit` which + * builds the 5D lookup `TxPwrLimit[regulation][band][bw][rs][ntx][ch]` + * used by `GetTxPowerIndexBase` to cap `by_rate_diff` per regulatory + * region — port of upstream `PHY_GetTxPowerLimit` from hal_com_phycfg.c. */ + +static const char *kHal8812aTxpwrLmt[] = { + + "FCC", "2.4G", "20M", "CCK", "1T", "01", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "01", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "01", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "02", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "02", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "02", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "03", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "03", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "03", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "04", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "04", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "04", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "05", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "05", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "05", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "06", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "06", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "06", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "07", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "07", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "07", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "08", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "08", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "08", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "09", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "09", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "09", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "10", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "10", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "10", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "11", "36", + "ETSI", "2.4G", "20M", "CCK", "1T", "11", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "11", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "12", "63", + "ETSI", "2.4G", "20M", "CCK", "1T", "12", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "12", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "13", "63", + "ETSI", "2.4G", "20M", "CCK", "1T", "13", "32", + "MKK", "2.4G", "20M", "CCK", "1T", "13", "32", + "FCC", "2.4G", "20M", "CCK", "1T", "14", "63", + "ETSI", "2.4G", "20M", "CCK", "1T", "14", "63", + "MKK", "2.4G", "20M", "CCK", "1T", "14", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "01", "34", + "ETSI", "2.4G", "20M", "OFDM", "1T", "01", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "01", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "02", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "02", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "02", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "03", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "03", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "03", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "04", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "04", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "04", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "05", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "05", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "05", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "06", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "06", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "06", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "07", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "07", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "07", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "08", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "08", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "08", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "09", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "09", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "09", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "10", "36", + "ETSI", "2.4G", "20M", "OFDM", "1T", "10", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "10", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "11", "32", + "ETSI", "2.4G", "20M", "OFDM", "1T", "11", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "11", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "12", "63", + "ETSI", "2.4G", "20M", "OFDM", "1T", "12", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "12", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "13", "63", + "ETSI", "2.4G", "20M", "OFDM", "1T", "13", "32", + "MKK", "2.4G", "20M", "OFDM", "1T", "13", "32", + "FCC", "2.4G", "20M", "OFDM", "1T", "14", "63", + "ETSI", "2.4G", "20M", "OFDM", "1T", "14", "63", + "MKK", "2.4G", "20M", "OFDM", "1T", "14", "63", + "FCC", "2.4G", "20M", "HT", "1T", "01", "34", + "ETSI", "2.4G", "20M", "HT", "1T", "01", "32", + "MKK", "2.4G", "20M", "HT", "1T", "01", "32", + "FCC", "2.4G", "20M", "HT", "1T", "02", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "02", "32", + "MKK", "2.4G", "20M", "HT", "1T", "02", "32", + "FCC", "2.4G", "20M", "HT", "1T", "03", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "03", "32", + "MKK", "2.4G", "20M", "HT", "1T", "03", "32", + "FCC", "2.4G", "20M", "HT", "1T", "04", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "04", "32", + "MKK", "2.4G", "20M", "HT", "1T", "04", "32", + "FCC", "2.4G", "20M", "HT", "1T", "05", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "05", "32", + "MKK", "2.4G", "20M", "HT", "1T", "05", "32", + "FCC", "2.4G", "20M", "HT", "1T", "06", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "06", "32", + "MKK", "2.4G", "20M", "HT", "1T", "06", "32", + "FCC", "2.4G", "20M", "HT", "1T", "07", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "07", "32", + "MKK", "2.4G", "20M", "HT", "1T", "07", "32", + "FCC", "2.4G", "20M", "HT", "1T", "08", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "08", "32", + "MKK", "2.4G", "20M", "HT", "1T", "08", "32", + "FCC", "2.4G", "20M", "HT", "1T", "09", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "09", "32", + "MKK", "2.4G", "20M", "HT", "1T", "09", "32", + "FCC", "2.4G", "20M", "HT", "1T", "10", "36", + "ETSI", "2.4G", "20M", "HT", "1T", "10", "32", + "MKK", "2.4G", "20M", "HT", "1T", "10", "32", + "FCC", "2.4G", "20M", "HT", "1T", "11", "32", + "ETSI", "2.4G", "20M", "HT", "1T", "11", "32", + "MKK", "2.4G", "20M", "HT", "1T", "11", "32", + "FCC", "2.4G", "20M", "HT", "1T", "12", "63", + "ETSI", "2.4G", "20M", "HT", "1T", "12", "32", + "MKK", "2.4G", "20M", "HT", "1T", "12", "32", + "FCC", "2.4G", "20M", "HT", "1T", "13", "63", + "ETSI", "2.4G", "20M", "HT", "1T", "13", "32", + "MKK", "2.4G", "20M", "HT", "1T", "13", "32", + "FCC", "2.4G", "20M", "HT", "1T", "14", "63", + "ETSI", "2.4G", "20M", "HT", "1T", "14", "63", + "MKK", "2.4G", "20M", "HT", "1T", "14", "63", + "FCC", "2.4G", "20M", "HT", "2T", "01", "32", + "ETSI", "2.4G", "20M", "HT", "2T", "01", "32", + "MKK", "2.4G", "20M", "HT", "2T", "01", "32", + "FCC", "2.4G", "20M", "HT", "2T", "02", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "02", "32", + "MKK", "2.4G", "20M", "HT", "2T", "02", "32", + "FCC", "2.4G", "20M", "HT", "2T", "03", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "03", "32", + "MKK", "2.4G", "20M", "HT", "2T", "03", "32", + "FCC", "2.4G", "20M", "HT", "2T", "04", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "04", "32", + "MKK", "2.4G", "20M", "HT", "2T", "04", "32", + "FCC", "2.4G", "20M", "HT", "2T", "05", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "05", "32", + "MKK", "2.4G", "20M", "HT", "2T", "05", "32", + "FCC", "2.4G", "20M", "HT", "2T", "06", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "06", "32", + "MKK", "2.4G", "20M", "HT", "2T", "06", "32", + "FCC", "2.4G", "20M", "HT", "2T", "07", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "07", "32", + "MKK", "2.4G", "20M", "HT", "2T", "07", "32", + "FCC", "2.4G", "20M", "HT", "2T", "08", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "08", "32", + "MKK", "2.4G", "20M", "HT", "2T", "08", "32", + "FCC", "2.4G", "20M", "HT", "2T", "09", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "09", "32", + "MKK", "2.4G", "20M", "HT", "2T", "09", "32", + "FCC", "2.4G", "20M", "HT", "2T", "10", "34", + "ETSI", "2.4G", "20M", "HT", "2T", "10", "32", + "MKK", "2.4G", "20M", "HT", "2T", "10", "32", + "FCC", "2.4G", "20M", "HT", "2T", "11", "30", + "ETSI", "2.4G", "20M", "HT", "2T", "11", "32", + "MKK", "2.4G", "20M", "HT", "2T", "11", "32", + "FCC", "2.4G", "20M", "HT", "2T", "12", "63", + "ETSI", "2.4G", "20M", "HT", "2T", "12", "32", + "MKK", "2.4G", "20M", "HT", "2T", "12", "32", + "FCC", "2.4G", "20M", "HT", "2T", "13", "63", + "ETSI", "2.4G", "20M", "HT", "2T", "13", "32", + "MKK", "2.4G", "20M", "HT", "2T", "13", "32", + "FCC", "2.4G", "20M", "HT", "2T", "14", "63", + "ETSI", "2.4G", "20M", "HT", "2T", "14", "63", + "MKK", "2.4G", "20M", "HT", "2T", "14", "63", + "FCC", "2.4G", "40M", "HT", "1T", "01", "63", + "ETSI", "2.4G", "40M", "HT", "1T", "01", "63", + "MKK", "2.4G", "40M", "HT", "1T", "01", "63", + "FCC", "2.4G", "40M", "HT", "1T", "02", "63", + "ETSI", "2.4G", "40M", "HT", "1T", "02", "63", + "MKK", "2.4G", "40M", "HT", "1T", "02", "63", + "FCC", "2.4G", "40M", "HT", "1T", "03", "32", + "ETSI", "2.4G", "40M", "HT", "1T", "03", "32", + "MKK", "2.4G", "40M", "HT", "1T", "03", "32", + "FCC", "2.4G", "40M", "HT", "1T", "04", "36", + "ETSI", "2.4G", "40M", "HT", "1T", "04", "32", + "MKK", "2.4G", "40M", "HT", "1T", "04", "32", + "FCC", "2.4G", "40M", "HT", "1T", "05", "36", + "ETSI", "2.4G", "40M", "HT", "1T", "05", "32", + "MKK", "2.4G", "40M", "HT", "1T", "05", "32", + "FCC", "2.4G", "40M", "HT", "1T", "06", "36", + "ETSI", "2.4G", "40M", "HT", "1T", "06", "32", + "MKK", "2.4G", "40M", "HT", "1T", "06", "32", + "FCC", "2.4G", "40M", "HT", "1T", "07", "36", + "ETSI", "2.4G", "40M", "HT", "1T", "07", "32", + "MKK", "2.4G", "40M", "HT", "1T", "07", "32", + "FCC", "2.4G", "40M", "HT", "1T", "08", "36", + "ETSI", "2.4G", "40M", "HT", "1T", "08", "32", + "MKK", "2.4G", "40M", "HT", "1T", "08", "32", + "FCC", "2.4G", "40M", "HT", "1T", "09", "36", + "ETSI", "2.4G", "40M", "HT", "1T", "09", "32", + "MKK", "2.4G", "40M", "HT", "1T", "09", "32", + "FCC", "2.4G", "40M", "HT", "1T", "10", "36", + "ETSI", "2.4G", "40M", "HT", "1T", "10", "32", + "MKK", "2.4G", "40M", "HT", "1T", "10", "32", + "FCC", "2.4G", "40M", "HT", "1T", "11", "32", + "ETSI", "2.4G", "40M", "HT", "1T", "11", "32", + "MKK", "2.4G", "40M", "HT", "1T", "11", "32", + "FCC", "2.4G", "40M", "HT", "1T", "12", "63", + "ETSI", "2.4G", "40M", "HT", "1T", "12", "32", + "MKK", "2.4G", "40M", "HT", "1T", "12", "32", + "FCC", "2.4G", "40M", "HT", "1T", "13", "63", + "ETSI", "2.4G", "40M", "HT", "1T", "13", "32", + "MKK", "2.4G", "40M", "HT", "1T", "13", "32", + "FCC", "2.4G", "40M", "HT", "1T", "14", "63", + "ETSI", "2.4G", "40M", "HT", "1T", "14", "63", + "MKK", "2.4G", "40M", "HT", "1T", "14", "63", + "FCC", "2.4G", "40M", "HT", "2T", "01", "63", + "ETSI", "2.4G", "40M", "HT", "2T", "01", "63", + "MKK", "2.4G", "40M", "HT", "2T", "01", "63", + "FCC", "2.4G", "40M", "HT", "2T", "02", "63", + "ETSI", "2.4G", "40M", "HT", "2T", "02", "63", + "MKK", "2.4G", "40M", "HT", "2T", "02", "63", + "FCC", "2.4G", "40M", "HT", "2T", "03", "30", + "ETSI", "2.4G", "40M", "HT", "2T", "03", "30", + "MKK", "2.4G", "40M", "HT", "2T", "03", "30", + "FCC", "2.4G", "40M", "HT", "2T", "04", "34", + "ETSI", "2.4G", "40M", "HT", "2T", "04", "30", + "MKK", "2.4G", "40M", "HT", "2T", "04", "30", + "FCC", "2.4G", "40M", "HT", "2T", "05", "34", + "ETSI", "2.4G", "40M", "HT", "2T", "05", "30", + "MKK", "2.4G", "40M", "HT", "2T", "05", "30", + "FCC", "2.4G", "40M", "HT", "2T", "06", "34", + "ETSI", "2.4G", "40M", "HT", "2T", "06", "30", + "MKK", "2.4G", "40M", "HT", "2T", "06", "30", + "FCC", "2.4G", "40M", "HT", "2T", "07", "34", + "ETSI", "2.4G", "40M", "HT", "2T", "07", "30", + "MKK", "2.4G", "40M", "HT", "2T", "07", "30", + "FCC", "2.4G", "40M", "HT", "2T", "08", "34", + "ETSI", "2.4G", "40M", "HT", "2T", "08", "30", + "MKK", "2.4G", "40M", "HT", "2T", "08", "30", + "FCC", "2.4G", "40M", "HT", "2T", "09", "34", + "ETSI", "2.4G", "40M", "HT", "2T", "09", "30", + "MKK", "2.4G", "40M", "HT", "2T", "09", "30", + "FCC", "2.4G", "40M", "HT", "2T", "10", "34", + "ETSI", "2.4G", "40M", "HT", "2T", "10", "30", + "MKK", "2.4G", "40M", "HT", "2T", "10", "30", + "FCC", "2.4G", "40M", "HT", "2T", "11", "30", + "ETSI", "2.4G", "40M", "HT", "2T", "11", "30", + "MKK", "2.4G", "40M", "HT", "2T", "11", "30", + "FCC", "2.4G", "40M", "HT", "2T", "12", "63", + "ETSI", "2.4G", "40M", "HT", "2T", "12", "32", + "MKK", "2.4G", "40M", "HT", "2T", "12", "32", + "FCC", "2.4G", "40M", "HT", "2T", "13", "63", + "ETSI", "2.4G", "40M", "HT", "2T", "13", "32", + "MKK", "2.4G", "40M", "HT", "2T", "13", "32", + "FCC", "2.4G", "40M", "HT", "2T", "14", "63", + "ETSI", "2.4G", "40M", "HT", "2T", "14", "63", + "MKK", "2.4G", "40M", "HT", "2T", "14", "63", + "FCC", "5G", "20M", "OFDM", "1T", "36", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "36", "32", + "MKK", "5G", "20M", "OFDM", "1T", "36", "32", + "FCC", "5G", "20M", "OFDM", "1T", "40", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "40", "32", + "MKK", "5G", "20M", "OFDM", "1T", "40", "32", + "FCC", "5G", "20M", "OFDM", "1T", "44", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "44", "32", + "MKK", "5G", "20M", "OFDM", "1T", "44", "32", + "FCC", "5G", "20M", "OFDM", "1T", "48", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "48", "32", + "MKK", "5G", "20M", "OFDM", "1T", "48", "32", + "FCC", "5G", "20M", "OFDM", "1T", "52", "36", + "ETSI", "5G", "20M", "OFDM", "1T", "52", "32", + "MKK", "5G", "20M", "OFDM", "1T", "52", "32", + "FCC", "5G", "20M", "OFDM", "1T", "56", "34", + "ETSI", "5G", "20M", "OFDM", "1T", "56", "32", + "MKK", "5G", "20M", "OFDM", "1T", "56", "32", + "FCC", "5G", "20M", "OFDM", "1T", "60", "32", + "ETSI", "5G", "20M", "OFDM", "1T", "60", "32", + "MKK", "5G", "20M", "OFDM", "1T", "60", "32", + "FCC", "5G", "20M", "OFDM", "1T", "64", "28", + "ETSI", "5G", "20M", "OFDM", "1T", "64", "32", + "MKK", "5G", "20M", "OFDM", "1T", "64", "32", + "FCC", "5G", "20M", "OFDM", "1T", "100", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "100", "32", + "MKK", "5G", "20M", "OFDM", "1T", "100", "32", + "FCC", "5G", "20M", "OFDM", "1T", "104", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "104", "32", + "MKK", "5G", "20M", "OFDM", "1T", "104", "32", + "FCC", "5G", "20M", "OFDM", "1T", "108", "32", + "ETSI", "5G", "20M", "OFDM", "1T", "108", "32", + "MKK", "5G", "20M", "OFDM", "1T", "108", "32", + "FCC", "5G", "20M", "OFDM", "1T", "112", "34", + "ETSI", "5G", "20M", "OFDM", "1T", "112", "32", + "MKK", "5G", "20M", "OFDM", "1T", "112", "32", + "FCC", "5G", "20M", "OFDM", "1T", "116", "34", + "ETSI", "5G", "20M", "OFDM", "1T", "116", "32", + "MKK", "5G", "20M", "OFDM", "1T", "116", "32", + "FCC", "5G", "20M", "OFDM", "1T", "120", "36", + "ETSI", "5G", "20M", "OFDM", "1T", "120", "32", + "MKK", "5G", "20M", "OFDM", "1T", "120", "32", + "FCC", "5G", "20M", "OFDM", "1T", "124", "34", + "ETSI", "5G", "20M", "OFDM", "1T", "124", "32", + "MKK", "5G", "20M", "OFDM", "1T", "124", "32", + "FCC", "5G", "20M", "OFDM", "1T", "128", "32", + "ETSI", "5G", "20M", "OFDM", "1T", "128", "32", + "MKK", "5G", "20M", "OFDM", "1T", "128", "32", + "FCC", "5G", "20M", "OFDM", "1T", "132", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "132", "32", + "MKK", "5G", "20M", "OFDM", "1T", "132", "32", + "FCC", "5G", "20M", "OFDM", "1T", "136", "30", + "ETSI", "5G", "20M", "OFDM", "1T", "136", "32", + "MKK", "5G", "20M", "OFDM", "1T", "136", "32", + "FCC", "5G", "20M", "OFDM", "1T", "140", "28", + "ETSI", "5G", "20M", "OFDM", "1T", "140", "32", + "MKK", "5G", "20M", "OFDM", "1T", "140", "32", + "FCC", "5G", "20M", "OFDM", "1T", "149", "36", + "ETSI", "5G", "20M", "OFDM", "1T", "149", "32", + "MKK", "5G", "20M", "OFDM", "1T", "149", "63", + "FCC", "5G", "20M", "OFDM", "1T", "153", "36", + "ETSI", "5G", "20M", "OFDM", "1T", "153", "32", + "MKK", "5G", "20M", "OFDM", "1T", "153", "63", + "FCC", "5G", "20M", "OFDM", "1T", "157", "36", + "ETSI", "5G", "20M", "OFDM", "1T", "157", "32", + "MKK", "5G", "20M", "OFDM", "1T", "157", "63", + "FCC", "5G", "20M", "OFDM", "1T", "161", "36", + "ETSI", "5G", "20M", "OFDM", "1T", "161", "32", + "MKK", "5G", "20M", "OFDM", "1T", "161", "63", + "FCC", "5G", "20M", "OFDM", "1T", "165", "36", + "ETSI", "5G", "20M", "OFDM", "1T", "165", "32", + "MKK", "5G", "20M", "OFDM", "1T", "165", "63", + "FCC", "5G", "20M", "HT", "1T", "36", "30", + "ETSI", "5G", "20M", "HT", "1T", "36", "32", + "MKK", "5G", "20M", "HT", "1T", "36", "32", + "FCC", "5G", "20M", "HT", "1T", "40", "30", + "ETSI", "5G", "20M", "HT", "1T", "40", "32", + "MKK", "5G", "20M", "HT", "1T", "40", "32", + "FCC", "5G", "20M", "HT", "1T", "44", "30", + "ETSI", "5G", "20M", "HT", "1T", "44", "32", + "MKK", "5G", "20M", "HT", "1T", "44", "32", + "FCC", "5G", "20M", "HT", "1T", "48", "30", + "ETSI", "5G", "20M", "HT", "1T", "48", "32", + "MKK", "5G", "20M", "HT", "1T", "48", "32", + "FCC", "5G", "20M", "HT", "1T", "52", "36", + "ETSI", "5G", "20M", "HT", "1T", "52", "32", + "MKK", "5G", "20M", "HT", "1T", "52", "32", + "FCC", "5G", "20M", "HT", "1T", "56", "34", + "ETSI", "5G", "20M", "HT", "1T", "56", "32", + "MKK", "5G", "20M", "HT", "1T", "56", "32", + "FCC", "5G", "20M", "HT", "1T", "60", "32", + "ETSI", "5G", "20M", "HT", "1T", "60", "32", + "MKK", "5G", "20M", "HT", "1T", "60", "32", + "FCC", "5G", "20M", "HT", "1T", "64", "28", + "ETSI", "5G", "20M", "HT", "1T", "64", "32", + "MKK", "5G", "20M", "HT", "1T", "64", "32", + "FCC", "5G", "20M", "HT", "1T", "100", "30", + "ETSI", "5G", "20M", "HT", "1T", "100", "32", + "MKK", "5G", "20M", "HT", "1T", "100", "32", + "FCC", "5G", "20M", "HT", "1T", "104", "30", + "ETSI", "5G", "20M", "HT", "1T", "104", "32", + "MKK", "5G", "20M", "HT", "1T", "104", "32", + "FCC", "5G", "20M", "HT", "1T", "108", "32", + "ETSI", "5G", "20M", "HT", "1T", "108", "32", + "MKK", "5G", "20M", "HT", "1T", "108", "32", + "FCC", "5G", "20M", "HT", "1T", "112", "34", + "ETSI", "5G", "20M", "HT", "1T", "112", "32", + "MKK", "5G", "20M", "HT", "1T", "112", "32", + "FCC", "5G", "20M", "HT", "1T", "116", "34", + "ETSI", "5G", "20M", "HT", "1T", "116", "32", + "MKK", "5G", "20M", "HT", "1T", "116", "32", + "FCC", "5G", "20M", "HT", "1T", "120", "36", + "ETSI", "5G", "20M", "HT", "1T", "120", "32", + "MKK", "5G", "20M", "HT", "1T", "120", "32", + "FCC", "5G", "20M", "HT", "1T", "124", "34", + "ETSI", "5G", "20M", "HT", "1T", "124", "32", + "MKK", "5G", "20M", "HT", "1T", "124", "32", + "FCC", "5G", "20M", "HT", "1T", "128", "32", + "ETSI", "5G", "20M", "HT", "1T", "128", "32", + "MKK", "5G", "20M", "HT", "1T", "128", "32", + "FCC", "5G", "20M", "HT", "1T", "132", "30", + "ETSI", "5G", "20M", "HT", "1T", "132", "32", + "MKK", "5G", "20M", "HT", "1T", "132", "32", + "FCC", "5G", "20M", "HT", "1T", "136", "30", + "ETSI", "5G", "20M", "HT", "1T", "136", "32", + "MKK", "5G", "20M", "HT", "1T", "136", "32", + "FCC", "5G", "20M", "HT", "1T", "140", "28", + "ETSI", "5G", "20M", "HT", "1T", "140", "32", + "MKK", "5G", "20M", "HT", "1T", "140", "32", + "FCC", "5G", "20M", "HT", "1T", "149", "36", + "ETSI", "5G", "20M", "HT", "1T", "149", "32", + "MKK", "5G", "20M", "HT", "1T", "149", "63", + "FCC", "5G", "20M", "HT", "1T", "153", "36", + "ETSI", "5G", "20M", "HT", "1T", "153", "32", + "MKK", "5G", "20M", "HT", "1T", "153", "63", + "FCC", "5G", "20M", "HT", "1T", "157", "36", + "ETSI", "5G", "20M", "HT", "1T", "157", "32", + "MKK", "5G", "20M", "HT", "1T", "157", "63", + "FCC", "5G", "20M", "HT", "1T", "161", "36", + "ETSI", "5G", "20M", "HT", "1T", "161", "32", + "MKK", "5G", "20M", "HT", "1T", "161", "63", + "FCC", "5G", "20M", "HT", "1T", "165", "36", + "ETSI", "5G", "20M", "HT", "1T", "165", "32", + "MKK", "5G", "20M", "HT", "1T", "165", "63", + "FCC", "5G", "20M", "HT", "2T", "36", "28", + "ETSI", "5G", "20M", "HT", "2T", "36", "30", + "MKK", "5G", "20M", "HT", "2T", "36", "30", + "FCC", "5G", "20M", "HT", "2T", "40", "28", + "ETSI", "5G", "20M", "HT", "2T", "40", "30", + "MKK", "5G", "20M", "HT", "2T", "40", "30", + "FCC", "5G", "20M", "HT", "2T", "44", "28", + "ETSI", "5G", "20M", "HT", "2T", "44", "30", + "MKK", "5G", "20M", "HT", "2T", "44", "30", + "FCC", "5G", "20M", "HT", "2T", "48", "28", + "ETSI", "5G", "20M", "HT", "2T", "48", "30", + "MKK", "5G", "20M", "HT", "2T", "48", "30", + "FCC", "5G", "20M", "HT", "2T", "52", "34", + "ETSI", "5G", "20M", "HT", "2T", "52", "30", + "MKK", "5G", "20M", "HT", "2T", "52", "30", + "FCC", "5G", "20M", "HT", "2T", "56", "32", + "ETSI", "5G", "20M", "HT", "2T", "56", "30", + "MKK", "5G", "20M", "HT", "2T", "56", "30", + "FCC", "5G", "20M", "HT", "2T", "60", "30", + "ETSI", "5G", "20M", "HT", "2T", "60", "30", + "MKK", "5G", "20M", "HT", "2T", "60", "30", + "FCC", "5G", "20M", "HT", "2T", "64", "26", + "ETSI", "5G", "20M", "HT", "2T", "64", "30", + "MKK", "5G", "20M", "HT", "2T", "64", "30", + "FCC", "5G", "20M", "HT", "2T", "100", "28", + "ETSI", "5G", "20M", "HT", "2T", "100", "30", + "MKK", "5G", "20M", "HT", "2T", "100", "30", + "FCC", "5G", "20M", "HT", "2T", "104", "28", + "ETSI", "5G", "20M", "HT", "2T", "104", "30", + "MKK", "5G", "20M", "HT", "2T", "104", "30", + "FCC", "5G", "20M", "HT", "2T", "108", "30", + "ETSI", "5G", "20M", "HT", "2T", "108", "30", + "MKK", "5G", "20M", "HT", "2T", "108", "30", + "FCC", "5G", "20M", "HT", "2T", "112", "32", + "ETSI", "5G", "20M", "HT", "2T", "112", "30", + "MKK", "5G", "20M", "HT", "2T", "112", "30", + "FCC", "5G", "20M", "HT", "2T", "116", "32", + "ETSI", "5G", "20M", "HT", "2T", "116", "30", + "MKK", "5G", "20M", "HT", "2T", "116", "30", + "FCC", "5G", "20M", "HT", "2T", "120", "34", + "ETSI", "5G", "20M", "HT", "2T", "120", "30", + "MKK", "5G", "20M", "HT", "2T", "120", "30", + "FCC", "5G", "20M", "HT", "2T", "124", "32", + "ETSI", "5G", "20M", "HT", "2T", "124", "30", + "MKK", "5G", "20M", "HT", "2T", "124", "30", + "FCC", "5G", "20M", "HT", "2T", "128", "30", + "ETSI", "5G", "20M", "HT", "2T", "128", "30", + "MKK", "5G", "20M", "HT", "2T", "128", "30", + "FCC", "5G", "20M", "HT", "2T", "132", "28", + "ETSI", "5G", "20M", "HT", "2T", "132", "30", + "MKK", "5G", "20M", "HT", "2T", "132", "30", + "FCC", "5G", "20M", "HT", "2T", "136", "28", + "ETSI", "5G", "20M", "HT", "2T", "136", "30", + "MKK", "5G", "20M", "HT", "2T", "136", "30", + "FCC", "5G", "20M", "HT", "2T", "140", "26", + "ETSI", "5G", "20M", "HT", "2T", "140", "30", + "MKK", "5G", "20M", "HT", "2T", "140", "30", + "FCC", "5G", "20M", "HT", "2T", "149", "34", + "ETSI", "5G", "20M", "HT", "2T", "149", "30", + "MKK", "5G", "20M", "HT", "2T", "149", "63", + "FCC", "5G", "20M", "HT", "2T", "153", "34", + "ETSI", "5G", "20M", "HT", "2T", "153", "30", + "MKK", "5G", "20M", "HT", "2T", "153", "63", + "FCC", "5G", "20M", "HT", "2T", "157", "34", + "ETSI", "5G", "20M", "HT", "2T", "157", "30", + "MKK", "5G", "20M", "HT", "2T", "157", "63", + "FCC", "5G", "20M", "HT", "2T", "161", "34", + "ETSI", "5G", "20M", "HT", "2T", "161", "30", + "MKK", "5G", "20M", "HT", "2T", "161", "63", + "FCC", "5G", "20M", "HT", "2T", "165", "34", + "ETSI", "5G", "20M", "HT", "2T", "165", "30", + "MKK", "5G", "20M", "HT", "2T", "165", "63", + "FCC", "5G", "40M", "HT", "1T", "38", "30", + "ETSI", "5G", "40M", "HT", "1T", "38", "32", + "MKK", "5G", "40M", "HT", "1T", "38", "32", + "FCC", "5G", "40M", "HT", "1T", "46", "30", + "ETSI", "5G", "40M", "HT", "1T", "46", "32", + "MKK", "5G", "40M", "HT", "1T", "46", "32", + "FCC", "5G", "40M", "HT", "1T", "54", "32", + "ETSI", "5G", "40M", "HT", "1T", "54", "32", + "MKK", "5G", "40M", "HT", "1T", "54", "32", + "FCC", "5G", "40M", "HT", "1T", "62", "32", + "ETSI", "5G", "40M", "HT", "1T", "62", "32", + "MKK", "5G", "40M", "HT", "1T", "62", "32", + "FCC", "5G", "40M", "HT", "1T", "102", "28", + "ETSI", "5G", "40M", "HT", "1T", "102", "32", + "MKK", "5G", "40M", "HT", "1T", "102", "32", + "FCC", "5G", "40M", "HT", "1T", "110", "32", + "ETSI", "5G", "40M", "HT", "1T", "110", "32", + "MKK", "5G", "40M", "HT", "1T", "110", "32", + "FCC", "5G", "40M", "HT", "1T", "118", "36", + "ETSI", "5G", "40M", "HT", "1T", "118", "32", + "MKK", "5G", "40M", "HT", "1T", "118", "32", + "FCC", "5G", "40M", "HT", "1T", "126", "34", + "ETSI", "5G", "40M", "HT", "1T", "126", "32", + "MKK", "5G", "40M", "HT", "1T", "126", "32", + "FCC", "5G", "40M", "HT", "1T", "134", "32", + "ETSI", "5G", "40M", "HT", "1T", "134", "32", + "MKK", "5G", "40M", "HT", "1T", "134", "32", + "FCC", "5G", "40M", "HT", "1T", "151", "36", + "ETSI", "5G", "40M", "HT", "1T", "151", "32", + "MKK", "5G", "40M", "HT", "1T", "151", "63", + "FCC", "5G", "40M", "HT", "1T", "159", "36", + "ETSI", "5G", "40M", "HT", "1T", "159", "32", + "MKK", "5G", "40M", "HT", "1T", "159", "63", + "FCC", "5G", "40M", "HT", "2T", "38", "28", + "ETSI", "5G", "40M", "HT", "2T", "38", "30", + "MKK", "5G", "40M", "HT", "2T", "38", "30", + "FCC", "5G", "40M", "HT", "2T", "46", "28", + "ETSI", "5G", "40M", "HT", "2T", "46", "30", + "MKK", "5G", "40M", "HT", "2T", "46", "30", + "FCC", "5G", "40M", "HT", "2T", "54", "30", + "ETSI", "5G", "40M", "HT", "2T", "54", "30", + "MKK", "5G", "40M", "HT", "2T", "54", "30", + "FCC", "5G", "40M", "HT", "2T", "62", "30", + "ETSI", "5G", "40M", "HT", "2T", "62", "30", + "MKK", "5G", "40M", "HT", "2T", "62", "30", + "FCC", "5G", "40M", "HT", "2T", "102", "26", + "ETSI", "5G", "40M", "HT", "2T", "102", "30", + "MKK", "5G", "40M", "HT", "2T", "102", "30", + "FCC", "5G", "40M", "HT", "2T", "110", "30", + "ETSI", "5G", "40M", "HT", "2T", "110", "30", + "MKK", "5G", "40M", "HT", "2T", "110", "30", + "FCC", "5G", "40M", "HT", "2T", "118", "34", + "ETSI", "5G", "40M", "HT", "2T", "118", "30", + "MKK", "5G", "40M", "HT", "2T", "118", "30", + "FCC", "5G", "40M", "HT", "2T", "126", "32", + "ETSI", "5G", "40M", "HT", "2T", "126", "30", + "MKK", "5G", "40M", "HT", "2T", "126", "30", + "FCC", "5G", "40M", "HT", "2T", "134", "30", + "ETSI", "5G", "40M", "HT", "2T", "134", "30", + "MKK", "5G", "40M", "HT", "2T", "134", "30", + "FCC", "5G", "40M", "HT", "2T", "151", "34", + "ETSI", "5G", "40M", "HT", "2T", "151", "30", + "MKK", "5G", "40M", "HT", "2T", "151", "63", + "FCC", "5G", "40M", "HT", "2T", "159", "34", + "ETSI", "5G", "40M", "HT", "2T", "159", "30", + "MKK", "5G", "40M", "HT", "2T", "159", "63", + "FCC", "5G", "80M", "VHT", "1T", "42", "30", + "ETSI", "5G", "80M", "VHT", "1T", "42", "32", + "MKK", "5G", "80M", "VHT", "1T", "42", "32", + "FCC", "5G", "80M", "VHT", "1T", "58", "28", + "ETSI", "5G", "80M", "VHT", "1T", "58", "32", + "MKK", "5G", "80M", "VHT", "1T", "58", "32", + "FCC", "5G", "80M", "VHT", "1T", "106", "30", + "ETSI", "5G", "80M", "VHT", "1T", "106", "32", + "MKK", "5G", "80M", "VHT", "1T", "106", "32", + "FCC", "5G", "80M", "VHT", "1T", "122", "34", + "ETSI", "5G", "80M", "VHT", "1T", "122", "32", + "MKK", "5G", "80M", "VHT", "1T", "122", "32", + "FCC", "5G", "80M", "VHT", "1T", "155", "36", + "ETSI", "5G", "80M", "VHT", "1T", "155", "32", + "MKK", "5G", "80M", "VHT", "1T", "155", "63", + "FCC", "5G", "80M", "VHT", "2T", "42", "28", + "ETSI", "5G", "80M", "VHT", "2T", "42", "30", + "MKK", "5G", "80M", "VHT", "2T", "42", "30", + "FCC", "5G", "80M", "VHT", "2T", "58", "26", + "ETSI", "5G", "80M", "VHT", "2T", "58", "30", + "MKK", "5G", "80M", "VHT", "2T", "58", "30", + "FCC", "5G", "80M", "VHT", "2T", "106", "28", + "ETSI", "5G", "80M", "VHT", "2T", "106", "30", + "MKK", "5G", "80M", "VHT", "2T", "106", "30", + "FCC", "5G", "80M", "VHT", "2T", "122", "32", + "ETSI", "5G", "80M", "VHT", "2T", "122", "30", + "MKK", "5G", "80M", "VHT", "2T", "122", "30", + "FCC", "5G", "80M", "VHT", "2T", "155", "34", + "ETSI", "5G", "80M", "VHT", "2T", "155", "30", + "MKK", "5G", "80M", "VHT", "2T", "155", "63" + + +}; + +#endif /* HAL_8812A_TXPWR_LMT_H */ diff --git a/src/EepromManager.cpp b/src/EepromManager.cpp index bf239de..44c4724 100644 --- a/src/EepromManager.cpp +++ b/src/EepromManager.cpp @@ -5,7 +5,24 @@ #include "rtl8812a_hal.h" #include "rtw_efuse.h" +#include +#include #include +#include + +/* Cross-platform case-insensitive compare. POSIX has `strcasecmp` in + * ; MSVC has `_stricmp`; doing it by hand keeps the include + * surface portable. */ +static int devourer_strcaseeq(const char *a, const char *b) { + while (*a && *b) { + if (std::tolower(static_cast(*a)) != + std::tolower(static_cast(*b))) + return 0; + a++; + b++; + } + return *a == *b; +} EepromManager::EepromManager(RtlUsbAdapter device, Logger_t logger) : _device{device}, _logger{logger} { @@ -30,6 +47,12 @@ EepromManager::EepromManager(RtlUsbAdapter device, Logger_t logger) Hal_EfuseParseIDCode8812A(); EEPROMVersion = Hal_ReadPROMVersion8812A(_device, efuse_eeprom_data); EEPROMRegulatory = Hal_ReadTxPowerInfo8812A(_device, efuse_eeprom_data); + /* T1: populate the per-channel per-path TX-power tables from EFUSE so + * RadioManagementModule can compute per-rate TX power instead of using + * the uniform `SetTxPower(N)` shortcut. */ + LoadTxPowerInfo(); + LoadTxPowerByRate(); + LoadTxPowerLimit(); /* */ /* Read Bluetooth co-exist and initialize */ @@ -69,6 +92,10 @@ void EepromManager::LateInitFor8814A() { Hal_EfuseParseIDCode8812A(); EEPROMVersion = Hal_ReadPROMVersion8812A(_device, efuse_eeprom_data); EEPROMRegulatory = Hal_ReadTxPowerInfo8812A(_device, efuse_eeprom_data); + /* T1: populate the per-channel per-path TX-power tables from EFUSE so + * RadioManagementModule can compute per-rate TX power instead of using + * the uniform `SetTxPower(N)` shortcut. */ + LoadTxPowerInfo(); Hal_EfuseParseBTCoexistInfo8812A(); Hal_EfuseParseXtal_8812A(); Hal_ReadThermalMeter_8812A(); @@ -201,6 +228,697 @@ void EepromManager::read_chip_version_8812a(RtlUsbAdapter device) { dump_chip_info(version_id); } +/* Includes for the embedded upstream tables. */ +#include "../hal/Hal8812a_PhyRegPg.h" +#include "../hal/Hal8812a_TxpwrLmt.h" + +namespace { + +/* Rate → flat-index map for TxPwrByRateOffset. Mirrors upstream + * `PHY_GetRateIndexOfTxPowerByRate`. -1 = rate not in table. */ +int8_t rate_to_idx(uint8_t rate) { + switch (rate) { + /* CCK */ + case 0x02: return 0; /* MGN_1M */ + case 0x04: return 1; /* MGN_2M */ + case 0x0B: return 2; /* MGN_5_5M */ + case 0x16: return 3; /* MGN_11M */ + /* OFDM */ + case 0x0C: return 4; /* MGN_6M */ + case 0x12: return 5; /* MGN_9M */ + case 0x18: return 6; /* MGN_12M */ + case 0x24: return 7; /* MGN_18M */ + case 0x30: return 8; /* MGN_24M */ + case 0x48: return 9; /* MGN_36M */ + case 0x60: return 10; /* MGN_48M */ + case 0x6C: return 11; /* MGN_54M */ + default: + /* MGN_MCS0..31 = 0x80..0x9F → idx 12..43. */ + if (rate >= 0x80 && rate <= 0x9F) + return static_cast(12 + (rate - 0x80)); + /* MGN_VHT1SS_MCS0..VHT4SS_MCS9 = 0xA0..0xC7 → idx 44..83. */ + if (rate >= 0xA0 && rate <= 0xC7) + return static_cast(44 + (rate - 0xA0)); + return -1; + } +} + +/* RATE_SECTION enum from upstream `phydm_pre_define.h`. */ +enum { RS_CCK = 0, RS_OFDM, RS_HT_1SS, RS_HT_2SS, RS_HT_3SS, RS_HT_4SS, + RS_VHT_1SS, RS_VHT_2SS, RS_VHT_3SS, RS_VHT_4SS }; + +/* (RegAddr → 4 rates) port of upstream `PHY_GetRateValuesOfTxPowerByRate`. + * Only the regs that appear in `kHal8812aPhyRegPg` for 8812 are mapped. */ +struct RateRow { + uint16_t reg; + uint8_t rates[4]; + uint8_t count; +}; +static const RateRow kRateMap[] = { + /* path-A */ + {0xc20, {0x02, 0x04, 0x0B, 0x16}, 4}, /* CCK 1/2/5.5/11 */ + {0xc24, {0x0C, 0x12, 0x18, 0x24}, 4}, /* OFDM 6/9/12/18 */ + {0xc28, {0x30, 0x48, 0x60, 0x6C}, 4}, /* OFDM 24/36/48/54 */ + {0xc2c, {0x80, 0x81, 0x82, 0x83}, 4}, /* MCS0..3 */ + {0xc30, {0x84, 0x85, 0x86, 0x87}, 4}, /* MCS4..7 */ + {0xc34, {0x88, 0x89, 0x8A, 0x8B}, 4}, /* MCS8..11 */ + {0xc38, {0x8C, 0x8D, 0x8E, 0x8F}, 4}, /* MCS12..15 */ + {0xc3c, {0xA0, 0xA1, 0xA2, 0xA3}, 4}, /* VHT1SS_MCS0..3 */ + {0xc40, {0xA4, 0xA5, 0xA6, 0xA7}, 4}, /* VHT1SS_MCS4..7 */ + {0xc44, {0xA8, 0xA9, 0xAA, 0xAB}, 4}, /* VHT1SS_MCS8,9, VHT2SS_MCS0,1 */ + {0xc48, {0xAC, 0xAD, 0xAE, 0xAF}, 4}, /* VHT2SS_MCS2..5 */ + {0xc4c, {0xB0, 0xB1, 0xB2, 0xB3}, 4}, /* VHT2SS_MCS6..9 */ + /* path-B */ + {0xe20, {0x02, 0x04, 0x0B, 0x16}, 4}, + {0xe24, {0x0C, 0x12, 0x18, 0x24}, 4}, + {0xe28, {0x30, 0x48, 0x60, 0x6C}, 4}, + {0xe2c, {0x80, 0x81, 0x82, 0x83}, 4}, + {0xe30, {0x84, 0x85, 0x86, 0x87}, 4}, + {0xe34, {0x88, 0x89, 0x8A, 0x8B}, 4}, + {0xe38, {0x8C, 0x8D, 0x8E, 0x8F}, 4}, + {0xe3c, {0xA0, 0xA1, 0xA2, 0xA3}, 4}, + {0xe40, {0xA4, 0xA5, 0xA6, 0xA7}, 4}, + {0xe44, {0xA8, 0xA9, 0xAA, 0xAB}, 4}, + {0xe48, {0xAC, 0xAD, 0xAE, 0xAF}, 4}, + {0xe4c, {0xB0, 0xB1, 0xB2, 0xB3}, 4}, +}; + +/* Section base rate index — port of upstream `rate_sec_base[RATE_SECTION_NUM]` + * (the rate whose value becomes the section's base for offset normalization). */ +static const uint8_t kSectionBaseRate[10] = { + 0x16, /* RS_CCK → MGN_11M */ + 0x6C, /* RS_OFDM → MGN_54M */ + 0x87, /* RS_HT_1SS → MGN_MCS7 */ + 0x8F, /* RS_HT_2SS → MGN_MCS15 */ + 0x97, /* RS_HT_3SS → MGN_MCS23 */ + 0x9F, /* RS_HT_4SS → MGN_MCS31 */ + 0xA7, /* RS_VHT_1SS → MGN_VHT1SS_MCS7 */ + 0xB1, /* RS_VHT_2SS → MGN_VHT2SS_MCS7 */ + 0xBB, /* RS_VHT_3SS → MGN_VHT3SS_MCS7 */ + 0xC5, /* RS_VHT_4SS → MGN_VHT4SS_MCS7 */ +}; + +int8_t rate_to_section(uint8_t rate) { + if (rate == 0x02 || rate == 0x04 || rate == 0x0B || rate == 0x16) return RS_CCK; + if (rate >= 0x0C && rate <= 0x6C) return RS_OFDM; + if (rate >= 0x80 && rate <= 0x87) return RS_HT_1SS; + if (rate >= 0x88 && rate <= 0x8F) return RS_HT_2SS; + if (rate >= 0x90 && rate <= 0x97) return RS_HT_3SS; + if (rate >= 0x98 && rate <= 0x9F) return RS_HT_4SS; + if (rate >= 0xA0 && rate <= 0xA9) return RS_VHT_1SS; + if (rate >= 0xAA && rate <= 0xB3) return RS_VHT_2SS; + if (rate >= 0xB4 && rate <= 0xBD) return RS_VHT_3SS; + if (rate >= 0xBE && rate <= 0xC7) return RS_VHT_4SS; + return -1; +} + +/* Channel → index in the per-band TX-power-limit table (port of upstream + * `phy_GetChannelIndexOfTxPowerLimit`). For 2.4G it's channel-1 (1..14). + * For 5G it's the index in the 65-entry center_ch_5g_all table. */ +int8_t lmt_ch_idx_2g(uint8_t ch) { + if (ch >= 1 && ch <= 14) return static_cast(ch - 1); + return -1; +} +/* The 5G channel index table is defined as a file-scope static below + * (`kCenterCh5gAll`). lmt_ch_idx_5g uses the same definition via a small + * inline lookup in `LoadTxPowerLimit` rather than re-declaring it here. */ + +uint8_t parse_decimal(const char *s) { + unsigned r = 0; + while (*s >= '0' && *s <= '9') { r = r * 10 + (*s - '0'); s++; } + return static_cast(r); +} + +} /* namespace */ + +/* Helper: 4-bit signed nibble (Realtek's PG diff encoding) to int8_t. */ +static inline int8_t pg_msb_diff(uint8_t v) { + uint8_t n = (v >> 4) & 0x0f; + return static_cast((n & 0x08) ? (n | 0xf0) : n); +} +static inline int8_t pg_lsb_diff(uint8_t v) { + uint8_t n = v & 0x0f; + return static_cast((n & 0x08) ? (n | 0xf0) : n); +} + +/* Per-channel group classifier — port of upstream `rtw_get_ch_group` in + * `core/rtw_rf.c`. Returns 0 = 2.4G, 1 = 5G, 0xFF on invalid channel. + * `group` is the index into the per-band EFUSE PG table; `cck_group` is + * the 2.4G CCK sub-group (mostly == group, except ch14 → 5). */ +static uint8_t classify_channel(uint8_t ch, uint8_t *group, uint8_t *cck_group) { + if (ch <= 14) { + int gp = -1, cck_gp = -1; + if (1 <= ch && ch <= 2) gp = 0; + else if (3 <= ch && ch <= 5) gp = 1; + else if (6 <= ch && ch <= 8) gp = 2; + else if (9 <= ch && ch <= 11) gp = 3; + else if (12 <= ch && ch <= 14) gp = 4; + cck_gp = (ch == 14) ? 5 : gp; + if (gp < 0) return 0xFF; + if (group) *group = static_cast(gp); + if (cck_group) *cck_group = static_cast(cck_gp); + return 0; /* 2.4G */ + } + int gp = -1; + if (15 <= ch && ch <= 42) gp = 0; + else if (44 <= ch && ch <= 48) gp = 1; + else if (50 <= ch && ch <= 58) gp = 2; + else if (60 <= ch && ch <= 80) gp = 3; + else if (82 <= ch && ch <= 106) gp = 4; + else if (108 <= ch && ch <= 114) gp = 5; + else if (116 <= ch && ch <= 122) gp = 6; + else if (124 <= ch && ch <= 130) gp = 7; + else if (132 <= ch && ch <= 138) gp = 8; + else if (140 <= ch && ch <= 144) gp = 9; + else if (149 <= ch && ch <= 155) gp = 10; + else if (157 <= ch && ch <= 161) gp = 11; + else if (165 <= ch && ch <= 171) gp = 12; + else if (173 <= ch && ch <= 177) gp = 13; + if (gp < 0) return 0xFF; + if (group) *group = static_cast(gp); + return 1; /* 5G */ +} + +/* Upstream `core/rtw_rf.c:center_ch_5g_all[CENTER_CH_5G_ALL_NUM]` — the + * canonical 5G channel index used to populate `Index5G_BW40_Base`. Mirrors + * the upstream table verbatim. */ +static const uint8_t kCenterCh5gAll[65] = { + 15, 16, 17, 18, 20, 24, 28, 32, 36, 38, 40, 42, 44, 46, 48, + 52, 54, 56, 58, 60, 62, 64, 68, 72, 76, 80, 84, 88, 92, 96, + 100, 102, 104, 106, 108, 110, 112, 116, 118, 120, 122, 124, 126, 128, + 132, 134, 136, 138, 140, 142, 144, 149, 151, 153, 155, 157, 159, 161, + 165, 167, 169, 171, 173, 175, 177, +}; + +void EepromManager::LoadTxPowerInfo() { + /* EFUSE PG TX-power block layout — port of + * `hal_load_pg_txpwr_info_path_{2,5}g` from upstream + * `hal/hal_com_phycfg.c`. Per-path, 2.4G uses 18 bytes, 5G uses 24 + * bytes, both paths laid out contiguously starting at PG offset 0x10 + * (8812/8814 share this layout per `pg_txpwr_saddr=0x10`). + * + * Sequence per path: + * 2.4G: + * 6 bytes CCK base (one per channel group, MAX_CHNL_GROUP_24G=6) + * 5 bytes BW40 base (MAX_CHNL_GROUP_24G-1; last group folds in) + * 1 byte Ntx=1 BW20+OFDM diffs (MSB nibble=BW20, LSB=OFDM) + * 2 bytes Ntx=2 (BW40+BW20, then OFDM+CCK) + * 2 bytes Ntx=3 + * 2 bytes Ntx=4 + * 5G: + * 14 bytes BW40 base (MAX_CHNL_GROUP_5G) + * 1 byte Ntx=1 BW20+OFDM + * 2 bytes Ntx=2 (BW40+BW20, then OFDM) + * 2 bytes Ntx=3 + * 2 bytes Ntx=4 + * plus 3 bytes BW80 diffs (Ntx=1..3 in nibble pairs) + * + * Diff nibbles are signed 4-bit. Helpers `pg_msb_diff` / `pg_lsb_diff` + * sign-extend to int8_t. */ + constexpr uint16_t kPgSaddr = 0x10; + uint16_t off = kPgSaddr; + + /* Stage 1: per-path EFUSE byte stream → per-group base + per-Ntx diff. + * Iterates rfpath=0..numTotalRfPath-1 because that's what's compiled + * into the EFUSE for this chip variant. The kernel iterates MAX_RF_PATH + * unconditionally and skips per `HAL_SPEC_CHK_RF_PATH_*` — devourer + * already has `numTotalRfPath` set by `rtw_hal_config_rftype` post-EFUSE + * read. */ + /* Per-path-per-group base arrays (intermediate, before per-channel + * scattering). Layout: [path][group]. */ + uint8_t cck_base_2g[kMaxRfPath][6] = {}; + uint8_t bw40_base_2g[kMaxRfPath][6] = {}; + uint8_t bw40_base_5g[kMaxRfPath][14] = {}; + + for (int path = 0; path < numTotalRfPath && path < kMaxRfPath; path++) { + /* 2.4G section — 18 bytes per path. */ + for (int g = 0; g < 6; g++) + cck_base_2g[path][g] = efuse_eeprom_data[off++]; + for (int g = 0; g < 5; g++) + bw40_base_2g[path][g] = efuse_eeprom_data[off++]; + /* Ntx=1: 1 byte (MSB=BW20, LSB=OFDM) */ + { + uint8_t v = efuse_eeprom_data[off++]; + BW20_24G_Diff[path][0] = pg_msb_diff(v); + OFDM_24G_Diff[path][0] = pg_lsb_diff(v); + } + /* Ntx=2..4: 2 bytes each (BW40|BW20 then OFDM|CCK) */ + for (int t = 1; t < 4; t++) { + uint8_t v = efuse_eeprom_data[off++]; + BW40_24G_Diff[path][t] = pg_msb_diff(v); + BW20_24G_Diff[path][t] = pg_lsb_diff(v); + v = efuse_eeprom_data[off++]; + OFDM_24G_Diff[path][t] = pg_msb_diff(v); + CCK_24G_Diff[path][t] = pg_lsb_diff(v); + } + + /* 5G section — 24 bytes per path. */ + for (int g = 0; g < 14; g++) + bw40_base_5g[path][g] = efuse_eeprom_data[off++]; + /* Ntx=1: 1 byte (MSB=BW20, LSB=OFDM) */ + { + uint8_t v = efuse_eeprom_data[off++]; + BW20_5G_Diff[path][0] = pg_msb_diff(v); + OFDM_5G_Diff[path][0] = pg_lsb_diff(v); + } + /* Ntx=2..4: 2 bytes each (BW40|BW20, OFDM|-) */ + for (int t = 1; t < 4; t++) { + uint8_t v = efuse_eeprom_data[off++]; + BW40_5G_Diff[path][t] = pg_msb_diff(v); + BW20_5G_Diff[path][t] = pg_lsb_diff(v); + v = efuse_eeprom_data[off++]; + OFDM_5G_Diff[path][t] = pg_msb_diff(v); + /* LSB nibble of this byte is unused for 5G (no CCK on 5G). */ + } + /* 3 bytes BW80 diffs, Ntx=1..3 stored as nibble pairs: + * byte 0: MSB=Ntx2-BW80, LSB=Ntx1-BW80 + * byte 1: MSB=Ntx4-BW80, LSB=Ntx3-BW80 + * byte 2: reserved + * Upstream uses a different layout per IC; the 8812 path packs as + * above per `hal_load_pg_txpwr_info_path_5g`. */ + { + uint8_t v = efuse_eeprom_data[off++]; + BW80_5G_Diff[path][1] = pg_msb_diff(v); + BW80_5G_Diff[path][0] = pg_lsb_diff(v); + v = efuse_eeprom_data[off++]; + BW80_5G_Diff[path][3] = pg_msb_diff(v); + BW80_5G_Diff[path][2] = pg_lsb_diff(v); + /* third byte ignored */ + off++; + } + } + + /* Stage 2: per-group → per-channel. Mirrors upstream `hal_load_txpwr_info`. */ + for (int path = 0; path < numTotalRfPath && path < kMaxRfPath; path++) { + /* 2.4G: 14 channels mapped via classify_channel. */ + for (int ch_idx = 0; ch_idx < kCenterCh2gNum; ch_idx++) { + uint8_t group = 0, cck_group = 0; + if (classify_channel(static_cast(ch_idx + 1), &group, + &cck_group) != 0) + continue; + Index24G_CCK_Base[path][ch_idx] = cck_base_2g[path][cck_group]; + Index24G_BW40_Base[path][ch_idx] = bw40_base_2g[path][group]; + } + /* 5G: 65 channels from kCenterCh5gAll. */ + for (int ch_idx = 0; ch_idx < kCenterCh5gAllNum; ch_idx++) { + uint8_t group = 0; + if (classify_channel(kCenterCh5gAll[ch_idx], &group, nullptr) != 1) + continue; + Index5G_BW40_Base[path][ch_idx] = bw40_base_5g[path][group]; + } + } + + TxPowerInfoLoaded = true; + _logger->info("LoadTxPowerInfo: parsed {} paths, 2.4G ch6 CCK_Base[0]=0x{:02x} " + "BW40_Base[0]=0x{:02x}", + unsigned(numTotalRfPath), + unsigned(Index24G_CCK_Base[0][5]), + unsigned(Index24G_BW40_Base[0][5])); +} + +void EepromManager::LoadTxPowerByRate() { + /* Reset to zero — invalid rates / unmapped registers stay 0 and the + * lookup returns 0 (no offset). */ + std::memset(TxPwrByRateOffset, 0, sizeof(TxPwrByRateOffset)); + std::memset(TxPwrByRateBase, 0, sizeof(TxPwrByRateBase)); + + /* Stage 1: parse kHal8812aPhyRegPg into per-(band,path,rate) raw values. */ + constexpr size_t row_words = 6; + const size_t num_rows = sizeof(kHal8812aPhyRegPg) / sizeof(uint32_t) / row_words; + for (size_t r = 0; r < num_rows; r++) { + uint32_t band = kHal8812aPhyRegPg[r * row_words + 0]; + uint32_t rfpath = kHal8812aPhyRegPg[r * row_words + 1]; + /* row[2] = tx_num — not used for our flat per-rate index; the rate + * IDs already differentiate by Ntx. */ + uint32_t addr = kHal8812aPhyRegPg[r * row_words + 3]; + /* row[4] = bitmask, always 0xffffffff for 8812. */ + uint32_t data = kHal8812aPhyRegPg[r * row_words + 5]; + if (band >= 2 || rfpath >= kMaxRfPath) continue; + const uint16_t reg = static_cast(addr); + const RateRow *rr = nullptr; + for (const auto &m : kRateMap) { + if (m.reg == reg) { rr = &m; break; } + } + if (!rr) continue; + for (int i = 0; i < rr->count; i++) { + int8_t idx = rate_to_idx(rr->rates[i]); + if (idx < 0) continue; + uint8_t val = static_cast((data >> (i * 8)) & 0xff); + TxPwrByRateOffset[band][rfpath][idx] = static_cast(val); + } + } + + /* Stage 2: compute per-section base (value at rate_sec_base[rs]). */ + for (int band = 0; band < 2; band++) { + for (int path = 0; path < numTotalRfPath && path < kMaxRfPath; path++) { + for (int rs = 0; rs < kNumRateSection; rs++) { + int8_t base_idx = rate_to_idx(kSectionBaseRate[rs]); + if (base_idx < 0) continue; + TxPwrByRateBase[band][path][rs] = + static_cast(TxPwrByRateOffset[band][path][base_idx]); + } + } + } + + /* Stage 3: normalize — replace each rate's raw value with + * (raw - section_base), yielding a small signed offset. Mirrors + * upstream `phy_ConvertTxPowerByRateInDbmToRelativeValues`. */ + for (int band = 0; band < 2; band++) { + for (int path = 0; path < numTotalRfPath && path < kMaxRfPath; path++) { + for (int idx = 0; idx < kNumRateIdx; idx++) { + /* Skip rates whose section base is zero (rate not loaded). */ + /* Reconstruct the MGN_RATE from idx to find its section. */ + uint8_t rate; + if (idx <= 3) { + static const uint8_t cck[] = {0x02, 0x04, 0x0B, 0x16}; + rate = cck[idx]; + } else if (idx <= 11) { + static const uint8_t ofdm[] = {0x0C, 0x12, 0x18, 0x24, + 0x30, 0x48, 0x60, 0x6C}; + rate = ofdm[idx - 4]; + } else if (idx <= 43) { + rate = static_cast(0x80 + (idx - 12)); + } else { + rate = static_cast(0xA0 + (idx - 44)); + } + int8_t section = rate_to_section(rate); + if (section < 0) continue; + int raw = TxPwrByRateOffset[band][path][idx]; + int base = TxPwrByRateBase[band][path][section]; + TxPwrByRateOffset[band][path][idx] = + static_cast(raw - base); + } + } + } + + TxPwrByRateLoaded = true; + _logger->info("LoadTxPowerByRate: 2.4G path-A OFDM-6M offset={} HT-MCS7 base={}", + int(TxPwrByRateOffset[0][0][rate_to_idx(0x0C)]), + unsigned(TxPwrByRateBase[0][0][RS_HT_1SS])); +} + +void EepromManager::LoadTxPowerLimit() { + /* Init to a sentinel "63" = txgi_max meaning "no limit" — entries not + * touched by the parser end up unconstrained. */ + std::memset(TxPwrLimit2g, 63, sizeof(TxPwrLimit2g)); + std::memset(TxPwrLimit5g, 63, sizeof(TxPwrLimit5g)); + + /* Honour DEVOURER_REGULATION env override (FCC|ETSI|MKK|WW). */ + if (const char *e = std::getenv("DEVOURER_REGULATION")) { + if (devourer_strcaseeq(e, "ETSI")) CurrentRegulation = 1; + else if (devourer_strcaseeq(e, "MKK")) CurrentRegulation = 2; + else if (devourer_strcaseeq(e, "WW")) CurrentRegulation = 3; + else CurrentRegulation = 0; + } + + const size_t num_strings = + sizeof(kHal8812aTxpwrLmt) / sizeof(kHal8812aTxpwrLmt[0]); + for (size_t i = 0; i + 6 < num_strings; i += 7) { + const char *reg_s = kHal8812aTxpwrLmt[i + 0]; + const char *band_s = kHal8812aTxpwrLmt[i + 1]; + const char *bw_s = kHal8812aTxpwrLmt[i + 2]; + const char *rate_s = kHal8812aTxpwrLmt[i + 3]; + const char *ntx_s = kHal8812aTxpwrLmt[i + 4]; + const char *ch_s = kHal8812aTxpwrLmt[i + 5]; + const char *val_s = kHal8812aTxpwrLmt[i + 6]; + + uint8_t reg; + if (!strcmp(reg_s, "FCC")) reg = 0; + else if (!strcmp(reg_s, "ETSI")) reg = 1; + else if (!strcmp(reg_s, "MKK")) reg = 2; + else if (!strcmp(reg_s, "WW")) reg = 3; + else continue; + + uint8_t band; + if (!strcmp(band_s, "2.4G")) band = 0; + else if (!strcmp(band_s, "5G")) band = 1; + else continue; + + uint8_t bw; + if (!strcmp(bw_s, "20M")) bw = 0; + else if (!strcmp(bw_s, "40M")) bw = 1; + else if (!strcmp(bw_s, "80M")) bw = 2; + else if (!strcmp(bw_s, "160M")) bw = 3; + else continue; + + uint8_t rs; + if (!strcmp(rate_s, "CCK")) rs = 0; + else if (!strcmp(rate_s, "OFDM")) rs = 1; + else if (!strcmp(rate_s, "HT")) rs = 2; + else if (!strcmp(rate_s, "VHT")) rs = 3; + else continue; + + uint8_t ntx; + if (!strcmp(ntx_s, "1T")) ntx = 0; + else if (!strcmp(ntx_s, "2T")) ntx = 1; + else if (!strcmp(ntx_s, "3T")) ntx = 2; + else if (!strcmp(ntx_s, "4T")) ntx = 3; + else continue; + + uint8_t ch = parse_decimal(ch_s); + /* val_s may be signed (kept as-is via decimal parse, but no negatives + * appear in this table). */ + uint8_t val = parse_decimal(val_s); + + if (band == 0) { + if (bw >= kNum2gBw) continue; + int8_t ch_idx = lmt_ch_idx_2g(ch); + if (ch_idx < 0) continue; + TxPwrLimit2g[reg][bw][rs][ntx][ch_idx] = static_cast(val); + } else { + if (bw >= kNum5gBw) continue; + int8_t ch_idx = -1; + for (int j = 0; j < kCenterCh5gAllNumLmt; j++) + if (kCenterCh5gAll[j] == ch) { ch_idx = static_cast(j); break; } + if (ch_idx < 0) continue; + TxPwrLimit5g[reg][bw][rs][ntx][ch_idx] = static_cast(val); + } + } + + TxPwrLimitLoaded = true; + static const char *kRegName[] = {"FCC", "ETSI", "MKK", "WW"}; + _logger->info("LoadTxPowerLimit: active regulation={} (FCC OFDM 1T ch6 = {})", + kRegName[CurrentRegulation], + int(TxPwrLimit2g[0][0][1][0][5])); +} + +uint8_t EepromManager::GetTxPowerIndexBase(uint8_t path, uint8_t rate, + uint8_t ntx_idx, uint8_t bandwidth, + uint8_t channel) const { + if (!TxPowerInfoLoaded || path >= kMaxRfPath) + return 0; + + /* Port of `PHY_GetTxPowerIndexBase` from `hal_com_phycfg.c`. Rates are + * the MGN_* values from upstream `phydm_types.h`; we mirror the macro + * conditions inline. Bandwidth enum matches devourer's ChannelWidth_t + * (20=0, 40=1, 80=2, 160=3). */ + /* MGN_* rate group classifiers — keep in sync with upstream. */ + auto is_cck = [](uint8_t r) { + /* MGN_1M=0x02, MGN_2M=0x04, MGN_5_5M=0x0B, MGN_11M=0x16 */ + return r == 0x02 || r == 0x04 || r == 0x0B || r == 0x16; + }; + auto is_ofdm = [](uint8_t r) { + /* MGN_6M..MGN_54M: 0x0C, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6C */ + return r == 0x0C || r == 0x12 || r == 0x18 || r == 0x24 || + r == 0x30 || r == 0x48 || r == 0x60 || r == 0x6C; + }; + /* MGN_MCS0=0x80, MCS31=0x9F (HT); MGN_VHT1SS_MCS0=0xA0..VHT4SS_MCS9=0xC9 */ + auto is_mcs0_7 = [](uint8_t r) { return r >= 0x80 && r <= 0x87; }; + auto is_mcs8_15 = [](uint8_t r) { return r >= 0x88 && r <= 0x8F; }; + auto is_mcs16_23 = [](uint8_t r) { return r >= 0x90 && r <= 0x97; }; + auto is_mcs24_31 = [](uint8_t r) { return r >= 0x98 && r <= 0x9F; }; + auto is_vht1ss = [](uint8_t r) { return r >= 0xA0 && r <= 0xA9; }; + auto is_vht2ss = [](uint8_t r) { return r >= 0xAA && r <= 0xB3; }; + auto is_vht3ss = [](uint8_t r) { return r >= 0xB4 && r <= 0xBD; }; + auto is_vht4ss = [](uint8_t r) { return r >= 0xBE && r <= 0xC7; }; + + uint8_t group = 0, cck_group = 0; + uint8_t band = classify_channel(channel, &group, &cck_group); + if (band == 0xFF) + return 0; + /* Channel index into the per-band base array. For 2.4G it's + * `channel - 1` (channels 1-14). For 5G it's the index in + * kCenterCh5gAll. */ + uint8_t ch_idx = 0; + if (band == 0) { + if (channel == 0 || channel > kCenterCh2gNum) + return 0; + ch_idx = static_cast(channel - 1); + } else { + bool found = false; + for (uint8_t i = 0; i < kCenterCh5gAllNum; i++) { + if (kCenterCh5gAll[i] == channel) { + ch_idx = i; + found = true; + break; + } + } + if (!found) + return 0; + } + + int txPower = 0; + + if (band == 0) { + /* 2.4G */ + if (is_cck(rate)) { + txPower = Index24G_CCK_Base[path][ch_idx]; + txPower += CCK_24G_Diff[path][0]; + if (ntx_idx >= 1) txPower += CCK_24G_Diff[path][1]; + if (ntx_idx >= 2) txPower += CCK_24G_Diff[path][2]; + if (ntx_idx >= 3) txPower += CCK_24G_Diff[path][3]; + goto clamp_and_return; + } + txPower = Index24G_BW40_Base[path][ch_idx]; + if (is_ofdm(rate)) { + txPower += OFDM_24G_Diff[path][0]; + if (ntx_idx >= 1) txPower += OFDM_24G_Diff[path][1]; + if (ntx_idx >= 2) txPower += OFDM_24G_Diff[path][2]; + if (ntx_idx >= 3) txPower += OFDM_24G_Diff[path][3]; + goto clamp_and_return; + } + /* MCS / VHT — pick BW20 / BW40 (BW80 falls through to BW40 per upstream + * comment "Willis suggest adopt BW 40M power index while in BW 80 mode"). */ + if (bandwidth == 0) { /* BW20 */ + if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || + is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW20_24G_Diff[path][0]; + if (is_mcs8_15 (rate) || (ntx_idx >= 1 && (is_vht2ss(rate) || is_vht3ss(rate) || is_vht4ss(rate)))) + txPower += BW20_24G_Diff[path][1]; + if (is_mcs16_23(rate) || (ntx_idx >= 2 && (is_vht3ss(rate) || is_vht4ss(rate)))) + txPower += BW20_24G_Diff[path][2]; + if (is_mcs24_31(rate) || (ntx_idx >= 3 && is_vht4ss(rate))) + txPower += BW20_24G_Diff[path][3]; + } else { /* BW40 or BW80 */ + if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || + is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW40_24G_Diff[path][0]; + if (is_mcs8_15 (rate) || (ntx_idx >= 1 && (is_vht2ss(rate) || is_vht3ss(rate) || is_vht4ss(rate)))) + txPower += BW40_24G_Diff[path][1]; + if (is_mcs16_23(rate) || (ntx_idx >= 2 && (is_vht3ss(rate) || is_vht4ss(rate)))) + txPower += BW40_24G_Diff[path][2]; + if (is_mcs24_31(rate) || (ntx_idx >= 3 && is_vht4ss(rate))) + txPower += BW40_24G_Diff[path][3]; + } + } else { + /* 5G — no CCK */ + if (rate < 0x0C) + return 0; + txPower = Index5G_BW40_Base[path][ch_idx]; + if (is_ofdm(rate)) { + txPower += OFDM_5G_Diff[path][0]; + if (ntx_idx >= 1) txPower += OFDM_5G_Diff[path][1]; + if (ntx_idx >= 2) txPower += OFDM_5G_Diff[path][2]; + if (ntx_idx >= 3) txPower += OFDM_5G_Diff[path][3]; + goto clamp_and_return; + } + /* MCS / VHT BW20 / BW40 / BW80. */ + if (bandwidth == 0) { + if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || + is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW20_5G_Diff[path][0]; + if (is_mcs8_15 (rate)) txPower += BW20_5G_Diff[path][1]; + if (is_mcs16_23(rate)) txPower += BW20_5G_Diff[path][2]; + if (is_mcs24_31(rate)) txPower += BW20_5G_Diff[path][3]; + } else if (bandwidth == 1) { + if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || + is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW40_5G_Diff[path][0]; + if (is_mcs8_15 (rate)) txPower += BW40_5G_Diff[path][1]; + if (is_mcs16_23(rate)) txPower += BW40_5G_Diff[path][2]; + if (is_mcs24_31(rate)) txPower += BW40_5G_Diff[path][3]; + } else { /* BW80 */ + if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || + is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW80_5G_Diff[path][0]; + if (is_mcs8_15 (rate)) txPower += BW80_5G_Diff[path][1]; + if (is_mcs16_23(rate)) txPower += BW80_5G_Diff[path][2]; + if (is_mcs24_31(rate)) txPower += BW80_5G_Diff[path][3]; + } + } + +clamp_and_return: + /* Layer 2: per-rate offset (port of upstream PHY_GetTxPowerByRate). + * VHT rates at 2.4G are not capped by the txpwr_lmt table (no entries — + * VHT is 5G-only per Wi-Fi spec). Kernel writes base+boost for those, + * so force by_rate=0 there to match. */ + int by_rate_diff = 0; + const int8_t section_classifier = rate_to_section(rate); + const bool is_vht_24g = + (band == 0) && section_classifier >= RS_VHT_1SS; + if (TxPwrByRateLoaded && !is_vht_24g) { + int8_t rate_idx = rate_to_idx(rate); + if (rate_idx >= 0) + by_rate_diff = TxPwrByRateOffset[band][path][rate_idx]; + } + + /* Layer 3: regulatory limit (port of upstream PHY_GetTxPowerLimit). */ + int limit = 63; + if (TxPwrLimitLoaded) { + int8_t section = rate_to_section(rate); + int rs_lmt; + if (section == RS_CCK) rs_lmt = 0; + else if (section == RS_OFDM) rs_lmt = 1; + else if (section >= RS_HT_1SS && section <= RS_HT_4SS) rs_lmt = 2; + else if (section >= RS_VHT_1SS) rs_lmt = 3; + else rs_lmt = -1; + if (rs_lmt >= 0) { + if (band == 0) { + if (bandwidth < kNum2gBw && ntx_idx < kMaxTxCount) { + int8_t v = TxPwrLimit2g[CurrentRegulation][bandwidth][rs_lmt] + [ntx_idx][ch_idx]; + if (v < 63 && v > -63) limit = v; + } + } else { + if (bandwidth < kNum5gBw && ntx_idx < kMaxTxCount) { + int8_t v = TxPwrLimit5g[CurrentRegulation][bandwidth][rs_lmt] + [ntx_idx][ch_idx]; + if (v < 63 && v > -63) limit = v; + } + } + } + } + + /* Cap `by_rate_diff` such that the final power doesn't exceed the + * regulatory `limit`. Upstream writes: + * by_rate_diff = (by_rate_diff > limit) ? limit : by_rate_diff; + * power = base + by_rate_diff + boost; + * But empirically that lets `base + by_rate_diff + boost` exceed `limit` + * when by_rate is small enough vs limit but base is high (the kernel + * 0xc24 OFDM-6M canary = 0x31 = 49 contradicts the literal formula — + * by_rate=20 limit=36 base=47 would predict 47+20+2=69 clamped to 63, + * but kernel writes 49 = base+boost as if by_rate was forced to 0). + * The effective rule is: by_rate caps at (limit - base - boost), with a + * floor of 0. */ + constexpr int boost = 2; /* upstream `transmit_power_boost` default */ + int headroom = limit - txPower - boost; + if (headroom < 0) headroom = 0; + if (by_rate_diff > headroom) by_rate_diff = headroom; + txPower += by_rate_diff + boost; + + if (txPower < 0) txPower = 0; + if (txPower > 63) txPower = 63; /* txgi_max for 8812/8821/8814 */ + return static_cast(txPower); +} + +bool EepromManager::GetMacAddress(uint8_t out[6]) const { + /* EFUSE MAC-address offsets per upstream `include/hal_pg.h`: + * EEPROM_MAC_ADDR_8812AU = 0xD7 + * EEPROM_MAC_ADDR_8814AU = 0xD8 + * EEPROM_MAC_ADDR_8821AU = 0x107 */ + uint16_t off; + switch (version_id.ICType) { + case CHIP_8814A: off = 0xD8; break; + case CHIP_8821: off = 0x107; break; + default: off = 0xD7; break; /* 8812AU / 8811AU (8812 silicon) */ + } + for (int i = 0; i < 6; ++i) + out[i] = efuse_eeprom_data[off + i]; + /* All-0xFF means EFUSE empty / unburnt; all-0x00 means we haven't read + * EFUSE yet. Both are invalid. */ + bool all_ff = true, all_zero = true; + for (int i = 0; i < 6; ++i) { + if (out[i] != 0xFF) all_ff = false; + if (out[i] != 0x00) all_zero = false; + } + return !all_ff && !all_zero; +} + JaguarPhyContext EepromManager::GetPhyContext() const { /* ODM_ITRF_USB and ODM_CE are phydm enum values (we don't pull the phydm * subsystem in, so hardcode the canonical numbers from upstream diff --git a/src/EepromManager.h b/src/EepromManager.h index deea597..f4f1ec6 100644 --- a/src/EepromManager.h +++ b/src/EepromManager.h @@ -44,6 +44,33 @@ class EepromManager { * populated from EFUSE values already read into this manager. */ JaguarPhyContext GetPhyContext() const; + /* Read the chip's MAC address from the EFUSE shadow. Returns true if a + * non-empty (not all-0xFF / not all-0x00) MAC was found. EFUSE offsets per + * upstream `hal_pg.h`: 0xD7 for 8812AU, 0xD8 for 8814AU, 0x107 for 8821AU. + * Caller is expected to fall back to a hardcoded MAC if this returns + * false (the T1 canary diff against `aircrack-ng/88XXau` showed + * `REG_MACID @ 0x610` was unprogrammed on 8812AU because devourer never + * wrote it — many Realtek MAC-TX paths refuse to schedule frames if the + * MAC ID is zero). */ + bool GetMacAddress(uint8_t out[6]) const; + + /* Parse the EFUSE TX-power PG block at offset 0x10 into the per-channel + * per-path Index{24G,5G}_*_Base and *_Diff tables. Port of upstream + * `hal_load_pg_txpwr_info` + `hal_load_pg_txpwr_info_path_{2,5}g` from + * `hal_com_phycfg.c`. Called once during init after the EFUSE shadow is + * populated. Once loaded, `RadioManagementModule::PHY_GetTxPowerIndexBase` + * computes the per-rate per-channel power index instead of using the + * uniform `SetTxPower(N)` shortcut (T1 root cause for the 0xc20..0xc40 + * TX-AGC divergence + the 0xc54 TxPwrTraing divergence at ch6). */ + void LoadTxPowerInfo(); + + /* Returns the chip's TX-power index for (path, rate, ntx, bw, channel), + * mirroring upstream `PHY_GetTxPowerIndexBase` (sans the by-rate / + * regulatory-limit / tracking-offset overlay — those layers come later). + * Returns 0 if `LoadTxPowerInfo()` hasn't run yet. */ + uint8_t GetTxPowerIndexBase(uint8_t path, uint8_t rate, uint8_t ntx_idx, + uint8_t bandwidth, uint8_t channel) const; + HAL_VERSION version_id; odm_cut_version_e cut_version; uint8_t crystal_cap; @@ -58,6 +85,71 @@ class EepromManager { HAL_RF_TYPE_E rf_type; uint16_t rfe_type; + /* Per-channel per-path TX-power tables parsed from the EFUSE PG block. + * Mirrors `HAL_DATA_TYPE::Index{24G,5G}_*_Base` and `*_Diff` arrays from + * upstream `aircrack-ng/rtl8812au/include/hal_data.h`. Populated by + * `LoadTxPowerInfo()` post-EFUSE-read; consumed by + * `RadioManagementModule::PHY_GetTxPowerIndexBase8812` to compute the + * per-rate per-channel TX power index instead of using the historical + * uniform `SetTxPower(N)` shortcut. + * + * Dimensions match upstream: MAX_RF_PATH = 4, MAX_TX_COUNT = 4, + * CENTER_CH_2G_NUM = 14, CENTER_CH_5G_ALL_NUM = 65. */ + static constexpr int kMaxRfPath = 4; + static constexpr int kMaxTxCount = 4; + static constexpr int kCenterCh2gNum = 14; + static constexpr int kCenterCh5gAllNum = 65; + uint8_t Index24G_CCK_Base[kMaxRfPath][kCenterCh2gNum]{}; + uint8_t Index24G_BW40_Base[kMaxRfPath][kCenterCh2gNum]{}; + uint8_t Index5G_BW40_Base[kMaxRfPath][kCenterCh5gAllNum]{}; + int8_t CCK_24G_Diff[kMaxRfPath][kMaxTxCount]{}; + int8_t OFDM_24G_Diff[kMaxRfPath][kMaxTxCount]{}; + int8_t BW20_24G_Diff[kMaxRfPath][kMaxTxCount]{}; + int8_t BW40_24G_Diff[kMaxRfPath][kMaxTxCount]{}; + int8_t OFDM_5G_Diff[kMaxRfPath][kMaxTxCount]{}; + int8_t BW20_5G_Diff[kMaxRfPath][kMaxTxCount]{}; + int8_t BW40_5G_Diff[kMaxRfPath][kMaxTxCount]{}; + int8_t BW80_5G_Diff[kMaxRfPath][kMaxTxCount]{}; + bool TxPowerInfoLoaded = false; + + /* TxPwrByRateOffset: per-rate fine-tune offset applied on top of the + * per-channel base. Loaded from `kHal8812aPhyRegPg` (verbatim copy of + * upstream `array_mp_8812a_phy_reg_pg`). After `NormalizeTxPowerByRate` + * runs, each entry holds (raw_value - section_base) — a small signed + * offset (~-12..+20). Indexed by RateToIndex(MGN_*). */ + static constexpr int kNumRateIdx = 84; + static constexpr int kNumRateSection = 10; + int8_t TxPwrByRateOffset[2][kMaxRfPath][kNumRateIdx]{}; + uint8_t TxPwrByRateBase[2][kMaxRfPath][kNumRateSection]{}; + bool TxPwrByRateLoaded = false; + + /* TX power regulatory limit table from `kHal8812aTxpwrLmt`. After load, + * each entry holds the regulation-imposed max power-index for a given + * (regulation, band, bw, rate_section, ntx, channel). devourer's + * `GetTxPowerIndexBase` caps `by_rate_offset` against this when + * computing the final per-rate per-channel power. */ + static constexpr int kNumRegulations = 4; /* FCC, ETSI, MKK, WW */ + static constexpr int kNum2gBw = 2; /* 20M, 40M */ + static constexpr int kNum5gBw = 4; /* 20M, 40M, 80M, 160M */ + static constexpr int kNumRateSectionLmt = 4; /* CCK, OFDM, HT, VHT */ + static constexpr int kCenterCh2gNumLmt = 14; + static constexpr int kCenterCh5gAllNumLmt = 65; + int8_t TxPwrLimit2g[kNumRegulations][kNum2gBw][kNumRateSectionLmt] + [kMaxTxCount][kCenterCh2gNumLmt]{}; + int8_t TxPwrLimit5g[kNumRegulations][kNum5gBw][kNumRateSectionLmt] + [kMaxTxCount][kCenterCh5gAllNumLmt]{}; + bool TxPwrLimitLoaded = false; + /* Active regulation domain — defaults to FCC (least restrictive on the + * canonical 8812AU). Can be overridden via DEVOURER_REGULATION env. */ + uint8_t CurrentRegulation = 0; /* 0=FCC, 1=ETSI, 2=MKK, 3=WW */ + + /* Load + normalize per-rate offsets from the upstream phy_reg_pg table. + * Called after LoadTxPowerInfo. */ + void LoadTxPowerByRate(); + + /* Parse upstream txpwr_lmt array into the 5D limit lookup. */ + void LoadTxPowerLimit(); + private: void read_chip_version_8812a(RtlUsbAdapter device); void dump_chip_info(HAL_VERSION ChipVersion); diff --git a/src/HalModule.cpp b/src/HalModule.cpp index 4bd5d31..7c6a57c 100644 --- a/src/HalModule.cpp +++ b/src/HalModule.cpp @@ -428,17 +428,35 @@ bool HalModule::rtl8812au_hal_init() { "REG_RXFLTMAP2=0xFFFF", cr_observed, cr_final); - if (is_8814a) { - /* Program MAC address to REG_MACID (0x0610). usbmon-trace diff vs - * kernel-driver shows kernel writes 6 individual bytes at 0x610..0x615 - * during init; devourer never writes REG_MACID at all. Many Realtek - * MAC TX paths refuse to schedule a frame if the chip's MAC address - * isn't programmed. Using a hardcoded locally-administered address - * for now — proper EFUSE-read of the per-chip MAC is a follow-up. */ - static const uint8_t kHardcodedMac[6] = {0x02, 0x0d, 0xb0, 0xc7, 0xe4, 0xb3}; - for (uint16_t i = 0; i < 6; ++i) { - _device.rtw_write8(0x0610 + i, kHardcodedMac[i]); - } + /* Program MAC address to REG_MACID (0x0610). usbmon-trace diff vs + * kernel-driver shows kernel writes 6 individual bytes at 0x610..0x615 + * during init; devourer never wrote REG_MACID for 8812AU at all and used + * a hardcoded locally-administered address for 8814AU. Many Realtek MAC + * TX paths refuse to schedule a frame if the MAC ID is zero — caught by + * the T1 canary diff (TODO.md) on 8812AU at ch6: kernel side + * `MAC 0x610 = 0x02FFC954`, devourer side `MAC 0x610 = 0x00000000`. + * + * Read the MAC from EFUSE per chip type (EepromManager handles the + * 8812/8814/8821 offset split). Fall back to the historical hardcoded + * locally-administered 8814AU address for cards whose EFUSE is empty, + * matching prior behaviour. */ + uint8_t mac[6]; + if (_eepromManager->GetMacAddress(mac)) { + for (uint16_t i = 0; i < 6; ++i) + _device.rtw_write8(0x0610 + i, mac[i]); + _logger->info("REG_MACID programmed from EFUSE: " + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + unsigned(mac[0]), unsigned(mac[1]), unsigned(mac[2]), + unsigned(mac[3]), unsigned(mac[4]), unsigned(mac[5])); + } else if (is_8814a) { + static const uint8_t kHardcoded8814Mac[6] = {0x02, 0x0d, 0xb0, + 0xc7, 0xe4, 0xb3}; + for (uint16_t i = 0; i < 6; ++i) + _device.rtw_write8(0x0610 + i, kHardcoded8814Mac[i]); + _logger->info("REG_MACID: EFUSE empty, using 8814AU hardcoded fallback"); + } else { + _logger->info("REG_MACID: EFUSE empty and no fallback for this chip — " + "leaving zero (MAC-TX may drop frames)"); } if (is_8814a) { @@ -1552,10 +1570,14 @@ void HalModule::_InitInterrupt_8812AU() { } void HalModule::_InitNetworkType_8812A() { + /* devourer is monitor-only; the kernel rtw driver sets MSR (REG_CR + * bits [17:16] for port 0) to NT_NO_LINK in this case. The earlier + * NT_LINK_AP value here was a leftover that the T1 canary diff + * caught — `MAC 0x102 = 0x02` on devourer vs `0x00` on kernel + * (TODO.md, kernel side via `iwpriv read 4,0x100`). Setting + * NT_NO_LINK matches kernel's monitor-mode state. */ auto value32 = _device.rtw_read32(REG_CR); - /* TODO: use the other function to set network type */ - value32 = (value32 & ~MASK_NETTYPE) | _NETTYPE(NT_LINK_AP); - + value32 = (value32 & ~MASK_NETTYPE) | _NETTYPE(NT_NO_LINK); _device.rtw_write32(REG_CR, value32); } diff --git a/src/RadioManagementModule.cpp b/src/RadioManagementModule.cpp index d6fd202..e986fdc 100644 --- a/src/RadioManagementModule.cpp +++ b/src/RadioManagementModule.cpp @@ -255,6 +255,37 @@ void RadioManagementModule::phy_SwChnlAndSetBwMode8812() { PHY_SetTxPowerLevel8812(_currentChannel); } + /* T1 cross-validation oracle (TODO.md): when DEVOURER_DUMP_CANARY=1 + * is set, dump the canary BB/MAC/RF registers after channel-set is + * complete. Output format matches `iwpriv read 4,` / + * `iwpriv rfr ` so kernel and devourer dumps + * can be diffed line-by-line. */ + if (std::getenv("DEVOURER_DUMP_CANARY")) { + static const uint16_t bb_canary[] = { + 0x808, 0x80c, 0x82c, 0x830, 0x834, 0x838, 0x84c, 0x860, 0x8ac, + 0x8b0, 0x8c4, 0xc00, 0xc1c, 0xc20, 0xc24, 0xc28, 0xc2c, 0xc30, + 0xc34, 0xc38, 0xc3c, 0xc40, 0xc50, 0xc54, 0xc60, 0xc64, 0xc68, + 0xc6c, 0xc70, 0xc90, 0xe1c, 0xe50, 0xe54}; + static const uint16_t mac_canary[] = {0x40, 0xcf, 0xf0, 0x100, + 0x102, 0x420, 0x4c8, 0x508, + 0x522, 0x550, 0x560, 0x610, + 0x614}; + static const uint32_t rf_canary[] = {0x00, 0x05, 0x18, 0x42, 0x65, 0x8f}; + _logger->info("=== DEVOURER_DUMP_CANARY (post channel-set ch={}) ===", + unsigned(_currentChannel)); + for (uint16_t a : bb_canary) + _logger->info("BB 0x{:03x} = 0x{:08X}", a, _device.rtw_read32(a)); + for (uint16_t a : mac_canary) + _logger->info("MAC 0x{:03x} = 0x{:08X}", a, _device.rtw_read32(a)); + for (uint32_t a : rf_canary) + _logger->info("RF[A] 0x{:02x} = 0x{:05X}", a, + phy_query_rf_reg(RfPath::RF_PATH_A, a, 0xfffffu)); + for (uint32_t a : rf_canary) + _logger->info("RF[B] 0x{:02x} = 0x{:05X}", a, + phy_query_rf_reg(RfPath::RF_PATH_B, a, 0xfffffu)); + _logger->info("=== END DEVOURER_DUMP_CANARY ==="); + } + _needIQK = false; } @@ -1283,9 +1314,38 @@ static uint8_t phy_get_tx_power_index() { return 16; } void RadioManagementModule::PHY_SetTxPowerIndexByRateArray( RfPath rfPath, const std::vector &rates) { + /* T1 fix: per-rate TX power from EFUSE-derived tables (port of + * upstream `PHY_GetTxPowerIndexBase`). Before this, every rate got + * the same `power` (set once via SetTxPower) which produced the + * uniform 0x28 across the 0xc20..0xc40 TX-AGC cluster and diverged + * from kernel's per-rate per-channel values (0x2D..0x31 at ch6). + * + * Fallback when EFUSE tables not loaded (autoload failed for this + * chip, e.g. 8814 pre-LateInit): the legacy `power` shortcut. Once + * EFUSE is loaded all rates compute against base + per-Ntx diff. */ + /* ntx_idx is the rate's stream count - 1, NOT the chip's RfPath count. + * Mirrors upstream `phy_get_current_tx_num`: OFDM/MCS0-7/VHT1SS → 0, + * MCS8-15/VHT2SS → 1, MCS16-23/VHT3SS → 2, MCS24-31/VHT4SS → 3. */ + auto rate_ntx = [](uint8_t r) -> uint8_t { + if (r >= 0x88 && r <= 0x8F) return 1; /* MCS8-15 */ + if (r >= 0x90 && r <= 0x97) return 2; /* MCS16-23 */ + if (r >= 0x98 && r <= 0x9F) return 3; /* MCS24-31 */ + if (r >= 0xAA && r <= 0xB3) return 1; /* VHT2SS */ + if (r >= 0xB4 && r <= 0xBD) return 2; /* VHT3SS */ + if (r >= 0xBE && r <= 0xC7) return 3; /* VHT4SS */ + return 0; + }; + const uint8_t bw = static_cast(_currentChannelBw); for (int i = 0; i < rates.size(); ++i) { - auto powerIndex = power; MGN_RATE rate = rates[i]; + uint32_t powerIndex; + if (_eepromManager->TxPowerInfoLoaded) { + powerIndex = _eepromManager->GetTxPowerIndexBase( + static_cast(rfPath), static_cast(rate), + rate_ntx(static_cast(rate)), bw, _currentChannel); + } else { + powerIndex = power; + } PHY_SetTxPowerIndex_8812A(powerIndex, rfPath, rate); } } @@ -1330,8 +1390,15 @@ void RadioManagementModule::PHY_SetTxPowerIndex_8812A(uint32_t powerIndex, if (static_cast(rfPath) >= RF_PATH_C) { return; } - if (powerIndex % 2 == 1) + /* Upstream `rtl8812a_phycfg.c:629` — workaround for the 8812A/8821A + * TEST CHIPS only, which had a bug accepting odd Tx-power indexes. + * Normal-production silicon doesn't need it. Devourer was applying + * the decrement unconditionally and introducing a systematic -1 + * offset in the per-rate TX-power canary diff vs kernel. */ + if (!IS_NORMAL_CHIP(_eepromManager->version_id) && + powerIndex % 2 == 1) { powerIndex -= 1; + } if (rfPath == RF_PATH_A) { switch (rate) { case MGN_1M: @@ -1781,8 +1848,19 @@ void RadioManagementModule::PHY_TxPowerTrainingByPath_8812(RfPath rfPath) { } uint16_t writeOffset; - uint32_t powerLevel; - powerLevel = power; + /* Upstream `PHY_TxPowerTrainingByPath_8812` uses + * `phy_get_tx_power_index(adapter, path, MGN_MCS7, bw, channel)` as the + * starting PowerLevel — i.e. the per-channel per-Ntx TX-power index for + * HT MCS7 (1-stream). devourer used to read the uniform `power` class + * member instead, which produced 0xc54 = 0x10161E vs kernel's 0x171D25 + * at ch6 (the T1 canary diff's last outstanding divergence in the + * TX-power cluster). MGN_MCS7 = 0x87, ntx_idx = 0 (1-stream rate). */ + uint32_t powerLevel = _eepromManager->TxPowerInfoLoaded + ? _eepromManager->GetTxPowerIndexBase( + static_cast(rfPath), /*rate MGN_MCS7=*/0x87, + /*ntx_idx=*/0, + static_cast(_currentChannelBw), _currentChannel) + : power; if (rfPath == RfPath::RF_PATH_A) { writeOffset = rA_TxPwrTraing_Jaguar; diff --git a/tests/aircrack-ng-88XXau-siocdevprivate.patch b/tests/aircrack-ng-88XXau-siocdevprivate.patch new file mode 100644 index 0000000..bb91994 --- /dev/null +++ b/tests/aircrack-ng-88XXau-siocdevprivate.patch @@ -0,0 +1,118 @@ +# Patch for aircrack-ng/8812au v5.6.4.2_35491.20191025 to route SIOCDEVPRIVATE +# via .ndo_siocdevprivate on kernel 5.15+. +# +# Background +# ---------- +# Linux 5.6 added struct net_device_ops::ndo_siocdevprivate, and 5.13 switched +# SIOCDEVPRIVATE (0x89F0..0x89FF) dispatch over to it. Drivers that still only +# register .ndo_do_ioctl no longer receive SIOCDEVPRIVATE ioctls on modern +# kernels. +# +# aircrack-ng/88XXau (vintage 2019) wires the entire MP / EFUSE / debug +# command surface through SIOCDEVPRIVATE → .ndo_do_ioctl → rtw_ioctl_wext_private +# (the name-dispatch table at os_dep/linux/ioctl_linux.c:_rtw_ioctl_wext_private). +# On kernel 5.15 these all return -EOPNOTSUPP from rtwpriv v5.9.1 (the +# canonical Realtek MP-mode tool), even though MP/EFUSE handlers are compiled +# in (CONFIG_MP_INCLUDED=y). +# +# iwpriv keeps working because it uses SIOCIWFIRSTPRIV+N (different ioctl +# numbers, still routed via .ndo_do_ioctl). +# +# This patch adds a thin .ndo_siocdevprivate shim that rebuilds the iwreq +# userspace-pointer field (now passed separately by the new ABI) and dispatches +# into the existing rtw_ioctl() path. Unlocks rtwpriv v5.9.1 for: +# - efuse_get realmap / realraw / rmap,XX,N (T3 EFUSE state-machine work) +# - mp_start, mp_channel, mp_bandwidth, mp_rate, mp_txpower, mp_ctx, mp_query +# (T4 MP-mode subcommand work) +# - rfr / rfw via SIOCDEVPRIVATE (alternative to the iwpriv path the canary +# diff oracle already uses) +# +# Apply +# ----- +# cd /usr/src/8812au-5.6.4.2_35491.20191025 +# patch -p1 < /path/to/this/patch +# dkms remove -m realtek-rtl88xxau -v 5.6.4.2~20230501 --all +# dkms add /usr/src/8812au-5.6.4.2_35491.20191025 +# dkms build -m realtek-rtl88xxau -v 5.6.4.2~20230501 +# dkms install -m realtek-rtl88xxau -v 5.6.4.2~20230501 --force +# rmmod 88XXau && modprobe 88XXau +# +# Verify +# ------ +# iw dev wlxXXXX set type monitor && ip link set wlxXXXX up +# iw dev wlxXXXX set channel 6 +# rtwpriv wlxXXXX mp_start # → "mp_start ok" +# rtwpriv wlxXXXX efuse_get realmap # → 16-byte rows of EFUSE +# +# Tested against: Ubuntu 22.04 / 5.15.0-179-generic / aircrack-ng/8812au +# v5.6.4.2_35491.20191025 + rtwpriv_x86_64 from rtwpriv_release_v5.9.1.20241014. + +--- a/include/osdep_intf.h ++++ b/include/osdep_intf.h +@@ -52,6 +52,11 @@ struct intf_priv { + #ifdef PLATFORM_LINUX + int rtw_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); + ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) ++int rtw_siocdevprivate(struct net_device *dev, struct ifreq *rq, ++ void __user *data, int cmd); ++#endif ++ + int rtw_init_netdev_name(struct net_device *pnetdev, const char *ifname); + struct net_device *rtw_init_netdev(_adapter *padapter); + u16 rtw_recv_select_queue(struct sk_buff *skb); +--- a/os_dep/linux/ioctl_linux.c ++++ b/os_dep/linux/ioctl_linux.c +@@ -12821,3 +12821,30 @@ int rtw_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) + + return ret; + } ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) ++/* Kernel 5.15+ deprecated SIOCDEVPRIVATE routing through .ndo_do_ioctl — ++ * those ioctls now flow through .ndo_siocdevprivate with a separate ++ * userspace pointer arg. Wrap rtw_ioctl so the existing dispatch logic ++ * still receives SIOCDEVPRIVATE / SIOCDEVPRIVATE+1 etc. ++ * ++ * Without this shim, rtwpriv v5.9.1 (which talks via SIOCDEVPRIVATE) fails ++ * with "Operation not supported" on every command, even though MP/EFUSE ++ * handlers are compiled in. iwpriv keeps working because it uses ++ * SIOCIWFIRSTPRIV+N which still routes via .ndo_do_ioctl. ++ */ ++int rtw_siocdevprivate(struct net_device *dev, struct ifreq *rq, ++ void __user *data, int cmd) ++{ ++ struct iwreq *wrq = (struct iwreq *)rq; ++ ++ /* The legacy rtw_ioctl reads the userspace buffer via copy_from_user ++ * on `wrq->u.data.pointer`. The new ABI gives us that pointer ++ * separately in `data`; rebuild the iwreq so the legacy path sees ++ * what it expects. */ ++ if (cmd == SIOCDEVPRIVATE || cmd == SIOCDEVPRIVATE + 1) ++ wrq->u.data.pointer = data; ++ ++ return rtw_ioctl(dev, rq, cmd); ++} ++#endif +--- a/os_dep/linux/os_intfs.c ++++ b/os_dep/linux/os_intfs.c +@@ -1535,6 +1535,9 @@ static const struct net_device_ops rtw_netdev_ops = { + .ndo_get_stats = rtw_net_get_stats, + #ifdef CONFIG_WIRELESS_EXT + .ndo_do_ioctl = rtw_ioctl, ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) ++ .ndo_siocdevprivate = rtw_siocdevprivate, ++#endif + #endif + }; + #endif +@@ -2823,6 +2826,9 @@ static const struct net_device_ops rtw_netdev_ops = { + .ndo_get_stats = rtw_net_get_stats, + #ifdef CONFIG_WIRELESS_EXT + .ndo_do_ioctl = rtw_ioctl, ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) ++ .ndo_siocdevprivate = rtw_siocdevprivate, ++#endif + #endif + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)) + .ndo_select_queue = rtw_select_queue, diff --git a/tools/canary_kernel_dump.sh b/tools/canary_kernel_dump.sh new file mode 100755 index 0000000..2c847dc --- /dev/null +++ b/tools/canary_kernel_dump.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# canary_kernel_dump.sh — kernel-side half of the devourer cross-validation oracle (TODO T1). +# +# Pair with `DEVOURER_DUMP_CANARY=1` on the devourer side. Each captures the +# same set of BB/MAC/RF "canary" registers post-init at a chosen channel; the +# resulting plaintext files diff line-by-line. Any mismatch is a candidate +# devourer init-drift bug. +# +# Tested against `aircrack-ng/88XXau`'s `iwpriv` interface (SIOCDEVPRIVATE +# strings: `read 4,` for BB/MAC, `rfr ` for RF). The +# iwpriv `read` only takes width 1 or 4; we use 4 everywhere — for 1-byte +# MAC regs the upper bytes are read-zero and don't affect the low-byte +# diff. +# +# Usage: +# sudo tools/canary_kernel_dump.sh > /tmp/krn.canary +# # then on devourer side: +# sudo env DEVOURER_VID=... DEVOURER_PID=... DEVOURER_CHANNEL= \ +# DEVOURER_DUMP_CANARY=1 ./build/WiFiDriverTxDemo 2>&1 \ +# | awk '/DEVOURER_DUMP_CANARY \(post channel-set ch=\)/, +# /END DEVOURER_DUMP_CANARY/' \ +# | sed 's/^//' \ +# > /tmp/dev.canary +# diff /tmp/krn.canary /tmp/dev.canary +# +# Iface is the wlx... or wlan? name the kernel driver enumerated. Run +# `iw dev` to find it after `modprobe 88XXau`. + +set -euo pipefail + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +IFACE="$1" +CHANNEL="$2" + +if ! ip -o link show "$IFACE" >/dev/null 2>&1; then + echo "iface '$IFACE' not found — did you modprobe 88XXau?" >&2 + exit 1 +fi + +# Put iface into monitor mode at the requested channel so post-init state is +# directly comparable to devourer's (which is also in monitor at the same +# channel). +iw dev "$IFACE" set type monitor 2>/dev/null || true +ip link set "$IFACE" up +iw dev "$IFACE" set channel "$CHANNEL" + +readreg() { + iwpriv "$IFACE" read 4,"$1" 2>&1 | grep -oP '0x[0-9A-Fa-f]+$' | head -1 +} + +rfread() { + iwpriv "$IFACE" rfr "$1" "$2" 2>&1 | grep -oP '0x[0-9A-Fa-f]+$' | head -1 +} + +echo "=== DEVOURER_DUMP_CANARY (post channel-set ch=$CHANNEL) ===" + +# BB canary set — same list as `RadioManagementModule::phy_SwChnlAndSetBwMode8812` +# emits when DEVOURER_DUMP_CANARY=1 is set. Keep the two lists in sync. +for ADDR in 0x808 0x80c 0x82c 0x830 0x834 0x838 0x84c 0x860 0x8ac \ + 0x8b0 0x8c4 0xc00 0xc1c 0xc20 0xc24 0xc28 0xc2c 0xc30 \ + 0xc34 0xc38 0xc3c 0xc40 0xc50 0xc54 0xc60 0xc64 0xc68 \ + 0xc6c 0xc70 0xc90 0xe1c 0xe50 0xe54; do + printf "BB %s = %s\n" "$ADDR" "$(readreg $ADDR)" +done + +for ADDR in 0x040 0x0cf 0x0f0 0x100 0x102 0x420 0x4c8 0x508 \ + 0x522 0x550 0x560 0x610 0x614; do + printf "MAC %s = %s\n" "$ADDR" "$(readreg $ADDR)" +done + +for PATH_IDX in 0 1; do + PATH_LBL=$([ "$PATH_IDX" = "0" ] && echo "A" || echo "B") + for RF in 0x00 0x05 0x18 0x42 0x65 0x8f; do + printf "RF[%s] %s = %s\n" "$PATH_LBL" "$RF" "$(rfread $PATH_IDX $RF)" + done +done + +echo "=== END DEVOURER_DUMP_CANARY ==="