diff --git a/src/IRtlDevice.h b/src/IRtlDevice.h index 0546ae2..54fe965 100644 --- a/src/IRtlDevice.h +++ b/src/IRtlDevice.h @@ -343,13 +343,17 @@ 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 — 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. */ 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;