diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ada613..245af54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ add_library(WiFiDriver hal/Hal8812PhyReg.h hal/Hal8812PwrSeq.c hal/Hal8812PwrSeq.h + hal/Hal8812a_TxPwrTrack.cpp + hal/Hal8812a_TxPwrTrack.h hal/Hal8814_PostFwdlReplay.h hal/Hal8814PwrSeq.c hal/Hal8814PwrSeq.h @@ -55,6 +57,8 @@ add_library(WiFiDriver src/ParsedRadioPacket.cpp src/PhyTableLoader.cpp src/PhyTableLoader.h + src/PowerTracking8812a.cpp + src/PowerTracking8812a.h src/RadioManagementModule.cpp src/RadioManagementModule.h src/Radiotap.c diff --git a/hal/Hal8812a_TxPwrTrack.cpp b/hal/Hal8812a_TxPwrTrack.cpp new file mode 100644 index 0000000..4579c85 --- /dev/null +++ b/hal/Hal8812a_TxPwrTrack.cpp @@ -0,0 +1,105 @@ +/* Verbatim copy of `g_delta_swing_table_idx_mp_*_txpowertrack_usb_8812a` + + * `tx_scaling_table_jaguar` from `aircrack-ng/rtl8812au` — + * `hal/phydm/rtl8812a/halhwimg8812a_rf.c:1310..1339` and + * `hal/phydm/halrf/halrf_powertracking_ce.c:538`. Do not hand-edit; if + * upstream changes these tables, regenerate from the canonical source. + */ +#include "Hal8812a_TxPwrTrack.h" + +const uint8_t kDeltaSwingTable2gaP[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; +const uint8_t kDeltaSwingTable2gaN[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, + 7, 8, 8, 9, 10, 10, 10, 10, 10, 10}; +const uint8_t kDeltaSwingTable2gbP[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; +const uint8_t kDeltaSwingTable2gbN[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 11, 11, 11, 11}; +const uint8_t kDeltaSwingTable2gCckAP[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; +const uint8_t kDeltaSwingTable2gCckAN[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, + 7, 8, 8, 9, 10, 10, 10, 10, 10, 10}; +const uint8_t kDeltaSwingTable2gCckBP[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; +const uint8_t kDeltaSwingTable2gCckBN[kDeltaSwingIdxSize] = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 11, 11, 11, 11}; + +const uint8_t kDeltaSwingTable5gaP[kFiveGBandNum][kDeltaSwingIdxSize] = { + {0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 7, 8, 8, 9, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}, + {0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 8, 9, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}, + {0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 8, 9, 10, 11, 11, 12, 12, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}, +}; +const uint8_t kDeltaSwingTable5gaN[kFiveGBandNum][kDeltaSwingIdxSize] = { + {0, 1, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 15, 15, 15, 15, 15}, + {0, 1, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 15, 15, 15, 15, 15}, + {0, 1, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 15, 15, 15, 15, 15}, +}; +const uint8_t kDeltaSwingTable5gbP[kFiveGBandNum][kDeltaSwingIdxSize] = { + {0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}, + {0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}, + {0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 8, 9, 9, 10, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}, +}; +const uint8_t kDeltaSwingTable5gbN[kFiveGBandNum][kDeltaSwingIdxSize] = { + {0, 1, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 14, 14, 14, 14, 14}, + {0, 1, 1, 2, 2, 3, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 14, 14, 14, 14, 14}, + {0, 1, 1, 2, 2, 3, 4, 5, 6, 6, 7, 8, 8, 9, 10, 10, 11, 11, 12, 12, + 13, 13, 14, 14, 15, 16, 16, 16, 16, 16}, +}; + +const uint32_t kTxScalingTableJaguar[kTxScaleTableSize] = { + 0x081, /* 0, -12.0 dB */ + 0x088, /* 1, -11.5 dB */ + 0x090, /* 2, -11.0 dB */ + 0x099, /* 3, -10.5 dB */ + 0x0A2, /* 4, -10.0 dB */ + 0x0AC, /* 5, -9.5 dB */ + 0x0B6, /* 6, -9.0 dB */ + 0x0C0, /* 7, -8.5 dB */ + 0x0CC, /* 8, -8.0 dB */ + 0x0D8, /* 9, -7.5 dB */ + 0x0E5, /* 10, -7.0 dB */ + 0x0F2, /* 11, -6.5 dB */ + 0x101, /* 12, -6.0 dB */ + 0x110, /* 13, -5.5 dB */ + 0x120, /* 14, -5.0 dB */ + 0x131, /* 15, -4.5 dB */ + 0x143, /* 16, -4.0 dB */ + 0x156, /* 17, -3.5 dB */ + 0x16A, /* 18, -3.0 dB */ + 0x180, /* 19, -2.5 dB */ + 0x197, /* 20, -2.0 dB */ + 0x1AF, /* 21, -1.5 dB */ + 0x1C8, /* 22, -1.0 dB */ + 0x1E3, /* 23, -0.5 dB */ + 0x200, /* 24, +0.0 dB */ + 0x21E, /* 25, +0.5 dB */ + 0x23E, /* 26, +1.0 dB */ + 0x261, /* 27, +1.5 dB */ + 0x285, /* 28, +2.0 dB */ + 0x2AB, /* 29, +2.5 dB */ + 0x2D3, /* 30, +3.0 dB */ + 0x2FE, /* 31, +3.5 dB */ + 0x32B, /* 32, +4.0 dB */ + 0x35C, /* 33, +4.5 dB */ + 0x38E, /* 34, +5.0 dB */ + 0x3C4, /* 35, +5.5 dB */ + 0x3FE, /* 36, +6.0 dB */ +}; diff --git a/hal/Hal8812a_TxPwrTrack.h b/hal/Hal8812a_TxPwrTrack.h new file mode 100644 index 0000000..b34af76 --- /dev/null +++ b/hal/Hal8812a_TxPwrTrack.h @@ -0,0 +1,52 @@ +#ifndef HAL_8812A_TX_PWR_TRACK_H +#define HAL_8812A_TX_PWR_TRACK_H + +#include + +/* Verbatim copy of `g_delta_swing_table_idx_mp_*_txpowertrack_usb_8812a` from + * `aircrack-ng/rtl8812au/hal/phydm/rtl8812a/halhwimg8812a_rf.c:1310..1339`. + * + * These are phydm's per-band, per-RF-path, per-temperature-direction + * lookup tables for the TX BB-swing thermal compensation loop. Indexed + * by `|thermal_value - eeprom_thermal|` (clamped to 29). The value + * returned is `absolute_ofdm_swing_idx[p]` — added to + * `default_ofdm_index` (= 24 for 8812A, i.e. table-index 0x200/0dB) + * to produce the final `tx_scaling_table_jaguar[]` index that + * `odm_tx_pwr_track_set_pwr8812a` writes to BB 0xc1c[31:21] + * (path A) or 0xe1c[31:21] (path B). + * + * Dimensions: `5g*_p/n` are [3][30] — three sub-band buckets (ch36..64, + * ch100..144, ch149..177) × 30 temperature deltas. The 2G arrays + * collapse to [30] (single band). + * + * "_p" suffix = thermal_value > eeprom_thermal (chip warmer than PG), + * "_n" suffix = chip cooler (negate the delta before applying). + * + * Source tag: aircrack-ng/rtl8812au@5.6.4.2_35491.20191025 + */ + +constexpr int kDeltaSwingIdxSize = 30; +constexpr int kFiveGBandNum = 3; + +extern const uint8_t kDeltaSwingTable2gaP[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable2gaN[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable2gbP[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable2gbN[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable2gCckAP[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable2gCckAN[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable2gCckBP[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable2gCckBN[kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable5gaP[kFiveGBandNum][kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable5gaN[kFiveGBandNum][kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable5gbP[kFiveGBandNum][kDeltaSwingIdxSize]; +extern const uint8_t kDeltaSwingTable5gbN[kFiveGBandNum][kDeltaSwingIdxSize]; + +/* phydm's TX BB-swing table for the Jaguar family, indexed by ofdm-swing + * index. Verbatim from `tx_scaling_table_jaguar` in + * `halrf_powertracking_ce.c:538`. Index 24 = 0x200 (0 dB), index 26 = + * 0x23E (+1.0 dB), table size 37 entries spanning -12 dB to +6 dB in + * 0.5 dB steps. */ +constexpr int kTxScaleTableSize = 37; +extern const uint32_t kTxScalingTableJaguar[kTxScaleTableSize]; + +#endif /* HAL_8812A_TX_PWR_TRACK_H */ diff --git a/src/EepromManager.h b/src/EepromManager.h index f4f1ec6..15c521c 100644 --- a/src/EepromManager.h +++ b/src/EepromManager.h @@ -32,6 +32,12 @@ class EepromManager { EepromManager(RtlUsbAdapter device, Logger_t logger); uint8_t GetBoardType(); void efuse_ShadowRead1Byte(uint16_t Offset, uint8_t *Value); + /* phydm thermal-meter pwrtrk baseline from EFUSE (set by + * `Hal_ReadThermalMeter_8812A`). Returns 0xFF when EFUSE autoload + * failed — in that case pwrtrk should be disabled because there's + * no factory-calibrated reference temperature to compute delta + * against. */ + uint8_t GetEepromThermalMeter() const { return eeprom_thermal_meter; } /* 8814AU only: read EFUSE and populate rfe_type, PA/LNA types, crystal cap, * etc. Must be called AFTER firmware download (pre-fwdl EFUSE access diff --git a/src/HalModule.cpp b/src/HalModule.cpp index 7c6a57c..40cb0f5 100644 --- a/src/HalModule.cpp +++ b/src/HalModule.cpp @@ -289,6 +289,14 @@ bool HalModule::rtl8812au_hal_init() { PHY_RF6052_Config_8812(); + phydm_SetIgiFloor_Jaguar(); + + /* Initialise phydm thermal-meter pwrtrk state now that BB+RF tables + * have been applied. Mirrors phydm's `phydm_rf_init` -> + * `odm_txpowertracking_init`. The watchdog ticks themselves run from + * the channel-set path + RtlJaguarDevice background thread. */ + _radioManagementModule->InitPwrTrack(); + if (_eepromManager->version_id.RFType == RF_TYPE_1T1R) { PHY_BB8812_Config_1T(); } @@ -2955,6 +2963,18 @@ void HalModule::odm_config_rf_radio_b_8812a(uint32_t addr, uint32_t data) { (uint16_t)(addr | maskfor_phy_set)); } +void HalModule::phydm_SetIgiFloor_Jaguar() { + /* Port of phydm DIG floor convergence for the Jaguar family. Upstream + * phydm_dig.c sets `dig_t->dm_dig_min = 0x1c` for + * `(ODM_RTL8812 | ODM_RTL8814A | ODM_RTL8821 | ODM_RTL8822B)` and the + * DIG watchdog walks 0xc50/0xe50 down to this floor under clean RX + * conditions. Without phydm's watchdog devourer's IGI never moves + * from the 0x20 BB-table seed and runs ~4 dB less sensitive than the + * kernel driver. Match kernel by writing the floor once here. */ + _device.phy_set_bb_reg(rA_IGI_Jaguar, bMaskByte0, 0x1c); + _device.phy_set_bb_reg(rB_IGI_Jaguar, bMaskByte0, 0x1c); +} + void HalModule::PHY_BB8812_Config_1T() { /* BB OFDM RX Path_A */ _device.phy_set_bb_reg(rRxPath_Jaguar, bRxPath_Jaguar, 0x11); diff --git a/src/HalModule.h b/src/HalModule.h index 0695500..eaa7373 100644 --- a/src/HalModule.h +++ b/src/HalModule.h @@ -148,6 +148,18 @@ class HalModule { void odm_config_rf_radio_b_8812a(uint32_t addr, uint32_t data); void PHY_BB8812_Config_1T(); + /* Pre-converge the path-A/B Initial Gain Index to phydm's documented + * DIG floor (0x1c) for all Jaguar chips. Upstream phydm runs DIG + * (Dynamic Initial Gain) from its watchdog and converges 0xc50/0xe50 + * to 0x1c when no false-alarm pressure is present (phydm_dig.c: + * `dig_t->dm_dig_min = 0x1c` for ODM_RTL8812 | RTL8814A | RTL8821). + * Devourer has no phydm watchdog, so devourer's static 0x20 (from the + * BB-init table) never moves and the chip runs slightly less sensitive + * than the kernel driver. Writing 0x1c once at init matches kernel + * post-DIG-converged state — last remaining T1 canary divergence on + * 0xc50/0xe50 that wasn't a real init bug. Safe for monitor RX: + * higher sensitivity, lower CCA threshold, no TX-side impact. */ + void phydm_SetIgiFloor_Jaguar(); }; #endif /* HALMODULE_H */ diff --git a/src/PowerTracking8812a.cpp b/src/PowerTracking8812a.cpp new file mode 100644 index 0000000..93187fa --- /dev/null +++ b/src/PowerTracking8812a.cpp @@ -0,0 +1,246 @@ +#include "PowerTracking8812a.h" + +#include "Hal8812a_TxPwrTrack.h" +#include "Hal8812PhyReg.h" +#include "RadioManagementModule.h" +#include "RfPath.h" + +PowerTracking8812a::PowerTracking8812a( + RtlUsbAdapter device, std::shared_ptr eepromManager, + RadioManagementModule *radio, Logger_t logger) + : _device(device), _eepromManager(eepromManager), _radio(radio), + _logger(logger) {} + +uint8_t PowerTracking8812a::LookupSwingIndexFromBb() { + /* Mirror upstream `get_swing_index` for the Jaguar (8812A) branch + * (`halrf_powertracking_ce.c:632`). Read BB 0xc1c[31:21] and find + * the matching index in `tx_scaling_table_jaguar`. If no match, the + * upstream caller falls back to default_ofdm_index = 24. */ + uint32_t bb_swing = _radio->phy_query_bb_reg_public(rA_TxScale_Jaguar, 0xFFE00000u); + for (int i = 0; i < kTxScaleTableSize; i++) { + if (bb_swing == kTxScalingTableJaguar[i]) { + return static_cast(i); + } + } + return static_cast(kTxScaleTableSize); /* sentinel — caller picks 24 */ +} + +void PowerTracking8812a::Init() { + /* Mirror `odm_txpowertracking_thermal_meter_init` (the 8812A path at + * line 730 of halrf_powertracking_ce.c). + * + * `default_bb_swing_index_flag` is per-driver-lifetime — devourer + * has only one HalModule init per device-open, so we always run + * the init body. */ + uint8_t swing_idx = LookupSwingIndexFromBb(); + if (swing_idx >= kTxScaleTableSize) { + _defaultOfdmIndex = 24; /* 0 dB */ + } else { + _defaultOfdmIndex = swing_idx; + } + + /* eeprom_thermal_meter is populated by `Hal_ReadThermalMeter_8812A` + * during EFUSE shadow-map parse. 0xff means autoload failed — no + * thermal compensation possible. */ + if (_eepromManager->GetEepromThermalMeter() == 0xff) { + _txPowerTrackControl = false; + _logger->info("PowerTracking8812a: EFUSE thermal_meter=0xFF — " + "tracking disabled"); + } else { + _txPowerTrackControl = true; + } + + _thermalValue = _eepromManager->GetEepromThermalMeter(); + _thermalValueAvgIndex = 0; + for (auto &v : _thermalValueAvg) { + v = 0; + } + for (int p = 0; p < 2; p++) { + _absoluteOfdmSwingIdx[p] = 0; + _deltaPowerIndex[p] = 0; + _deltaPowerIndexLast[p] = 0; + } + _initialised = true; + + _logger->info( + "PowerTracking8812a init: default_ofdm_index={} (swing_idx_from_bb={}) " + "eeprom_thermal=0x{:x} txpwrtrack_ctrl={}", + unsigned(_defaultOfdmIndex), unsigned(swing_idx), + unsigned(_eepromManager->GetEepromThermalMeter()), + unsigned(_txPowerTrackControl)); +} + +void PowerTracking8812a::ClearState() { + _thermalValueAvgIndex = 0; + for (auto &v : _thermalValueAvg) { + v = 0; + } + for (int p = 0; p < 2; p++) { + _absoluteOfdmSwingIdx[p] = 0; + _deltaPowerIndex[p] = 0; + _deltaPowerIndexLast[p] = 0; + } + _thermalValue = _eepromManager->GetEepromThermalMeter(); +} + +void PowerTracking8812a::GetTrackingTable(BandType band, uint8_t channel, + uint8_t thermal_value, + uint8_t delta) { + /* Mirror `odm_get_tracking_table` (halphyrf_ce.c:155) + the + * channel/rate dispatch from `get_delta_swing_table_8812a` + * (halrf_8812a_ce.c:328). devourer always passes `tx_rate==0xFF` + * (monitor mode, no current rate) so we always take the + * non-CCK OFDM lookup. */ + + const uint8_t *tab_up_a = kDeltaSwingTable2gaP; + const uint8_t *tab_down_a = kDeltaSwingTable2gaN; + const uint8_t *tab_up_b = kDeltaSwingTable2gbP; + const uint8_t *tab_down_b = kDeltaSwingTable2gbN; + + if (band == BandType::BAND_ON_2_4G) { + tab_up_a = kDeltaSwingTable2gaP; + tab_down_a = kDeltaSwingTable2gaN; + tab_up_b = kDeltaSwingTable2gbP; + tab_down_b = kDeltaSwingTable2gbN; + } else { + /* 5G — pick sub-band bucket per upstream: + * ch 36..64 -> [0], ch 100..144 -> [1], ch 149..177 -> [2]. */ + int bucket = 0; + if (channel >= 36 && channel <= 64) + bucket = 0; + else if (channel >= 100 && channel <= 144) + bucket = 1; + else if (channel >= 149 && channel <= 177) + bucket = 2; + else + bucket = 0; /* safety — invalid channel for 5G */ + tab_up_a = kDeltaSwingTable5gaP[bucket]; + tab_down_a = kDeltaSwingTable5gaN[bucket]; + tab_up_b = kDeltaSwingTable5gbP[bucket]; + tab_down_b = kDeltaSwingTable5gbN[bucket]; + } + + uint8_t eeprom_thermal = _eepromManager->GetEepromThermalMeter(); + bool warmer = (thermal_value > eeprom_thermal); + + for (int p = 0; p < 2; p++) { + _deltaPowerIndexLast[p] = _deltaPowerIndex[p]; + const uint8_t *tab = (p == 0) + ? (warmer ? tab_up_a : tab_down_a) + : (warmer ? tab_up_b : tab_down_b); + int8_t v = static_cast(tab[delta]); + if (warmer) { + _deltaPowerIndex[p] = v; + _absoluteOfdmSwingIdx[p] = v; + } else { + _deltaPowerIndex[p] = static_cast(-v); + _absoluteOfdmSwingIdx[p] = static_cast(-v); + } + } +} + +void PowerTracking8812a::ApplySwingToBb() { + /* Mirror `odm_tx_pwr_track_set_pwr8812a` MIX_MODE path + * (halrf_8812a_ce.c:218). Devourer never sees a known tx_rate so + * `pwr_tracking_limit` keeps its initial value of 26 (+1.0 dB) — + * same as upstream's `tx_rate==0xFF` default. */ + for (int p = 0; p < 2; p++) { + int final_ofdm_swing_index = + static_cast(_defaultOfdmIndex) + _absoluteOfdmSwingIdx[p]; + + int idx_to_write; + if (final_ofdm_swing_index > kPwrTrackingLimit) { + idx_to_write = kPwrTrackingLimit; + } else if (final_ofdm_swing_index <= 0) { + idx_to_write = 0; + } else { + idx_to_write = final_ofdm_swing_index; + } + + uint32_t bb_addr = + (p == 0) ? rA_TxScale_Jaguar : rB_TxScale_Jaguar; + _device.phy_set_bb_reg(bb_addr, 0xFFE00000u, + kTxScalingTableJaguar[idx_to_write]); + } +} + +void PowerTracking8812a::TickThermalMeter(BandType band, uint8_t channel) { + if (!_initialised) { + _logger->debug( + "PowerTracking8812a::TickThermalMeter called before Init — skipped"); + return; + } + if (!_txPowerTrackControl) { + return; + } + + /* Read live thermal meter via RF[A][0x42][15:10]. Mirrors the + * `odm_get_rf_reg(dm, RF_PATH_A, c.thermal_reg_addr, 0xfc00)` call + * in `odm_txpowertracking_callback_thermal_meter` — 8812A's + * `c.thermal_reg_addr = RF_T_METER_8812A = 0x42`. RMM's + * `phy_query_rf_reg` already shifts the masked bits down, so the + * returned value is the 6-bit thermal reading. */ + uint32_t rf_thermal = + _radio->phy_query_rf_reg(RfPath::RF_PATH_A, 0x42, 0xfc00u); + uint8_t thermal_value = static_cast(rf_thermal & 0x3F); + + /* Average over kAvgThermalNum samples. */ + _thermalValueAvg[_thermalValueAvgIndex] = thermal_value; + _thermalValueAvgIndex = static_cast( + (_thermalValueAvgIndex + 1) % kAvgThermalNum); + uint32_t sum = 0; + uint8_t cnt = 0; + for (int i = 0; i < kAvgThermalNum; i++) { + if (_thermalValueAvg[i] != 0) { + sum += _thermalValueAvg[i]; + cnt++; + } + } + uint8_t avg = thermal_value; + if (cnt > 0) { + avg = static_cast(sum / cnt); + } + + uint8_t eeprom_thermal = _eepromManager->GetEepromThermalMeter(); + uint8_t delta_abs = 0; + if (avg > _thermalValue) { + delta_abs = static_cast(avg - _thermalValue); + } else { + delta_abs = static_cast(_thermalValue - avg); + } + + _logger->debug( + "pwrtrk tick: ch={} band={} thermal_raw=0x{:x} avg=0x{:x} eeprom=0x{:x} " + "delta_abs={} last_thermal=0x{:x}", + unsigned(channel), unsigned(band), unsigned(thermal_value), unsigned(avg), + unsigned(eeprom_thermal), unsigned(delta_abs), unsigned(_thermalValue)); + + if (delta_abs > 0) { + uint8_t delta = 0; + if (avg > eeprom_thermal) { + delta = static_cast(avg - eeprom_thermal); + } else { + delta = static_cast(eeprom_thermal - avg); + } + if (delta >= kDeltaSwingIdxSize) { + delta = kDeltaSwingIdxSize - 1; + } + GetTrackingTable(band, channel, avg, delta); + + _logger->debug( + "pwrtrk delta={} abs_ofdm_swing_idx=[A={}, B={}] delta_pwr=[{}, {}] " + "default_ofdm={} -> final=[A={}, B={}]", + unsigned(delta), int(_absoluteOfdmSwingIdx[0]), + int(_absoluteOfdmSwingIdx[1]), int(_deltaPowerIndex[0]), + int(_deltaPowerIndex[1]), unsigned(_defaultOfdmIndex), + int(_defaultOfdmIndex) + int(_absoluteOfdmSwingIdx[0]), + int(_defaultOfdmIndex) + int(_absoluteOfdmSwingIdx[1])); + + if (_deltaPowerIndex[0] != _deltaPowerIndexLast[0] || + _deltaPowerIndex[1] != _deltaPowerIndexLast[1]) { + ApplySwingToBb(); + } + } + + _thermalValue = avg; +} diff --git a/src/PowerTracking8812a.h b/src/PowerTracking8812a.h new file mode 100644 index 0000000..87be98a --- /dev/null +++ b/src/PowerTracking8812a.h @@ -0,0 +1,112 @@ +#ifndef POWER_TRACKING_8812A_H +#define POWER_TRACKING_8812A_H + +#include "EepromManager.h" +#include "RfPath.h" +#include "RtlUsbAdapter.h" +#include "SelectedChannel.h" +#include "logger.h" + +#include +#include + +enum class BandType; +class RadioManagementModule; + +/* Port of upstream phydm's TX BB-swing thermal-meter compensation loop + * for the 8812AU. Closes the last T1 canary divergence on BB 0xc1c / + * 0xe1c bits 31:21 by reproducing the math kernel runs from its + * watchdog timer: + * + * 1. Read RF[A][0x42][15:10] = chip thermal meter (6-bit reading). + * 2. Maintain a rolling average over `kAvgThermalNum` samples. + * 3. delta = |avg - eeprom_thermal_meter|, clamped to 29. + * 4. Look up `absolute_ofdm_swing_idx` from `kDeltaSwingTable*` based + * on (band, channel range, sign-of-delta, RF path). + * 5. final_idx = default_ofdm_index (24, 0 dB) + absolute_ofdm_swing_idx + * (clamped to [0, kPwrTrackingLimit]). + * 6. Write `kTxScalingTableJaguar[final_idx]` to: + * - BB 0xc1c[31:21] for RF_PATH_A (rA_TxScale_Jaguar) + * - BB 0xe1c[31:21] for RF_PATH_B (rB_TxScale_Jaguar) + * + * Mirrors `odm_txpowertracking_callback_thermal_meter` + + * `odm_tx_pwr_track_set_pwr8812a` + `odm_get_tracking_table` in + * `aircrack-ng/rtl8812au/hal/phydm/halrf/halphyrf_ce.c` and + * `.../rtl8812a/halrf_8812a_ce.c`. Helpers omitted because they're not + * reachable from devourer's monitor-mode RX/TX path: + * - IQK retrigger on thermal delta — IQK is done at init only; + * - LCK retrigger — 8812A doesn't do LCK from powertracking; + * - by-rate `pwr_tracking_limit` table — devourer always sees + * tx_rate==0xFF, so the limit defaults to 26 (+1 dB) just like + * upstream when no rate is known; + * - tx-AGC remnant (`remnant_ofdm_swing_idx`) — final_idx is held + * below pwr_tracking_limit by clamping, so the remnant path never + * fires; + * - 8814A path-C/D — separate port; + * - CCK-rate / xtal-offset / DPK — not relevant on 8812AU. + * + * Wiring: + * - `Init()` runs once after BB+RF config (phydm: + * `odm_txpowertracking_init` from `phydm_rf_init`). + * - `TickThermalMeter()` runs after every channel-set; the + * `RtlJaguarDevice` watchdog thread also calls it every 2s for + * steady-state thermal tracking. + */ +class PowerTracking8812a { +public: + PowerTracking8812a(RtlUsbAdapter device, + std::shared_ptr eepromManager, + RadioManagementModule *radio, Logger_t logger); + + /* Initialise from EFUSE thermal-meter baseline. Reads the current + * value of BB 0xc1c[31:21] to seed `default_ofdm_index` (matches + * upstream's `get_swing_index`). */ + void Init(); + + /* Read live thermal meter, fold into the rolling average, recompute + * the BB-swing index, and write 0xc1c[31:21] / 0xe1c[31:21]. The + * `band` + `channel` args pick the right delta-swing-table bucket + * (per `get_delta_swing_table_8812a`). + * + * Set `phy_set_rf_reg` true when called from outside the per-channel + * lock path (i.e. from the watchdog thread); when called inside + * channel-set the device serialisation is already held. */ + void TickThermalMeter(BandType band, uint8_t channel); + + /* Reset rolling thermal-average buffer and per-path baselines back + * to `default_ofdm_index`. Upstream calls this on band switch + + * tx_agc change (`odm_clear_txpowertracking_state`). */ + void ClearState(); + +private: + RtlUsbAdapter _device; + std::shared_ptr _eepromManager; + RadioManagementModule *_radio; /* non-owning back-pointer for RF reads */ + Logger_t _logger; + + static constexpr int kAvgThermalNum = 4; + static constexpr int kPwrTrackingLimit = 26; /* +1.0 dB */ + static constexpr int kTxScaleTableSize = 37; + static constexpr int kDeltaSwingIdxSize = 30; + + bool _txPowerTrackControl = true; + bool _initialised = false; + + uint8_t _defaultOfdmIndex = 24; /* 0 dB; reseeded by Init() */ + + uint8_t _thermalValue = 0; /* last avg after compute */ + uint8_t _thermalValueAvgIndex = 0; + uint8_t _thermalValueAvg[kAvgThermalNum]{}; + + /* Per-RF-path running state. [0]=A, [1]=B. */ + int8_t _absoluteOfdmSwingIdx[2]{}; + int8_t _deltaPowerIndex[2]{}; + int8_t _deltaPowerIndexLast[2]{}; + + uint8_t LookupSwingIndexFromBb(); + void GetTrackingTable(BandType band, uint8_t channel, uint8_t thermal_value, + uint8_t delta); + void ApplySwingToBb(); +}; + +#endif /* POWER_TRACKING_8812A_H */ diff --git a/src/RadioManagementModule.cpp b/src/RadioManagementModule.cpp index e986fdc..4b8f9fe 100644 --- a/src/RadioManagementModule.cpp +++ b/src/RadioManagementModule.cpp @@ -48,7 +48,19 @@ int get_40mhz_center_channel(int channel) { RadioManagementModule::RadioManagementModule( RtlUsbAdapter device, std::shared_ptr eepromManager, Logger_t logger) - : _device{device}, _eepromManager{eepromManager}, _logger{logger} {} + : _device{device}, _eepromManager{eepromManager}, _logger{logger}, + _pwrTrk{device, eepromManager, this, logger} {} + +uint32_t RadioManagementModule::phy_query_bb_reg_public(uint16_t regAddr, + uint32_t bitMask) { + return phy_query_bb_reg(regAddr, bitMask); +} + +void RadioManagementModule::InitPwrTrack() { _pwrTrk.Init(); } + +void RadioManagementModule::TickPwrTrack() { + _pwrTrk.TickThermalMeter(current_band_type, _currentChannel); +} void RadioManagementModule::hw_var_rcr_config(uint32_t rcr) { _device.rtw_write32(REG_RCR, rcr); @@ -255,11 +267,24 @@ void RadioManagementModule::phy_SwChnlAndSetBwMode8812() { PHY_SetTxPowerLevel8812(_currentChannel); } + /* Run phydm thermal-meter pwrtrk once per channel-set. Mirrors the + * upstream watchdog tick — reads RF[A][0x42], folds into the + * thermal-value rolling average, walks the delta-swing table for + * the (band, channel) bucket, and writes the resulting BB-swing + * index to 0xc1c[31:21] / 0xe1c[31:21]. */ + _pwrTrk.TickThermalMeter(current_band_type, _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. */ + * can be diffed line-by-line. + * + * BB 0xc1c[31:21] / 0xe1c[31:21] are now phydm-managed; the value + * is thermal-meter-driven so small (~1-step) divergence is expected + * if devourer and kernel sampled the chip at non-identical + * temperatures. Capture both dumps within a few seconds for clean + * parity. */ if (std::getenv("DEVOURER_DUMP_CANARY")) { static const uint16_t bb_canary[] = { 0x808, 0x80c, 0x82c, 0x830, 0x834, 0x838, 0x84c, 0x860, 0x8ac, @@ -724,6 +749,15 @@ void RadioManagementModule::phy_SetBBSwingByBand_8812A(BandType Band) { _device.phy_set_bb_reg( rB_TxScale_Jaguar, 0xFFE00000, phy_get_tx_bb_swing_8812a(Band, RfPath::RF_PATH_B)); /* 0xE1C[31:21] */ + + /* Mirror upstream `phy_SetBBSwingByBand_8812A` which calls + * `odm_clear_txpowertracking_state(pDM_Odm)` after rewriting the + * BB-swing base. Without this the next pwrtrk tick sees + * delta_abs==0 (thermal_value unchanged since the previous tick) + * and short-circuits the re-apply — leaving 0xc1c[31:21] stuck at + * the BB-init base (0x200) even when the previous channel-set's + * tick had walked it up to the thermal-warmed value. */ + _pwrTrk.ClearState(); } #define EEPROM_TX_BBSWING_2G_8812 0xC6 diff --git a/src/RadioManagementModule.h b/src/RadioManagementModule.h index a563639..7cd0164 100644 --- a/src/RadioManagementModule.h +++ b/src/RadioManagementModule.h @@ -5,6 +5,7 @@ #include #include "EepromManager.h" +#include "PowerTracking8812a.h" #include "RfPath.h" #include "RtlUsbAdapter.h" #include "SelectedChannel.h" @@ -150,11 +151,21 @@ class RadioManagementModule { uint8_t _cur80MhzPrimeSc; uint8_t _currentCenterFrequencyIndex; uint8_t power = 16; + PowerTracking8812a _pwrTrk; public: RadioManagementModule(RtlUsbAdapter device, std::shared_ptr eepromManager, Logger_t logger); + /* Initialise phydm thermal-meter pwrtrk state. Call once after the + * BB + RF table application is complete (mirrors phydm's + * `phydm_rf_init -> odm_txpowertracking_init`). Reads EFUSE + + * current 0xc1c[31:21]. Safe to call multiple times. */ + void InitPwrTrack(); + /* Run one phydm thermal-meter pwrtrk tick. Mirrors the watchdog + * callback `odm_txpowertracking_callback_thermal_meter` and writes + * the resulting BB-swing index to 0xc1c[31:21] / 0xe1c[31:21]. */ + void TickPwrTrack(); void hw_var_rcr_config(uint32_t rcr); void SetMonitorMode(); void set_channel_bwmode(uint8_t channel, uint8_t channel_offset, @@ -163,6 +174,7 @@ class RadioManagementModule { uint32_t Data); uint32_t phy_query_rf_reg(RfPath eRFPath, uint32_t RegAddr, uint32_t BitMask); + uint32_t phy_query_bb_reg_public(uint16_t regAddr, uint32_t bitMask); void init_hw_mlme_ext(SelectedChannel pmlmeext); void rtw_hal_set_chnl_bw(uint8_t channel, ChannelWidth_t Bandwidth, uint8_t Offset40, uint8_t Offset80); diff --git a/tools/canary_kernel_dump.sh b/tools/canary_kernel_dump.sh index 2c847dc..ba93deb 100755 --- a/tools/canary_kernel_dump.sh +++ b/tools/canary_kernel_dump.sh @@ -25,6 +25,15 @@ # # Iface is the wlx... or wlan? name the kernel driver enumerated. Run # `iw dev` to find it after `modprobe 88XXau`. +# +# Expected divergence on a clean diff (do NOT chase as devourer bug): +# BB 0x0c1c bits 31:21 — phydm TX BB-swing thermal compensation. The +# kernel's phydm watchdog reads RF reg 0x42 thermal meter every 2s +# and walks 0xc1c[31:21] up/down through `tx_scaling_table_jaguar` +# (typical 0x21E/+0.5dB or 0x23E/+1.0dB on a warmed-up dongle). +# Devourer keeps the BB-init value 0x200 (0 dB, table index 24). +# Porting the full thermal-tracking watchdog (~2800 LOC) is out of +# scope; the other bits of 0xc1c are byte-for-byte. set -euo pipefail