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
36 changes: 34 additions & 2 deletions tests/canary_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@

Several registers shift on every capture for reasons that aren't
init drift — they're runtime state the kernel or devourer keep
updating after init completes. Listing them as divergences would
drown out real bugs. The mask:
updating after init completes, or calibration outputs that vary
between runs because the calibration samples a noisy signal.
Listing them as divergences would drown out real bugs. The mask:

- MAC 0x040, 0x550, 0x560: per-queue / beacon-window / TBTT
counters that increment continuously.
Expand All @@ -40,6 +41,17 @@
(and the kernel's phydm watchdog) based on the thermal-meter
sample. Same drift class as RF[A] 0x42. Other bits of 0xc1c
(AGC table select [11:8], static base bits) ARE checked.
- BB 0xc50, 0xe50, 0x1850, 0x1a50 bits 7:0: DIG IGI (path
A/B/C/D Initial Gain). The kernel's phydm DIG watchdog walks
the IGI value up and down each interrupt cycle based on
received noise floor; devourer writes the 0x1c floor once at
init and leaves it. Upper bits of the AGC core word are
static config and ARE checked.
- BB 0x8b0, 0xc10, 0xc14, 0xc90, 0xc94 (and 0xe10/0xe14/0xe90/0xe94
path-B mirrors): IQK output coefficients. Both sides run IQK,
but the tone sweep samples noise so the per-bit output varies
between runs even on the same chip. Functional IQK correctness
is validated by the on-air RX/TX matrix, not by canary diff.

There's also a known capture-state asymmetry: the kernel iface is
long-lived (CCK regs at 5G retain values written during prior 2.4G
Expand Down Expand Up @@ -73,6 +85,26 @@
# tx_scaling_table_jaguar index) are thermal-tracked.
("BB", 0xc1c): 0xFFE00000,
("BB", 0xe1c): 0xFFE00000,
# DIG IGI (bits 7:0) — kernel's phydm DIG watchdog continuously
# walks the path-IGI based on RX noise floor; devourer writes
# the 0x1c floor once and doesn't update.
("BB", 0xc50): 0x000000FF, # path A
("BB", 0xe50): 0x000000FF, # path B
("BB", 0x1850): 0x000000FF, # path C (8814 only)
("BB", 0x1a50): 0x000000FF, # path D (8814 only)
# IQK output regs — calibration results vary run-to-run because
# the tone sweep samples a noisy signal. Bit-exact match between
# captures (or between devourer and kernel) isn't expected;
# functional correctness is checked by the on-air RX/TX matrix.
("BB", 0x8b0): 0xFFFFFFFF,
("BB", 0xc10): 0xFFFFFFFF, # path-A RX IQK fill
("BB", 0xc14): 0xFFFFFFFF,
("BB", 0xc90): 0xFFFFFFFF, # path-A TX IQK matrix
("BB", 0xc94): 0xFFFFFFFF,
("BB", 0xe10): 0xFFFFFFFF, # path-B RX IQK fill
("BB", 0xe14): 0xFFFFFFFF,
("BB", 0xe90): 0xFFFFFFFF, # path-B TX IQK matrix
("BB", 0xe94): 0xFFFFFFFF,
}

# Capture-state artifacts at 5GHz only — registers that aren't
Expand Down
44 changes: 44 additions & 0 deletions tests/test_canary_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,50 @@ def test_strict_disables_masking(tmp_path: Path) -> None:
assert res.returncode == 1, res.stdout + res.stderr


def test_dig_igi_byte0_masked(tmp_path: Path) -> None:
"""BB 0xc50 bits 7:0 are the DIG IGI — kernel's phydm walks
them; devourer writes the floor once. Diffs in byte 0 must
be masked, but upper bytes still diff."""
kernel = wrap("BB 0xc50 = 0x69b80022")
devourer = wrap("BB 0xc50 = 0x69b8001c") # only byte 0 differs
res = run_diff(kernel, devourer, tmp_path=tmp_path)
assert res.returncode == 0, res.stdout + res.stderr


def test_dig_igi_upper_bits_still_diffed(tmp_path: Path) -> None:
"""BB 0xc50 bits 31:8 are static AGC config — a real divergence
there should still fail the diff."""
kernel = wrap("BB 0xc50 = 0x69b8001c")
devourer = wrap("BB 0xc50 = 0x69b8011c") # bit 8 differs
res = run_diff(kernel, devourer, tmp_path=tmp_path)
assert res.returncode == 1, res.stdout + res.stderr


def test_iqk_output_regs_masked(tmp_path: Path) -> None:
"""IQK output coefficients vary run-to-run; the canonical
set (0x8b0, 0xc10/0xc14/0xc90/0xc94, path-B mirrors) is
masked entirely."""
body = "\n".join(
f"BB 0x{addr:x} = 0xAAAAAAAA" for addr in
(0x8b0, 0xc10, 0xc14, 0xc90, 0xc94, 0xe10, 0xe14, 0xe90, 0xe94)
)
body_alt = "\n".join(
f"BB 0x{addr:x} = 0x55555555" for addr in
(0x8b0, 0xc10, 0xc14, 0xc90, 0xc94, 0xe10, 0xe14, 0xe90, 0xe94)
)
res = run_diff(wrap(body), wrap(body_alt), tmp_path=tmp_path)
assert res.returncode == 0, res.stdout + res.stderr


def test_iqk_output_unmasked_under_strict(tmp_path: Path) -> None:
"""--strict bypasses the IQK output mask; functional checks
that want bit-exact match (e.g. replay testing) can opt in."""
kernel = wrap("BB 0xc90 = 0xAAAAAAAA")
devourer = wrap("BB 0xc90 = 0x55555555")
res = run_diff(kernel, devourer, "--strict", tmp_path=tmp_path)
assert res.returncode == 1, res.stdout + res.stderr


def test_5g_capture_state_artifact_masked(tmp_path: Path) -> None:
"""BB 0xc20 is CCK-only — never written at 5G by either side, but
kernel iface (long-lived) retains a 2.4G value while devourer
Expand Down
Loading