jaguar3: EFUSE walk assumed ordered sections and quit early (8822C read rfe_type=0) - #384
Conversation
PR Summary by QodoJaguar3: fix EFUSE logical-map walk for unordered sections (8822C RFE type)
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1. Missing lambda capture
|
|
Both review findings addressed. 1. Read past if (a >= kPhysMax)
return 0xFF;
2. Stale Re-verified on hardware after both changes, unchanged from the original results:
|
josephnef
left a comment
There was a problem hiding this comment.
Reviewed with the branch checked out; decode math verified against the dump, callers audited. The fix is correct and the hardware evidence is exactly the right shape — before/after on the affected 8822CU cross-validated against the vendor kernel efuse_map, with the 8822EU as an explicit no-change regression check. CI fully green.
Correctness — verified:
- The decode math checks out against the dump:
hdr=0x0F ext=0x48→ offset0x20→ base0x100;hdr=0x4F ext=0x5D→0x2A→ base0x150. Both match the hand-decode, so the unordered-sections claim is substantiated, not inferred. - Removing
uptoinstead of leaving it unused is the right call — a parameter that looks like a bound but isn't would reintroduce exactly this bug class. - Smaller callers stay safe:
read_efuse_rfe_type's0x140-byte stack map vs the section at base0x150is handled by theidx < lenguard on every data write. - The
kPhysMaxguard inrd()is load-bearing, not belt-and-braces:efuse_OneByteReadmasks to 10 bits, so a truncated straddling section would alias back to phys 0 and decode the start of the EFUSE into a bogus logical base. Previously theuptobreak made that unreachable; with the full walk it matters. - 8822E untouched as claimed: the EU branch
returns before the removed break, and neither of its callers depended onupto.
Performance: the 8822C non-cached paths now walk the full programmed area (per-byte USB control reads), but the walk still stops at the first 0xFF header and both callers are one-shot at bring-up/probe time — no meaningful cost.
One factual error in the shipped comment (inline, worth fixing before merge — it's the kind of number a future debugging session will trust) plus two comment-style nits inline.
- 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) <noreply@anthropic.com>
|
Thanks for the review — all three comments are addressed in 38790b1 (comment-only; build clean, 49/49 ctest):
Independent cross-check against the vendor parserSince the argument for removing Two things fall out of that:
It also answers the "previously-read bytes moving" half of #385: the vendor writes #385's validation asks"check that The RFE blast radius, measured offline. I walked all five 8822C tables twice through
Identical counts and identical address sets everywhere — no row appears or disappears. The entire delta is 7 RF register values: Cut-independent (identical for On-air A/B — I don't have an SDR here, so I can't produce the per-rate measurement you asked for. What I can run on the affected 8822CU is a link-level A/B: fixed TX power and channel, N frames per MCS, before/after, comparing per-MCS PDR and receiver RSSI. Say the word and I'll post it. Worth noting the direction of travel independently of any measurement: Interaction with #383They conflict, in both directions — GitHub reports each as mergeable against master because it only tests them pairwise with the base. #383 calls I merged both onto master locally and resolved it that way: build clean, 49/49. Merge them in whatever order suits you and I'll push the fixup to the trailing one immediately — or say the word and I'll rebase #383 onto this branch so it lands conflict-free. |
josephnef
left a comment
There was a problem hiding this comment.
Approving with the on-air arm #385 asked for, run on this rig's affected RTL8822CU (the second of the 2/2 units that reproduce the truncated walk).
On-air TX A/B (master vs this fix, txdemo MCS7/20 flood, SDR duty)
| band | master (rfe_type=0) |
fixed (rfe_type=0x03) |
|---|---|---|
| ch36 (5 GHz) | 49.2% duty (~32.0 Mbps on-air) | 48.7% (~31.7 Mbps) — tie |
| ch6 (2.4 GHz) | 78.2–81.8% over 4 interleaved reps | 92.5–94.2% |
The 2.4 GHz gap is reproducible (interleaved A/B ×3, per-arm sd < 1 point, far above this rig's ±3-point single-probe floor): the correct front-end rows are worth ~+12 points of duty at MCS7/ch6 on this unit — master was flying the unprogrammed-default tables. 5 GHz is unchanged.
RX + identity regression checks
- RX smoke A/B on the same unit, ch6 ambient: 345 vs 408 clean frames / 8 s, 0 corrupt both arms, HEALTHY verdicts — the table change does not deafen RX.
- 8822CU efuse: stability probe id
0x8129valid,rfe_type=0x03(kernel agrees), and the #383 MAC decodes toa8:b5:8e:6a:94:ea— the exact value the #385 worktree experiment predicted on this unit. - 8822EU (
98:03:cf:cf:a4:49), 8814AU (20:0d:b0:c7:e4:b3): unchanged, stable across runs.
The kPhysMax guard against the 10-bit address aliasing is a good catch — a truncated tail section would otherwise silently decode the start of the EFUSE into a wrong logical base.
CI note: the pushes landed during today's GitHub Actions incident and the pull_request events were dropped (no runs existed for either head SHA); both PRs were close/reopened to re-trigger. Merge order: this one first, then #383 rides clean.
|
Code review by qodo was updated up to the latest commit 38790b1 |
## The problem
A consumer that keeps **per-adapter state** — a measured TX-power curve,
a
calibration, anything tied to one specific dongle — has to answer *"is
this the
same physical adapter I measured last time?"* across a re-plug, a
reboot, and a
port change. Applying one unit's measurements to another silently is the
failure
to avoid.
devourer currently exposes no key that can answer it:
- **USB bus path** (`1-1`, `3-1.2`) identifies a *port*, not a device.
It changes
whenever the dongle moves, orphaning the state.
- **USB serial descriptor** is not unique — see below.
## The serial is a burned-in constant; the MAC beside it is per-unit
Both live in the same EFUSE, a few bytes apart. Dumped through the
vendor kernel
driver (`/proc/net/rtl88x2{eu,cu}/<iface>/efuse_map`) on two adapters:
RTL8822EU — netdev `wlx84fc1450bcde`:
```
0x150 DA 0B 1A A8 FF 7E 02 84 FC 14 50 BC DE 09 03 52
0x170 20 4E 49 43 08 03 31 32 33 34 35 36 FF FF FF FF
```
RTL8822CU — netdev `wlx40a5ef2f2308`:
```
0x150 DA 0B 12 C8 FF 7E 02 40 A5 EF 2F 23 08 09 03 52
0x170 20 4E 49 43 08 03 31 32 33 34 35 36 FF FF FF FF
```
| logical offset | contents | per-unit? |
|---|---|---|
| **0x157** | the 6-byte MAC | **yes** |
| 0x15D / 0x166 | `"Realtek"` / `"802.11ac NIC"` string descriptors | no
|
| 0x174 | USB **serial** string descriptor — `"123456"` | **no,
identical on both** |
So the serial is not *missing*, it is a placeholder burned identically
into every
unit. Keying on it would be worse than the bus path: two adapters in one
host
would share state and silently apply each other's measurements.
The MAC is also exactly where Linux gets it — the vendor driver programs
it into
the netdev, and udev derives the stable `wlx<mac>` name from that. That
name is
stable across re-plug *because* it comes from the chip.
## What this adds
`IRtlDevice::GetPermanentMacAddress(uint8_t out[6])`, defaulting to
`false` so
unimplemented chips degrade gracefully and no existing consumer changes
behaviour.
- **Jaguar1** — routes to the existing `EepromManager::GetMacAddress`.
The read,
the per-chip offsets (from `hal_pg.h`: 8812AU `0xD7`, 8814AU `0xD8`,
8821AU
`0x107`) and the unprogrammed-value rejection were all already
implemented;
only a route to a caller was missing.
- **Jaguar3** — logical `0x157`. On **8822E** the value is captured
during the
existing `rtw_hal_init` efuse pass, because that OTP is not reliably
readable
after TX/coex bring-up — the same constraint `_efuse_cache` exists for.
One
walk decodes far enough for both, and `_efuse_cache` keeps its size so
`probe_efuse_map`'s compare surface is unchanged. On **8822C** the map
is
decoded on demand.
- **doctor** prints it, which is also how to check the offset on a chip
nobody
has measured: compare against the `wlx<mac>` name the vendor driver
gives the
same dongle.
Jaguar2 and Kestrel keep the default `false`. Kestrel's offset constant
already
exists in-tree (`EFUSE_USB_MAC_ADDR_8852B = 0x488`,
`kestrel/MacRegAx.h:179`) if
someone with the hardware wants to finish it.
## Hardware verification
Three adapters, all three implemented code paths, `doctor` output vs the
kernel's
own netdev MAC:
| adapter | path exercised | kernel | devourer | |
|---|---|---|---|---|
| RTL8812AU (Jaguar1) | `EepromManager`, offset 0xD7 |
`20:0d:b0:c4:a7:6a` | `20:0d:b0:c4:a7:6a` | **match** |
| RTL8822EU (Jaguar3, C8822E) | captured in `rtw_hal_init` |
`84:fc:14:50:bc:de` | `84:fc:14:50:bc:de` | **match** |
| RTL8822CU (Jaguar3, C8822C) | on-demand decode | `40:a5:ef:2f:23:08` |
`40:a5:ef:2f:23:08` | **match** (needs #384) |
**The 8822C row needed a separate fix, and it is not in this PR.** On
that unit the
shared non-EU walk in `read_efuse_logical_map` returned an empty logical
map, so
the MAC read found `0xFF` — independently visible in the same run as
`Jaguar3: rfe_type=0x00` while the kernel reads logical `0xCA = 0x03`
from the same
adapter. Root cause is now known and fixed in **#384** (tracked as
**#385**): the
walk stopped as soon as a section's base passed the requested byte,
which assumes
sections are burned in ascending base order — they are append-ordered.
My original
guess in this PR (the `if (hdr == 0xFF) break;` padding termination) was
**wrong**;
@josephnef's control experiment on a second 8822CU showed EU-style
0xFF-run
tolerance alone changes nothing.
The walk fix stays out of this PR deliberately: it feeds RFE and
per-channel
TX-power base for every 8822C user and deserves its own validation
rather than
riding along with a new accessor. The two changes are independent — this
one has no
unverified path once #384 lands.
## Caveats worth stating
- ~~`0x157` is **measured**, not read from a datasheet.~~ **Resolved** —
it is the
vendor constant. `include/hal_pg.h` (rtl88x2cu 20230728 / rtl88x2eu
20230815):
`EEPROM_MAC_ADDR_8822CU 0x157`, `EEPROM_MAC_ADDR_8822EU 0x157`. The
caveat's worry
was right in kind — the other variants do differ (`…CS/ES 0x16A`,
`…CE/EE 0x120`)
— but devourer is USB-only, so `0x157` is correct for both Jaguar3
parts. The
Jaguar1 offsets served by `EepromManager` check out against the same
header
(8812AU `0xD7`, 8814AU `0xD8`, 8821AU `0x107`), and the Jaguar2
follow-up already
has its constant there too (8822BU and 8821CU are both `0x107`).
- A MAC is a hardware identity rather than a secret, but it is a stable
device
identifier; consumers logging it should treat it as they treat any other
adapter identity.
Build is clean; no new warnings.
---------
Co-authored-by: Joseph <162703152+josephnef@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The bug
HalJaguar3::read_efuse_logical_mapstopped walking as soon as a section'slogical base passed the byte the caller asked for:
That is only valid if sections appear in ascending base order. They do not.
Physical EFUSE dumped off an RTL8822CU (
0bda:c812), decoded by hand:The third section on the chip jumps to base 0x100, so any request below that
— including
EEPROM_RFE_OPTION_8822Cat logical 0xCA, which is the whole reasonread_efuse_rfe_type()calls this — ended the walk after three sections andreturned a map that was 0xFF almost everywhere.
Why it matters
On the affected adapter
read_efuse_rfe_type()returned 0, while the vendorkernel driver reads 0x03 from the same chip
(
/proc/net/rtl88x2cu/<iface>/efuse_map, logical 0xCA). The RFE type gates BB /RFE configuration, so those units were being brought up against an unprogrammed
default rather than their actual front-end.
It is silent: nothing errors, the map just reads unprogrammed.
The fix
Walk the whole programmed area (the existing 0xFF-header terminator and
kPhysMaxbound already stop it). Theuptoparameter is removed rather thanleft unused — a parameter that still looks like it bounds the walk is how this
comes back.
The 8822E branch is untouched: it never used
upto, terminating on a long 0xFFrun instead, which is why only the C path was affected.
Hardware verification
efuse_map0xCA)0bda:c812, C8822C)rfe_type=0x00rfe_type=0x030bda:a81a, C8822E)rfe_type=0x15rfe_type=0x15The EU is the regression check — unchanged, and its
efuse decoded (0x22=46 0x4c=51 0xca=15)line is identical before and after. The 8822C EFUSE stabilityprobe also now reports a valid
0x8129EEPROM ID.Found while implementing #383 (EFUSE MAC as a per-unit identity), which could not
read the MAC on 8822C for this reason. With this fix that adapter's MAC decodes
correctly —
40:a5:ef:2f:23:08, matching its netdev exactly. The two changes areindependent; this one stands on its own regardless of what happens to #383.