From 7e5913d0932531bc1dc2fad6ecf659cf09085b82 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:56:32 +0300 Subject: [PATCH] =?UTF-8?q?T1:=20expand=20runtime-ephemeral=20mask=20?= =?UTF-8?q?=E2=80=94=20DIG=20IGI=20+=20IQK=20outputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the remaining post-init canary divergence clusters that aren't init drift: - BB 0xc50 / 0xe50 / 0x1850 / 0x1a50 byte 0 — DIG IGI for paths A/B/C/D. The kernel's phydm DIG watchdog walks the IGI value continuously based on RX noise floor; devourer writes the 0x1c floor once at init. Upper bits of the AGC core word stay diffed. - BB 0x8b0, 0xc10/0xc14/0xc90/0xc94 (+ path-B mirrors at 0xe10/0xe14/0xe90/0xe94) — IQK output coefficients. Both sides run IQK but the tone sweep samples a noisy signal so the per-bit output varies between runs even on the same chip. Functional IQK correctness is checked by the on-air RX/TX matrix, not by canary diff. `--strict` still bypasses every mask for replay/bit-exact debugging. Test suite grows from 13 to 17 cases covering the new mask entries. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/canary_diff.py | 36 ++++++++++++++++++++++++++++++-- tests/test_canary_diff.py | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/tests/canary_diff.py b/tests/canary_diff.py index 93f318c..e378e6a 100755 --- a/tests/canary_diff.py +++ b/tests/canary_diff.py @@ -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. @@ -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 @@ -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 diff --git a/tests/test_canary_diff.py b/tests/test_canary_diff.py index 0e3aa33..085f112 100644 --- a/tests/test_canary_diff.py +++ b/tests/test_canary_diff.py @@ -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