From d445a1e21fbe98adf9c71809ef3b5a8ff659376b Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Fri, 7 Aug 2026 08:03:31 +0300 Subject: [PATCH 1/2] GetPermanentMacAddress on Jaguar2 and Kestrel: the identity is now every-generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/IRtlDevice.h | 13 ++++++------- src/jaguar2/HalJaguar2.cpp | 16 ++++++++++++++++ src/jaguar2/HalJaguar2.h | 8 ++++++++ src/jaguar2/RtlJaguar2Device.cpp | 15 +++++++++++++++ src/jaguar2/RtlJaguar2Device.h | 2 ++ src/kestrel/RtlKestrelDevice.h | 13 +++++++++++++ 6 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/IRtlDevice.h b/src/IRtlDevice.h index 0546ae2..af515b8 100644 --- a/src/IRtlDevice.h +++ b/src/IRtlDevice.h @@ -343,13 +343,12 @@ class IRtlDevice { * from the MAC itself. Keying on it is worse than useless — two adapters in * one host would share state and silently apply each other's measurements. * - * `out` receives the 6 bytes in wire order. Returns false where the chip is - * unsupported (the default), or the EFUSE value is unprogrammed/unreadable — - * callers must treat false as "no stable identity available" rather than - * substituting a weaker one silently. The default is a defined answer, not a - * silent no-op: Jaguar2 and Kestrel are expected follow-ups, not permanent - * gaps (HalMAC has the efuse APIs; EFUSE_USB_MAC_ADDR_8852B is already in - * kestrel/MacRegAx.h — each just needs a hardware-verified route here). */ + * `out` receives the 6 bytes in wire order. Returns false where the EFUSE + * value is unprogrammed/unreadable — callers must treat false as "no stable + * identity available" rather than substituting a weaker one silently. + * Implemented on every generation (per-chip offsets at each HAL's perm_mac / + * efuse parse); the interface default stays false so a future generation + * degrades gracefully rather than fabricating an identity. */ virtual bool GetPermanentMacAddress(uint8_t /*out*/[6]) { return false; } /* Read the 64-bit hardware TSF (Timing Synchronization Function) timer — the diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index 022fbd6..ddf3c63 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -348,6 +348,22 @@ uint8_t HalJaguar2::efuse_logical_byte(uint16_t off) { return off < sizeof(_efuse_map) ? _efuse_map[off] : 0xFF; } +bool HalJaguar2::perm_mac(uint8_t out[6]) { + constexpr uint16_t kMacOff = 0x107; /* EEPROM_MAC_ADDR_8822BU == _8821CU */ + if (out == nullptr) + return false; + for (int i = 0; i < 6; ++i) + out[i] = efuse_logical_byte(kMacOff + i); + /* Unprogrammed EFUSE reads back all-0xFF; all-zero means the map was never + * read. Neither is an identity (same rule as the other generations). */ + 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; +} + uint8_t HalJaguar2::read_efuse_rfe() { constexpr uint16_t kRfeOff = 0xCA; /* EEPROM_RFE_OPTION_8822B */ read_efuse_logical_map(_efuse_map, sizeof(_efuse_map), diff --git a/src/jaguar2/HalJaguar2.h b/src/jaguar2/HalJaguar2.h index b60be2b..f090f9e 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -59,6 +59,14 @@ class HalJaguar2 { * default (logical 0xB9). */ uint8_t efuse_logical_byte(uint16_t off); + /* Per-unit MAC at logical EFUSE offset 0x107 — the same offset on both dies + * (hal_pg.h: EEPROM_MAC_ADDR_8822BU == EEPROM_MAC_ADDR_8821CU; why the MAC + * is the identity key at all: IRtlDevice::GetPermanentMacAddress). Served + * from the cached logical map — a lookup post-bring-up, a real physical walk + * on a pre-init call. false when unprogrammed (all-0xFF) or unread + * (all-0x00). */ + bool perm_mac(uint8_t out[6]); + /* Program the per-rate TXAGC (0x1d00 path A / 0x1d80 path B) from the EFUSE * power-by-rate calibration for `channel` at bandwidth `bw` (0=20/1=40/2=80; * 5/6 = 5/10 MHz narrowband, folded to the 20 MHz column — the RF runs in diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 25706be..1dd43ea 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -1601,6 +1601,21 @@ size_t RtlJaguar2Device::build_tx_block(const uint8_t *packet, size_t length, SelectedChannel RtlJaguar2Device::GetSelectedChannel() { return _channel; } +bool RtlJaguar2Device::GetPermanentMacAddress(uint8_t out[6]) { + /* Under _reg_mu like every register-touching entry point: a pre-init call + * triggers the HAL's lazy logical-map walk, which is real register I/O. Any + * of those control-INs can throw on a USB glitch (rtw_read's + * std::ios_base::failure) — the contract folds that into false ("no stable + * identity available"), it must not escape from an accessor. */ + std::lock_guard lk(_reg_mu); + try { + return _hal.perm_mac(out); + } catch (const std::exception &e) { + _logger->warn("GetPermanentMacAddress: efuse walk failed ({})", e.what()); + return false; + } +} + bool RtlJaguar2Device::StartBeacon(const uint8_t *beacon, size_t len, int interval_tu) { std::lock_guard lk(_reg_mu); diff --git a/src/jaguar2/RtlJaguar2Device.h b/src/jaguar2/RtlJaguar2Device.h index 436f93a..f67562c 100644 --- a/src/jaguar2/RtlJaguar2Device.h +++ b/src/jaguar2/RtlJaguar2Device.h @@ -83,6 +83,8 @@ class RtlJaguar2Device : public IRtlDevice { devourer::AmpduMode GetAmpduMode() override { return _ampdu; } devourer::TxStats GetTxStats() override { return _device.GetTxStats(); } SelectedChannel GetSelectedChannel() override; + /* EFUSE MAC at logical 0x107 (both dies — see HalJaguar2::perm_mac). */ + bool GetPermanentMacAddress(uint8_t out[6]) override; uint64_t ReadTsf() override; void WriteTsf(uint64_t tsf) override; bool StartBeacon(const uint8_t *beacon, size_t len, int interval_tu) override; diff --git a/src/kestrel/RtlKestrelDevice.h b/src/kestrel/RtlKestrelDevice.h index 536301b..513c815 100644 --- a/src/kestrel/RtlKestrelDevice.h +++ b/src/kestrel/RtlKestrelDevice.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -79,6 +80,18 @@ class RtlKestrelDevice : public IRtlDevice { devourer::TxStats GetTxStats() override { return _device.GetTxStats(); } SelectedChannel GetSelectedChannel() override { return _channel; } + /* EFUSE MAC at logical 0x488 on both dies: mac_ax's USB efuse-info dispatch + * has no 8852C entry and falls back to the 8852B table (efuse.c + * offset_usb_8852b), so one constant serves both. Served from the bring-up + * parse — false before Init/InitWrite, or when the EFUSE is unprogrammed + * (autoload_ok is exactly the all-FF / all-00 MAC check). */ + bool GetPermanentMacAddress(uint8_t out[6]) override { + if (out == nullptr || !_efuse.autoload_ok) + return false; + std::memcpy(out, _efuse.mac.data(), _efuse.mac.size()); + return true; + } + /* Static capability aggregate (resolved from chip identity; thread-safe, * callable pre-Init). The demos emit it as the adapter.caps JSONL event. */ devourer::TxCaps GetTxCaps() override; From b44613d81ad0ddc8929e82ee89cb0fbfb0a20891 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Fri, 7 Aug 2026 08:15:20 +0300 Subject: [PATCH 2/2] GetPermanentMacAddress contract: false may also mean "not brought up yet" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/IRtlDevice.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/IRtlDevice.h b/src/IRtlDevice.h index af515b8..54fe965 100644 --- a/src/IRtlDevice.h +++ b/src/IRtlDevice.h @@ -344,8 +344,13 @@ class IRtlDevice { * one host would share state and silently apply each other's measurements. * * `out` receives the 6 bytes in wire order. Returns false where the EFUSE - * value is unprogrammed/unreadable — callers must treat false as "no stable - * identity available" rather than substituting a weaker one silently. + * value is unprogrammed/unreadable — and, on any generation, possibly before + * Init/InitWrite: the EFUSE is only guaranteed readable on a brought-up chip, + * and this accessor never powers the chip on as a side effect (Kestrel serves + * the bring-up parse; the others fall back to a best-effort pre-init read + * that degrades to false on an unpowered adapter). Callers must treat false + * as "no stable identity available" rather than substituting a weaker one + * silently; for a guaranteed answer on a programmed unit, ask after bring-up. * Implemented on every generation (per-chip offsets at each HAL's perm_mac / * efuse parse); the interface default stays false so a future generation * degrades gracefully rather than fabricating an identity. */