From 2afd76f1ee6b762ec2e6cc5bbeb85f7ab22a09d7 Mon Sep 17 00:00:00 2001 From: snokvist Date: Thu, 6 Aug 2026 16:30:14 +0200 Subject: [PATCH 1/4] feat: expose the EFUSE MAC as a per-unit adapter identity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. devourer offers no key that can: a USB bus path identifies a port, not a device, and the USB serial descriptor is not unique. That second point is worth stating precisely, because it looks like the obvious answer. Dumped through the vendor kernel driver, both values live in the same EFUSE a few bytes apart: 0x157 the 6-byte MAC per-unit 0x174 USB serial descriptor the constant "123456" on every unit measured The MAC is also where Linux gets it: the vendor driver programs it into the netdev, and udev derives the stable `wlx` name from that. Adds IRtlDevice::GetPermanentMacAddress, 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 (hal_pg.h) and the unprogrammed-value rejection were all already there; only a route to a caller was missing. Jaguar3 — logical offset 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 the health probe's compare surface is unchanged. On 8822C the map is decoded on demand. doctor prints the value, which is also how to check the offset on a chip nobody has measured: compare it against the `wlx` name the vendor driver gives the same dongle. --- examples/doctor/main.cpp | 11 ++++++++ src/IRtlDevice.h | 20 ++++++++++++++ src/jaguar1/RtlJaguarDevice.cpp | 8 ++++++ src/jaguar1/RtlJaguarDevice.h | 4 +++ src/jaguar3/HalJaguar3.cpp | 45 +++++++++++++++++++++++++++++++- src/jaguar3/HalJaguar3.h | 20 ++++++++++++++ src/jaguar3/RtlJaguar3Device.cpp | 4 +++ src/jaguar3/RtlJaguar3Device.h | 3 +++ 8 files changed, 114 insertions(+), 1 deletion(-) diff --git a/examples/doctor/main.cpp b/examples/doctor/main.cpp index b81885f..61748d2 100644 --- a/examples/doctor/main.cpp +++ b/examples/doctor/main.cpp @@ -263,6 +263,17 @@ int main(int argc, char **argv) { /* 2. EFUSE stability */ in.efuse = dev->ProbeEfuseStability(a.reads); + /* 2b. Per-unit identity. Printed here because this is the natural place to + * check it against the netdev name the vendor driver would give the same + * dongle (`wlx`) — a one-line confirmation that the EFUSE offset is + * right on a chip nobody has measured yet. */ + uint8_t mac[6]; + if (dev->GetPermanentMacAddress(mac)) + std::printf("efuse MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], + mac[2], mac[3], mac[4], mac[5]); + else + std::printf("efuse MAC: unavailable (unsupported chip, or unprogrammed)\n"); + /* 4. RX smoke */ if (a.listen_secs > 0 && !g_devourer_should_stop) { std::atomic ok{0}, crc{0}; diff --git a/src/IRtlDevice.h b/src/IRtlDevice.h index c774c4d..a6dded3 100644 --- a/src/IRtlDevice.h +++ b/src/IRtlDevice.h @@ -329,6 +329,26 @@ class IRtlDevice { virtual SelectedChannel GetSelectedChannel() = 0; + /* Per-unit hardware identity: the MAC address burned in the adapter's EFUSE — + * the same value the vendor kernel driver programs into the netdev, and hence + * the thing udev's predictable name `wlx` is derived from. + * + * Why this is on the interface at all: 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. Neither key otherwise + * available can answer it. A USB bus path identifies a *port*, not a device. + * And the USB serial descriptor is not unique: on every RTL88x2 part measured + * it is the constant placeholder "123456", burned into the EFUSE a few bytes + * 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. */ + virtual bool GetPermanentMacAddress(uint8_t /*out*/[6]) { return false; } + /* Read the 64-bit hardware TSF (Timing Synchronization Function) timer — the * 802.11 MAC's free-running microsecond clock (REG_TSFTR). It runs off the * chip's crystal and is latched into every RX descriptor at receive diff --git a/src/jaguar1/RtlJaguarDevice.cpp b/src/jaguar1/RtlJaguarDevice.cpp index 70829fa..e82cf3b 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -385,6 +385,14 @@ void RtlJaguarDevice::measure_idle_noise_floor() { SelectedChannel RtlJaguarDevice::GetSelectedChannel() { return _channel; } +bool RtlJaguarDevice::GetPermanentMacAddress(uint8_t out[6]) { + /* The read, the per-chip offsets and the unprogrammed-value rejection all + * already existed in EepromManager; only the route to a caller was missing. */ + if (out == nullptr || !_eepromManager) + return false; + return _eepromManager->GetMacAddress(out); +} + uint64_t RtlJaguarDevice::ReadTsf() { /* REG_TSFTR (0x0560) = TSF low 32, 0x0564 = TSF high 32. Read hi, lo, hi * again and retry the pair once if the low word wrapped between the reads. */ diff --git a/src/jaguar1/RtlJaguarDevice.h b/src/jaguar1/RtlJaguarDevice.h index f59cbda..062d19d 100644 --- a/src/jaguar1/RtlJaguarDevice.h +++ b/src/jaguar1/RtlJaguarDevice.h @@ -270,6 +270,10 @@ class RtlJaguarDevice : public IRtlDevice { devourer::AmpduMode GetAmpduMode() override { return _ampdu; } devourer::TxStats GetTxStats() override { return _device.GetTxStats(); } SelectedChannel GetSelectedChannel() override; + /* EFUSE MAC via EepromManager (offsets from upstream hal_pg.h: 8812AU 0xD7, + * 8814AU 0xD8, 8821AU 0x107). The EEPROM map is already read during bring-up, + * so this is a lookup, not a chip access. */ + bool GetPermanentMacAddress(uint8_t out[6]) override; uint64_t ReadTsf() override; /* Hardware-timed beacon (IRtlDevice contract): download the beacon MPDU to diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index 0e76fb1..452e6ed 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -1,5 +1,6 @@ #include "HalJaguar3.h" #include +#include #include #include @@ -730,13 +731,55 @@ void HalJaguar3::read_efuse_logical_map(uint8_t *map, size_t len) { } } +/* Unprogrammed EFUSE reads back all-0xFF; an all-zero result means the map was + * never populated. Neither is an identity. (Same rule EepromManager applies on + * Jaguar1.) */ +bool HalJaguar3::mac_programmed(const uint8_t m[6]) { + bool all_ff = true, all_zero = true; + for (int i = 0; i < 6; ++i) { + if (m[i] != 0xFF) all_ff = false; + if (m[i] != 0x00) all_zero = false; + } + return !all_ff && !all_zero; +} + void HalJaguar3::cache_efuse_8822e() { if (_variant != ChipVariant::C8822E) return; - read_efuse_logical_map(_efuse_cache, sizeof(_efuse_cache)); + /* One walk, decoded far enough to reach the MAC, then split: the low 0x100 + * is the existing cache, and the 6 bytes at 0x157 are the per-unit identity. + * Done here rather than on demand because the 8822E OTP is not reliably + * readable after TX/coex bring-up — the same constraint this cache exists + * for. */ + uint8_t map[kMacLogicalOff + 0x10] = {}; + read_efuse_logical_map(map, sizeof(map)); + memcpy(_efuse_cache, map, sizeof(_efuse_cache)); _efuse_cache_valid = true; + memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac)); + _perm_mac_valid = mac_programmed(_perm_mac); _logger->info("Jaguar3(8822e): efuse decoded (0x22={:x} 0x4c={:x} 0xca={:x})", _efuse_cache[0x22], _efuse_cache[0x4c], _efuse_cache[0xca]); + if (_perm_mac_valid) + _logger->info("Jaguar3(8822e): efuse MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + _perm_mac[0], _perm_mac[1], _perm_mac[2], _perm_mac[3], + _perm_mac[4], _perm_mac[5]); +} + +bool HalJaguar3::perm_mac(uint8_t out[6]) { + if (out == nullptr) + return false; + if (!_perm_mac_valid && _variant == ChipVariant::C8822C) { + /* 8822C OTP stays readable post-bring-up (it is why probe_efuse_map is + * 8822C-only), so decode on demand and keep the result. */ + uint8_t map[kMacLogicalOff + 0x10] = {}; + read_efuse_logical_map(map, sizeof(map)); + memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac)); + _perm_mac_valid = mac_programmed(_perm_mac); + } + if (!_perm_mac_valid) + return false; + memcpy(out, _perm_mac, sizeof(_perm_mac)); + return true; } uint8_t HalJaguar3::read_efuse_rfe_type() { diff --git a/src/jaguar3/HalJaguar3.h b/src/jaguar3/HalJaguar3.h index d1fa7a4..0a27e1d 100644 --- a/src/jaguar3/HalJaguar3.h +++ b/src/jaguar3/HalJaguar3.h @@ -79,6 +79,17 @@ class HalJaguar3 { * stability probe there would flag healthy units. Returns false on 8822E. */ bool probe_efuse_map(uint8_t *map, size_t len); + /* Per-unit MAC burned in the EFUSE at logical offset 0x157 on this + * generation — the value the vendor driver programs into the netdev, and the + * only per-unit identifier these parts carry (the USB serial descriptor lives + * a few bytes further along at 0x174 and is the constant "123456"). + * + * On 8822E this is served from the value captured during rtw_hal_init: the + * OTP is not reliably readable after TX/coex bring-up by design, the same + * reason _efuse_cache exists. On 8822C the map is read on demand. + * false when unprogrammed (all-0xFF) or unread (all-0x00). */ + bool perm_mac(uint8_t out[6]); + /* Outcome of the fw download run by the last rtw_hal_init — forwarded from * the DLFW state machine's real hardware boundaries (checksum-ready bits vs * the 0xC078 boot handshake; see HalmacJaguar3Fw::boot_status). */ @@ -209,6 +220,15 @@ class HalJaguar3 { uint8_t _efuse_cache[0x100]; bool _efuse_cache_valid = false; + /* EFUSE MAC (see perm_mac). Logical 0x157 sits past _efuse_cache, so the + * 8822E capture decodes into a larger local buffer and copies both out — + * one OTP walk, and _efuse_cache keeps its size so the health probe's + * compare surface is unchanged. */ + static constexpr uint16_t kMacLogicalOff = 0x0157; + static bool mac_programmed(const uint8_t m[6]); + uint8_t _perm_mac[6] = {}; + bool _perm_mac_valid = false; + RtlAdapter _device; devourer::DeviceConfig _cfg; /* skip_iqk + calibration forward */ Logger_t _logger; diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index e443ff5..7bb0003 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -2081,6 +2081,10 @@ size_t RtlJaguar3Device::build_tx_block(const uint8_t *packet, size_t length, SelectedChannel RtlJaguar3Device::GetSelectedChannel() { return _channel; } +bool RtlJaguar3Device::GetPermanentMacAddress(uint8_t out[6]) { + return _hal.perm_mac(out); +} + uint64_t RtlJaguar3Device::ReadTsf() { /* REG_TSFTR 0x0560 (low) / 0x0564 (high); hi/lo/hi with a wrap retry. Under * _reg_mu (shared with the coex runtime thread). Starved to 0 under a heavy diff --git a/src/jaguar3/RtlJaguar3Device.h b/src/jaguar3/RtlJaguar3Device.h index 71fa82b..34482c5 100644 --- a/src/jaguar3/RtlJaguar3Device.h +++ b/src/jaguar3/RtlJaguar3Device.h @@ -83,6 +83,9 @@ class RtlJaguar3Device : public IRtlDevice { devourer::AmpduMode GetAmpduMode() override { return _ampdu; } devourer::TxStats GetTxStats() override { return _device.GetTxStats(); } SelectedChannel GetSelectedChannel() override; + /* EFUSE MAC at logical 0x157 — captured during rtw_hal_init on 8822E (the + * OTP is not reliably readable later), decoded on demand on 8822C. */ + 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; From 99b6696e8d1a86b22a015e676380ac050fb33444 Mon Sep 17 00:00:00 2001 From: snokvist Date: Thu, 6 Aug 2026 16:48:32 +0200 Subject: [PATCH 2/4] fix: serialize the Jaguar3 MAC read against the coex tick --- src/jaguar3/RtlJaguar3Device.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 7bb0003..60d5db1 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -2082,6 +2082,12 @@ size_t RtlJaguar3Device::build_tx_block(const uint8_t *packet, size_t length, SelectedChannel RtlJaguar3Device::GetSelectedChannel() { return _channel; } bool RtlJaguar3Device::GetPermanentMacAddress(uint8_t out[6]) { + /* Serialize vs the coex tick, like every other entry point that can touch + * the EFUSE or registers: on 8822C perm_mac may run a fresh on-demand map + * decode, which is real register I/O. (On 8822E it is a cached lookup and + * the lock is uncontended.) The lock also makes the lazy fill of + * _perm_mac/_perm_mac_valid single-writer. */ + std::lock_guard lk(_reg_mu); return _hal.perm_mac(out); } From c5c2e9b68e7838d656c5a7dd26316cb7ffed3d2a Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:05:46 +0300 Subject: [PATCH 3/4] review fixes: doctor report placement, one-shot 8822C probe, comment dedup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - doctor: the efuse-MAC line moves into the report block (and is attempted even after a failed bring-up, where it degrades to the unavailable line). - 8822C: a failed on-demand decode is no longer retried on every call — the walk is real register I/O under the device lock and an unprogrammed EFUSE stays unprogrammed, so one attempt is kept, positive or negative. - The identity rationale lives once, on the interface declaration; the HAL comment points there instead of restating it. - The interface doc names Jaguar2/Kestrel as expected follow-ups rather than permanent gaps. Hardware re-verified (doctor, two stable runs each): 8814AU + 8822EU show their programmed MACs in the report; 8822BU and 8822CU show the unavailable line (the 8822C decode gap is issue #385). Co-Authored-By: Claude Opus 4.8 --- examples/doctor/main.cpp | 26 +++++++++++++++----------- src/IRtlDevice.h | 5 ++++- src/jaguar3/HalJaguar3.cpp | 7 +++++-- src/jaguar3/HalJaguar3.h | 11 +++++------ 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/examples/doctor/main.cpp b/examples/doctor/main.cpp index 61748d2..728baca 100644 --- a/examples/doctor/main.cpp +++ b/examples/doctor/main.cpp @@ -263,17 +263,6 @@ int main(int argc, char **argv) { /* 2. EFUSE stability */ in.efuse = dev->ProbeEfuseStability(a.reads); - /* 2b. Per-unit identity. Printed here because this is the natural place to - * check it against the netdev name the vendor driver would give the same - * dongle (`wlx`) — a one-line confirmation that the EFUSE offset is - * right on a chip nobody has measured yet. */ - uint8_t mac[6]; - if (dev->GetPermanentMacAddress(mac)) - std::printf("efuse MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], - mac[2], mac[3], mac[4], mac[5]); - else - std::printf("efuse MAC: unavailable (unsupported chip, or unprogrammed)\n"); - /* 4. RX smoke */ if (a.listen_secs > 0 && !g_devourer_should_stop) { std::atomic ok{0}, crc{0}; @@ -303,6 +292,21 @@ int main(int argc, char **argv) { /* ---- report ---- */ std::printf("\n== adapter doctor ==\n"); std::printf("bring-up: %s\n", yn(in.init_completed)); + { + /* Per-unit identity. In the report so it can be checked against the netdev + * name the vendor driver would give the same dongle (`wlx`) — a + * one-line confirmation that the EFUSE offset is right on a chip nobody + * has measured yet. Attempted even after a failed bring-up: on Jaguar1 the + * EEPROM map may already be in by then, and false degrades to the + * unavailable line either way. */ + uint8_t mac[6]; + if (dev->GetPermanentMacAddress(mac)) + std::printf("efuse MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], + mac[1], mac[2], mac[3], mac[4], mac[5]); + else + std::printf("efuse MAC: unavailable (unsupported chip, or " + "unprogrammed)\n"); + } if (in.efuse.supported) { std::printf("efuse stability: %d reads, %d mismatched, %d bad-id " "(last id 0x%04x%s)\n", diff --git a/src/IRtlDevice.h b/src/IRtlDevice.h index a6dded3..0546ae2 100644 --- a/src/IRtlDevice.h +++ b/src/IRtlDevice.h @@ -346,7 +346,10 @@ class IRtlDevice { * `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. */ + * 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). */ virtual bool GetPermanentMacAddress(uint8_t /*out*/[6]) { return false; } /* Read the 64-bit hardware TSF (Timing Synchronization Function) timer — the diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index 452e6ed..09eb967 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -768,9 +768,12 @@ void HalJaguar3::cache_efuse_8822e() { bool HalJaguar3::perm_mac(uint8_t out[6]) { if (out == nullptr) return false; - if (!_perm_mac_valid && _variant == ChipVariant::C8822C) { + if (!_perm_mac_valid && !_perm_mac_probed && _variant == ChipVariant::C8822C) { /* 8822C OTP stays readable post-bring-up (it is why probe_efuse_map is - * 8822C-only), so decode on demand and keep the result. */ + * 8822C-only), so decode on demand and keep the result — including a + * negative one: the map walk is real register I/O under the device lock, + * and an unprogrammed EFUSE stays unprogrammed, so one attempt is enough. */ + _perm_mac_probed = true; uint8_t map[kMacLogicalOff + 0x10] = {}; read_efuse_logical_map(map, sizeof(map)); memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac)); diff --git a/src/jaguar3/HalJaguar3.h b/src/jaguar3/HalJaguar3.h index 0a27e1d..505a7b2 100644 --- a/src/jaguar3/HalJaguar3.h +++ b/src/jaguar3/HalJaguar3.h @@ -79,15 +79,13 @@ class HalJaguar3 { * stability probe there would flag healthy units. Returns false on 8822E. */ bool probe_efuse_map(uint8_t *map, size_t len); - /* Per-unit MAC burned in the EFUSE at logical offset 0x157 on this - * generation — the value the vendor driver programs into the netdev, and the - * only per-unit identifier these parts carry (the USB serial descriptor lives - * a few bytes further along at 0x174 and is the constant "123456"). + /* Per-unit MAC at logical EFUSE offset 0x157 on this generation (why the MAC + * is the identity key at all: IRtlDevice::GetPermanentMacAddress). * * On 8822E this is served from the value captured during rtw_hal_init: the * OTP is not reliably readable after TX/coex bring-up by design, the same - * reason _efuse_cache exists. On 8822C the map is read on demand. - * false when unprogrammed (all-0xFF) or unread (all-0x00). */ + * reason _efuse_cache exists. On 8822C the map is read on demand, one + * attempt. false when unprogrammed (all-0xFF) or unread (all-0x00). */ bool perm_mac(uint8_t out[6]); /* Outcome of the fw download run by the last rtw_hal_init — forwarded from @@ -228,6 +226,7 @@ class HalJaguar3 { static bool mac_programmed(const uint8_t m[6]); uint8_t _perm_mac[6] = {}; bool _perm_mac_valid = false; + bool _perm_mac_probed = false; /* 8822C on-demand walk: one attempt only */ RtlAdapter _device; devourer::DeviceConfig _cfg; /* skip_iqk + calibration forward */ From bc28f22c8ee79c032d88a5145ff27b2cf43ab55e Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:38:37 +0300 Subject: [PATCH 4/4] Jaguar3 MAC accessor: fold a glitched efuse walk into the false contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 8822C on-demand decode is thousands of control-IN reads, any of which can throw std::ios_base::failure on a USB glitch — most likely exactly when a caller probes identity on a dead or unpowered adapter (doctor does, after a failed bring-up). GetPermanentMacAddress now catches at the device layer and returns the contract's false; the one-attempt latch is already set by then, so a glitched walk is not silently retried either. memcpy sites take the std:: qualification the subtree already uses. Co-Authored-By: Claude Opus 4.8 --- src/jaguar3/HalJaguar3.cpp | 8 ++++---- src/jaguar3/RtlJaguar3Device.cpp | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index 09eb967..66abdca 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -753,9 +753,9 @@ void HalJaguar3::cache_efuse_8822e() { * for. */ uint8_t map[kMacLogicalOff + 0x10] = {}; read_efuse_logical_map(map, sizeof(map)); - memcpy(_efuse_cache, map, sizeof(_efuse_cache)); + std::memcpy(_efuse_cache, map, sizeof(_efuse_cache)); _efuse_cache_valid = true; - memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac)); + std::memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac)); _perm_mac_valid = mac_programmed(_perm_mac); _logger->info("Jaguar3(8822e): efuse decoded (0x22={:x} 0x4c={:x} 0xca={:x})", _efuse_cache[0x22], _efuse_cache[0x4c], _efuse_cache[0xca]); @@ -776,12 +776,12 @@ bool HalJaguar3::perm_mac(uint8_t out[6]) { _perm_mac_probed = true; uint8_t map[kMacLogicalOff + 0x10] = {}; read_efuse_logical_map(map, sizeof(map)); - memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac)); + std::memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac)); _perm_mac_valid = mac_programmed(_perm_mac); } if (!_perm_mac_valid) return false; - memcpy(out, _perm_mac, sizeof(_perm_mac)); + std::memcpy(out, _perm_mac, sizeof(_perm_mac)); return true; } diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 60d5db1..e12fffa 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -2088,7 +2088,18 @@ bool RtlJaguar3Device::GetPermanentMacAddress(uint8_t out[6]) { * the lock is uncontended.) The lock also makes the lazy fill of * _perm_mac/_perm_mac_valid single-writer. */ std::lock_guard lk(_reg_mu); - return _hal.perm_mac(out); + /* The 8822C walk is thousands of control-IN reads, any of which can throw on + * a USB glitch (rtw_read's std::ios_base::failure) — most likely exactly when + * a caller probes identity on a dead or unpowered adapter. The contract folds + * that into its false ("no stable identity available"), it must not escape as + * an exception from an accessor. The one-attempt latch has already been set by + * then, so a glitched walk is not silently retried on the next call either. */ + try { + return _hal.perm_mac(out); + } catch (const std::exception &e) { + _logger->warn("GetPermanentMacAddress: efuse walk failed ({})", e.what()); + return false; + } } uint64_t RtlJaguar3Device::ReadTsf() {