Skip to content

GetPermanentMacAddress on Jaguar2 and Kestrel: per-unit identity on every generation - #386

Merged
josephnef merged 2 commits into
masterfrom
feat/perm-mac-jaguar2-kestrel
Aug 7, 2026
Merged

GetPermanentMacAddress on Jaguar2 and Kestrel: per-unit identity on every generation#386
josephnef merged 2 commits into
masterfrom
feat/perm-mac-jaguar2-kestrel

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

What this adds

#383 shipped IRtlDevice::GetPermanentMacAddress on Jaguar1 and Jaguar3 and left Jaguar2 and Kestrel on the graceful default. This wires the remaining two generations — the identity is now every-generation.

Jaguar2 — logical EFUSE 0x107, one offset for both dies (hal_pg.h: EEPROM_MAC_ADDR_8822BU == EEPROM_MAC_ADDR_8821CU). Served from the logical map HalJaguar2 already caches for RFE/TX-power, so post-bring-up it is a lookup; a pre-init call triggers the existing lazy walk. The device entry point serializes on _reg_mu and folds a USB-glitch throw into the contract's false, matching the Jaguar3 shape.

Kestrel — a route, not a new read: the bring-up efuse parse already extracts the MAC at logical 0x488 and autoload_ok is exactly the programmed-value check. One constant serves both dies by vendor dispatch: mac_ax's USB efuse-info table has no 8852C entry and falls back to the 8852B offsets (reference/rtl8852cu mac_ax/efuse.c, the else info = efuse_info_usb_8852b arm).

The IRtlDevice.h doc drops the "expected follow-ups" paragraph — current state only; the default stays false so a future generation degrades gracefully.

Hardware verification

Two stable doctor runs per adapter; the Jaguar2 values are cross-checked against the vendor kernel driver built from reference/rtl88x2bu on the same host:

adapter path devourer vendor driver
RTL8822BU Jaguar2, 0x107 40:a5:ef:57:37:0c match
Archer T3U (8822BU) Jaguar2, 0x107 8c:86:dd:48:00:9d match
TP-Link TX50UH (8852C) Kestrel, 0x488 cc:ba:bd:61:57:6b see below
RTL8814AU / RTL8822CU regression unchanged

The TX50UH has no same-host vendor-driver run; its offset stands on the vendor-source dispatch above plus the fact that the same parse already feeds the on-air-working rfe/xtal/thermal fields. Its USB iSerial is the Realtek placeholder 00e04c000001 — the constant-serial premise that motivated #383, re-confirmed on AX silicon.

The 8821C variant (RTL8811CU/8821CU/8821CE) shares the Jaguar2 path and constant but no unit was on the rig for this run.

Build clean, 49/49 ctest.

🤖 Generated with Claude Code

…ery-generation

Jaguar2: logical EFUSE 0x107 — one offset for both dies (hal_pg.h:
EEPROM_MAC_ADDR_8822BU == EEPROM_MAC_ADDR_8821CU) — served from the logical
map HalJaguar2 already caches for RFE/TX-power, so post-bring-up it is a
lookup. The device entry point serializes on _reg_mu and folds a USB-glitch
throw from the lazy pre-init walk into the contract's false, like Jaguar3.

Kestrel: the bring-up efuse parse already extracts the MAC at logical 0x488
and gates autoload_ok on exactly the programmed-value check; this is a route
to a caller, not a new read. One constant serves both dies by vendor
dispatch: mac_ax's USB efuse-info table has no 8852C entry and falls back
to the 8852B offsets (reference/rtl8852cu mac_ax/efuse.c).

Hardware verification, two stable doctor runs each, cross-checked against
the vendor kernel driver built from reference/rtl88x2bu on the same host:
- RTL8822BU  40:a5:ef:57:37:0c — matches the vendor driver's netdev MAC
- Archer T3U 8c:86:dd:48:00:9d — matches the vendor driver's netdev MAC
- TX50UH (8852C) cc:ba:bd:61:57:6b — stable + programmed; no same-host
  vendor-driver run for this unit, so its offset stands on the vendor-source
  dispatch above and on the same parse already feeding the on-air-working
  rfe/xtal/thermal fields. Its USB iSerial is the Realtek placeholder
  00e04c000001 — the constant-serial premise, re-confirmed on AX silicon.
