Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions examples/doctor/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,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<mac>`) — 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");
Comment thread
josephnef marked this conversation as resolved.
}
if (in.efuse.supported) {
std::printf("efuse stability: %d reads, %d mismatched, %d bad-id "
"(last id 0x%04x%s)\n",
Expand Down
23 changes: 23 additions & 0 deletions src/IRtlDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,29 @@ 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<mac>` 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. 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
* 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
Expand Down
8 changes: 8 additions & 0 deletions src/jaguar1/RtlJaguarDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
4 changes: 4 additions & 0 deletions src/jaguar1/RtlJaguarDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 47 additions & 1 deletion src/jaguar3/HalJaguar3.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "HalJaguar3.h"
#include <cstdlib>
#include <cstring>

#include <chrono>
#include <stdexcept>
Expand Down Expand Up @@ -730,13 +731,58 @@ 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));
std::memcpy(_efuse_cache, map, sizeof(_efuse_cache));
_efuse_cache_valid = true;
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]);
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 && !_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 — 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));
std::memcpy(_perm_mac, map + kMacLogicalOff, sizeof(_perm_mac));
_perm_mac_valid = mac_programmed(_perm_mac);
}
if (!_perm_mac_valid)
return false;
std::memcpy(out, _perm_mac, sizeof(_perm_mac));
return true;
}

uint8_t HalJaguar3::read_efuse_rfe_type() {
Expand Down
19 changes: 19 additions & 0 deletions src/jaguar3/HalJaguar3.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ 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 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, 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
* the DLFW state machine's real hardware boundaries (checksum-ready bits vs
* the 0xC078 boot handshake; see HalmacJaguar3Fw::boot_status). */
Expand Down Expand Up @@ -209,6 +218,16 @@ 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;
bool _perm_mac_probed = false; /* 8822C on-demand walk: one attempt only */

RtlAdapter _device;
devourer::DeviceConfig _cfg; /* skip_iqk + calibration forward */
Logger_t _logger;
Expand Down
21 changes: 21 additions & 0 deletions src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,27 @@ 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<std::mutex> lk(_reg_mu);
/* 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;
}
}
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

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
Expand Down
3 changes: 3 additions & 0 deletions src/jaguar3/RtlJaguar3Device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading