From 222789298467e3bdfd2717797894aeff0c0851c3 Mon Sep 17 00:00:00 2001 From: snokvist Date: Thu, 6 Aug 2026 16:39:42 +0200 Subject: [PATCH 1/3] fix(jaguar3): efuse walk assumed ordered sections and quit early --- src/jaguar3/HalJaguar3.cpp | 38 ++++++++++++++++++++++++++------------ src/jaguar3/HalJaguar3.h | 6 ++++-- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index d299c2eb..ff66c809 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -602,11 +602,27 @@ void HalJaguar3::config_pa_bias_8822e() { } /* Decode the packed (extended-header) EFUSE into a logical map, up to (and - * including the block holding) logical offset `upto`. Shared by read_efuse_rfe_type - * and read_efuse_txpwr_base_8822e. `map` must be zero-init'd by the caller (this - * fills 0xFF for gaps). Standard Realtek section format: header (or header+ext) - * gives a logical block offset + 4-bit word-enable; each enabled 2-byte word - * follows. */ + * including the block holding) every programmed logical offset. Shared by + * read_efuse_rfe_type and read_efuse_txpwr_base_8822e. `map` must be zero-init'd + * by the caller (this fills 0xFF for gaps). Standard Realtek section format: + * header (or header+ext) gives a logical block offset + 4-bit word-enable; each + * enabled 2-byte word follows. + * + * The walk runs to the end of the programmed area. It used to stop early once a + * section's base passed the byte the caller asked for, which silently assumed + * the sections appear in ascending base order — they do not. Measured on an + * RTL8822CU, the third section on the chip jumps to base 0x100: + * + * phys 0x00 hdr=0x00 -> base 0x000 + * phys 0x09 hdr=0x10 -> base 0x008 + * phys 0x12 hdr=0x0F ext=48 -> base 0x100 <- early exit fired here + * phys 0x2C hdr=0x4F ext=5D -> base 0x150 (never reached) + * + * so asking for anything below 0x100 — including EEPROM_RFE_OPTION at 0xCA — + * ended the walk after three sections and returned a map that was 0xFF almost + * everywhere. On that adapter read_efuse_rfe_type() therefore returned 0 while + * the kernel driver read 0x15 from the same chip, i.e. the BB/RFE config was + * being chosen from an unprogrammed default. */ bool HalJaguar3::probe_efuse_map(uint8_t *map, size_t len) { /* 8822E OTP reads are not reliable after TX/coex bring-up (by design — see * cache_efuse_8822e); probing there would flag healthy units. 8822C only. */ @@ -614,11 +630,11 @@ bool HalJaguar3::probe_efuse_map(uint8_t *map, size_t len) { return false; if (map == nullptr || len != sizeof(_efuse_cache)) return false; - read_efuse_logical_map(map, len, 0xFA); + read_efuse_logical_map(map, len); return true; } -void HalJaguar3::read_efuse_logical_map(uint8_t *map, size_t len, uint16_t upto) { +void HalJaguar3::read_efuse_logical_map(uint8_t *map, size_t len) { constexpr uint16_t kPhysMax = 1024; /* EFUSE_REAL_CONTENT_LEN_8822C */ for (size_t i = 0; i < len; ++i) map[i] = 0xFF; @@ -704,15 +720,13 @@ void HalJaguar3::read_efuse_logical_map(uint8_t *map, size_t len, uint16_t upto) map[idx] = d; } } - if (base > upto + 8) - break; /* past the byte we need */ } } void HalJaguar3::cache_efuse_8822e() { if (_variant != ChipVariant::C8822E) return; - read_efuse_logical_map(_efuse_cache, sizeof(_efuse_cache), 0xFA); + read_efuse_logical_map(_efuse_cache, sizeof(_efuse_cache)); _efuse_cache_valid = true; _logger->info("Jaguar3(8822e): efuse decoded (0x22={:x} 0x4c={:x} 0xca={:x})", _efuse_cache[0x22], _efuse_cache[0x4c], _efuse_cache[0xca]); @@ -725,7 +739,7 @@ uint8_t HalJaguar3::read_efuse_rfe_type() { rfe = _efuse_cache[kRfeLogicalOff]; } else { uint8_t map[0x100 + 0x40]; /* enough to cover block holding 0xCA */ - read_efuse_logical_map(map, sizeof(map), kRfeLogicalOff); + read_efuse_logical_map(map, sizeof(map)); rfe = map[kRfeLogicalOff]; } return (rfe == 0xFF) ? 0 : rfe; @@ -762,7 +776,7 @@ void HalJaguar3::read_efuse_txpwr_base_8822e(uint8_t channel, uint8_t &base_a, if (_efuse_cache_valid) { map = _efuse_cache; /* decoded early where OTP access is reliable */ } else { - read_efuse_logical_map(local, sizeof(local), k5gB + 14); + read_efuse_logical_map(local, sizeof(local)); map = local; } int g = chnl_group_5g(channel); diff --git a/src/jaguar3/HalJaguar3.h b/src/jaguar3/HalJaguar3.h index 840624e6..9b1d940e 100644 --- a/src/jaguar3/HalJaguar3.h +++ b/src/jaguar3/HalJaguar3.h @@ -195,8 +195,10 @@ class HalJaguar3 { private: /* Decode the packed extended-header EFUSE into a logical map up to (the block - * holding) offset `upto`. Backs read_efuse_rfe_type + read_efuse_txpwr_base. */ - void read_efuse_logical_map(uint8_t *map, size_t len, uint16_t upto); + * holding) offset `upto`. Backs read_efuse_rfe_type + read_efuse_txpwr_base. + * Walks the whole programmed area: sections are NOT ordered by logical base, + * so there is no sound early exit (see the .cpp). */ + void read_efuse_logical_map(uint8_t *map, size_t len); /* One-shot decode of the logical EFUSE into _efuse_cache during rtw_hal_init, * where OTP access is reliable. RFE + per-channel TX-power base are then served From f0dc4b652bb6a2cd756695b2520bdd3e5ea27534 Mon Sep 17 00:00:00 2001 From: snokvist Date: Thu, 6 Aug 2026 16:48:11 +0200 Subject: [PATCH 2/3] fix: guard the EFUSE walk against reading past kPhysMax; correct the stale doc --- src/jaguar3/HalJaguar3.cpp | 9 +++++++++ src/jaguar3/HalJaguar3.h | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index ff66c809..93e82998 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -647,6 +647,15 @@ void HalJaguar3::read_efuse_logical_map(uint8_t *map, size_t len) { if (eu) efuse_pwr_cut_8822e(true); auto rd = [this, eu](uint16_t a) -> uint8_t { + /* A section straddling the end of the physical area would otherwise run + * `phys` past kPhysMax: the loop head checks it once per section, but a + * header + ext + four data words advance it up to ten more bytes. That + * matters because efuse_OneByteRead masks the address to 10 bits, so a + * read at 1024 aliases to 0 and would silently decode the START of the + * EFUSE into whatever logical base the truncated section named. 0xFF is + * what both walks already treat as end-of-map / skip. */ + if (a >= kPhysMax) + return 0xFF; if (eu) return efuse_phys_read_8822e(a); uint8_t d = 0xFF; diff --git a/src/jaguar3/HalJaguar3.h b/src/jaguar3/HalJaguar3.h index 9b1d940e..d1fa7a48 100644 --- a/src/jaguar3/HalJaguar3.h +++ b/src/jaguar3/HalJaguar3.h @@ -194,10 +194,11 @@ class HalJaguar3 { private: - /* Decode the packed extended-header EFUSE into a logical map up to (the block - * holding) offset `upto`. Backs read_efuse_rfe_type + read_efuse_txpwr_base. - * Walks the whole programmed area: sections are NOT ordered by logical base, - * so there is no sound early exit (see the .cpp). */ + /* Decode the packed extended-header EFUSE into a logical map. Backs + * read_efuse_rfe_type + read_efuse_txpwr_base. Walks the whole programmed + * area — sections are NOT ordered by logical base, so stopping once a + * requested offset is passed would drop later sections that backfill lower + * offsets (see the .cpp for the measured case). */ void read_efuse_logical_map(uint8_t *map, size_t len); /* One-shot decode of the logical EFUSE into _efuse_cache during rtw_hal_init, From 38790b1f1db9ec009939b18a5f28c5e1d6d10dfe Mon Sep 17 00:00:00 2001 From: snokvist Date: Thu, 6 Aug 2026 20:32:07 +0200 Subject: [PATCH 3/3] docs: fix the rfe_type value in the walk comment, move it onto the walk - The comment said the kernel reads 0x15 from the affected 8822CU. 0x15 is the 8822EU's value (the regression-check adapter); the 8822CU reads 0x03, as both the PR's hardware table and the fix's before/after show. - The block documents read_efuse_logical_map but sat above probe_efuse_map (pre-existing); moved onto the function it describes. - Present-tense: the invariant is that sections are not in ascending base order, with the measured phys dump as its evidence. Git carries the "it used to" part. Comment-only. Build clean, 49/49 ctest. Co-Authored-By: Claude Opus 5 (1M context) --- src/jaguar3/HalJaguar3.cpp | 42 ++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index 93e82998..0e76fb11 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -601,28 +601,6 @@ void HalJaguar3::config_pa_bias_8822e() { pg2a & 0xf, pg2b & 0xf, pg5a & 0xf, pg5b & 0xf); } -/* Decode the packed (extended-header) EFUSE into a logical map, up to (and - * including the block holding) every programmed logical offset. Shared by - * read_efuse_rfe_type and read_efuse_txpwr_base_8822e. `map` must be zero-init'd - * by the caller (this fills 0xFF for gaps). Standard Realtek section format: - * header (or header+ext) gives a logical block offset + 4-bit word-enable; each - * enabled 2-byte word follows. - * - * The walk runs to the end of the programmed area. It used to stop early once a - * section's base passed the byte the caller asked for, which silently assumed - * the sections appear in ascending base order — they do not. Measured on an - * RTL8822CU, the third section on the chip jumps to base 0x100: - * - * phys 0x00 hdr=0x00 -> base 0x000 - * phys 0x09 hdr=0x10 -> base 0x008 - * phys 0x12 hdr=0x0F ext=48 -> base 0x100 <- early exit fired here - * phys 0x2C hdr=0x4F ext=5D -> base 0x150 (never reached) - * - * so asking for anything below 0x100 — including EEPROM_RFE_OPTION at 0xCA — - * ended the walk after three sections and returned a map that was 0xFF almost - * everywhere. On that adapter read_efuse_rfe_type() therefore returned 0 while - * the kernel driver read 0x15 from the same chip, i.e. the BB/RFE config was - * being chosen from an unprogrammed default. */ bool HalJaguar3::probe_efuse_map(uint8_t *map, size_t len) { /* 8822E OTP reads are not reliable after TX/coex bring-up (by design — see * cache_efuse_8822e); probing there would flag healthy units. 8822C only. */ @@ -634,6 +612,26 @@ bool HalJaguar3::probe_efuse_map(uint8_t *map, size_t len) { return true; } +/* Decode the packed (extended-header) EFUSE into a logical map. Shared by + * read_efuse_rfe_type and read_efuse_txpwr_base_8822e. `map` must be zero-init'd + * by the caller (this fills 0xFF for gaps). Standard Realtek section format: + * header (or header+ext) gives a logical block offset + 4-bit word-enable; each + * enabled 2-byte word follows. + * + * The walk decodes the whole programmed area. Sections are not in ascending base + * order, so it must not stop at any requested offset — measured on an RTL8822CU, + * the third section on the chip jumps to base 0x100: + * + * phys 0x00 hdr=0x00 -> base 0x000 + * phys 0x09 hdr=0x10 -> base 0x008 + * phys 0x12 hdr=0x0F ext=48 -> base 0x100 + * phys 0x2C hdr=0x4F ext=5D -> base 0x150 + * + * A walk bounded by the byte the caller asked for ends after those first three + * sections for anything below 0x100 — including EEPROM_RFE_OPTION at logical + * 0xCA — and returns a map that is 0xFF almost everywhere. On that adapter it + * made read_efuse_rfe_type() return 0 while the kernel driver reads 0x03 from + * the same chip, i.e. BB/RFE config chosen from an unprogrammed default. */ void HalJaguar3::read_efuse_logical_map(uint8_t *map, size_t len) { constexpr uint16_t kPhysMax = 1024; /* EFUSE_REAL_CONTENT_LEN_8822C */ for (size_t i = 0; i < len; ++i) map[i] = 0xFF;