- 8814AU / 8822CU regression: unchanged values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Implement permanent MAC identity for Jaguar2 and Kestrel devices

✨ Enhancement 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Implement per-unit permanent MAC retrieval for Jaguar2 via cached EFUSE logical map
• Expose Kestrel permanent MAC by returning the bring-up parsed EFUSE MAC when valid
• Update IRtlDevice contract docs to reflect every-generation support and graceful fallback
Diagram

graph TD
  IR[["IRtlDevice"]] --> J2D["RtlJaguar2Device"] --> J2H["HalJaguar2"] --> J2M[("Jaguar2 EFUSE map")]
  IR --> KD["RtlKestrelDevice"] --> KC[("Kestrel EfuseInfo")]
  KH["HalKestrel"] --> KC
  subgraph Legend
    direction LR
    _if[["Interface"]] ~~~ _cls["Device/HAL"] ~~~ _ds[("Cached data")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Always perform a fresh physical EFUSE read in GetPermanentMacAddress()
  • ➕ Does not depend on bring-up timing or cached state
  • ➕ Avoids returning stale data if cache is corrupted
  • ➖ High latency and heavy USB control-IN traffic (especially on generations with large walks)
  • ➖ More failure-prone; increases likelihood of exceptions/USB glitches during simple accessors
  • ➖ Duplicates logic already centralized in HAL bring-up / efuse parsing
2. Centralize MAC validation (all-FF/all-00) in a shared helper
  • ➕ Eliminates repeated per-generation validation patterns
  • ➕ Makes contract semantics harder to accidentally diverge across generations
  • ➖ Small incremental benefit; current duplication is minimal and readable
  • ➖ May force awkward dependencies between generations/HALs if over-generalized

Recommendation: Current approach is the best tradeoff: Jaguar2 reuses the HAL’s existing lazy logical-map cache (so post-bring-up is a simple lookup), and Kestrel reuses the bring-up EFUSE parse (no new read path). Folding EFUSE-walk exceptions into false at the device entry point keeps the accessor contract consistent with Jaguar3 and avoids propagating USB-glitch failures to callers.

Files changed (5) +58 / -7

Enhancement (4) +52 / -0
HalJaguar2.cppAdd Jaguar2 perm_mac() reading EFUSE logical offset 0x107 +16/-0

Add Jaguar2 perm_mac() reading EFUSE logical offset 0x107

• Implements HalJaguar2::perm_mac() by reading six bytes from the lazy-cached logical EFUSE map at offset 0x107. Validates programmed-ness by rejecting all-0xFF (unprogrammed) and all-0x00 (unread/uninitialized) values.

src/jaguar2/HalJaguar2.cpp

HalJaguar2.hDeclare Jaguar2 perm_mac() API with offset/validity semantics +8/-0

Declare Jaguar2 perm_mac() API with offset/validity semantics

• Adds HalJaguar2::perm_mac() declaration and documents the shared-die offset (0x107) plus validity rules. Notes behavior differences between post-bring-up lookup vs pre-init lazy physical walk.

src/jaguar2/HalJaguar2.h

RtlJaguar2Device.cppImplement IRtlDevice::GetPermanentMacAddress on Jaguar2 with locking and exception folding +15/-0

Implement IRtlDevice::GetPermanentMacAddress on Jaguar2 with locking and exception folding

• Adds RtlJaguar2Device::GetPermanentMacAddress() that serializes under _reg_mu and delegates to _hal.perm_mac(). Catches EFUSE-walk/register I/O exceptions (e.g., USB glitches) and converts them to 'false' while logging a warning.

src/jaguar2/RtlJaguar2Device.cpp

RtlKestrelDevice.hExpose Kestrel permanent MAC from bring-up parsed EfuseInfo +13/-0

Expose Kestrel permanent MAC from bring-up parsed EfuseInfo

• Implements GetPermanentMacAddress inline by returning the MAC already parsed into _efuse during bring-up when _efuse.autoload_ok is true, otherwise returns false. Adds <cstring> include for memcpy.

src/kestrel/RtlKestrelDevice.h

Documentation (1) +6 / -7
IRtlDevice.hUpdate permanent MAC contract docs to reflect full generation coverage +6/-7

Update permanent MAC contract docs to reflect full generation coverage

• Rewords GetPermanentMacAddress documentation to remove the prior “expected follow-ups” note. Clarifies that all generations now implement per-chip offsets/parsing while the interface default remains 'false' for graceful future degradation.

src/IRtlDevice.h

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Kestrel MAC needs Init 🐞 Bug ⚙ Maintainability
Description
RtlKestrelDevice::GetPermanentMacAddress returns false until Init/InitWrite has populated
_efuse.autoload_ok, even if the EFUSE MAC is programmed. This conflicts with the updated
IRtlDevice documentation that describes false only as “unprogrammed/unreadable”, so pre-init
callers may incorrectly treat a valid device as having no stable identity.
Code

src/kestrel/RtlKestrelDevice.h[R88-91]

+  bool GetPermanentMacAddress(uint8_t out[6]) override {
+    if (out == nullptr || !_efuse.autoload_ok)
+      return false;
+    std::memcpy(out, _efuse.mac.data(), _efuse.mac.size());
Evidence
The Kestrel override gates success on _efuse.autoload_ok, but _efuse.autoload_ok is only set
during the EFUSE parse executed as part of bring-up; before that _efuse is default-initialized
with autoload_ok=false. Meanwhile, the updated interface documentation describes false primarily
as an EFUSE content/readability outcome, not as an initialization-state outcome.

src/kestrel/RtlKestrelDevice.h[83-94]
src/kestrel/RtlKestrelDevice.h[225-233]
src/kestrel/RtlKestrelDevice.cpp[170-192]
src/kestrel/HalKestrel.cpp[2303-2363]
src/IRtlDevice.h[332-352]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`RtlKestrelDevice::GetPermanentMacAddress()` currently returns `false` before `Init/InitWrite` because `_efuse.autoload_ok` is only set by the bring-up EFUSE parse, but `IRtlDevice::GetPermanentMacAddress` is now documented as if `false` only means “unprogrammed/unreadable EFUSE”. This makes the interface contract ambiguous/inaccurate for Kestrel.

### Issue Context
- Kestrel caches EFUSE info during bring-up (`BringUpMonitor` -> `PowerOnFwAndTrx(_efuse)` -> `HalKestrel::read_efuse`), so `_efuse.autoload_ok` is `false` until bring-up runs.
- Other generations (e.g., Jaguar3) allow a pre-init call by performing a lazy EFUSE walk under a mutex.

### Fix Focus Areas
Choose one consistent contract and implement it:
- **Option A (doc/contract fix):** Update `IRtlDevice.h` comment to explicitly allow `false` for “not initialized yet / unsupported generation” (not only unprogrammed/unreadable).
- **Option B (behavior fix):** Make Kestrel lazily read/cache the EFUSE MAC on first call (likely by powering on + reading efuse) and ensure it is serialized appropriately with other chip I/O.

#### Target locations
- src/kestrel/RtlKestrelDevice.h[83-94]
- src/IRtlDevice.h[332-352]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/kestrel/RtlKestrelDevice.h
…yet"

True on every generation — the EFUSE is only guaranteed readable on a
brought-up chip, and the accessor never powers the chip on as a side
effect. Kestrel serves the bring-up parse; the others' pre-init read is
best-effort and degrades to false on an unpowered adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef
josephnef merged commit 5a5dd62 into master Aug 7, 2026
22 checks passed
@josephnef
josephnef deleted the feat/perm-mac-jaguar2-kestrel branch August 7, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant