tools: measure the ENE read-back question instead of inferring it
P2 asked whether the ENE at 0x27 has readable state. The answer had been reached twice by elimination, and the last link in that chain was a comment in a third party's patched OpenRGB detector, not a measurement of ours. probe-ene-receive-byte.sh runs the experiment the driver made possible on 2026-07-27, when PNTR_SEL (BIT(18)) gave us a bare Receive Byte: a read with no command byte on the wire cannot return a transform of a register that was never sent. It does, however, return the last command byte the chip received — 12/12 on both channels — so the value is still ours, not the device's. A Receive Byte taken from 0x31 after addressing 0x27 answers 0xff instead of the echo, which places the latch in the device and not in the iMC engine; empty addresses NACK, so the transaction is real. Everything here is read-only: Receive Byte and Read Byte Data at 0x27, SPD reads through ee1004 sysfs, and the four SPD serials compared before and after every run. Two engine behaviours the script has to work around, both measured: the first transaction on a channel answers with a stale value (hence the discarded warm-up read), and a read following a successful one usually NACKs, which one retry always clears — the P1 policy, confirmed. lore-thread-check.py is unrelated to the probe but shares the same motive: lore and patchwork now sit behind an Anubis bot check, so a shell cannot tell "not published" from "not allowed to look". The NNTP mirror can.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
== P2 Receive Byte probe at 0x27 — 2026-08-10T18:27:12+02:00
|
||||
driver: /lib/modules/7.0.0-28-generic/updates/dkms/i2c-imc-skylake.ko.zst
|
||||
rounds per channel: 8
|
||||
|
||||
-- SPD baseline
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
|
||||
-- ch0 (i2c-1) — artefact map, read_byte_data(reg)
|
||||
reg value N>>2 N>>3 N-1 closer
|
||||
0x00 0x00 0x00 0x00 0xff NACK
|
||||
0x01 0x00 0x00 0x00 0x00 0x00
|
||||
0x02 0x00 0x00 0x00 0x01 NACK
|
||||
0x03 0x00 0x00 0x00 0x02 0x00
|
||||
0x04 0x01 0x01 0x00 0x03 NACK
|
||||
0x08 0x02 0x02 0x01 0x07 NACK
|
||||
0x0f 0x03 0x03 0x01 0x0e 0x03
|
||||
0x10 0x04 0x04 0x02 0x0f NACK
|
||||
0x11 0x04 0x04 0x02 0x10 0x04
|
||||
0x20 0x08 0x08 0x04 0x1f NACK
|
||||
0x21 0x02 0x08 0x04 0x20 NACK
|
||||
0x40 0x10 0x10 0x08 0x3f NACK
|
||||
0x41 0x10 0x10 0x08 0x40 0x10
|
||||
0x80 0x20 0x20 0x10 0x7f NACK
|
||||
0xa0 0x28 0x28 0x14 0x9f NACK
|
||||
0xa1 0x28 0x28 0x14 0xa0 0xe8
|
||||
0xaf 0xeb 0x2b 0x15 0xae 0xeb
|
||||
0xe1 0xf8 0x38 0x1c 0xe0 0xf8
|
||||
0xec 0xfb 0x3b 0x1d 0xeb NACK
|
||||
0xff 0x3f 0x3f 0x1f 0xfe 0xff
|
||||
|
||||
-- ch1 (i2c-2) — artefact map, read_byte_data(reg)
|
||||
reg value N>>2 N>>3 N-1 closer
|
||||
0x00 0x00 0x00 0x00 0xff NACK
|
||||
0x01 0x00 0x00 0x00 0x00 0x00
|
||||
0x02 0x00 0x00 0x00 0x01 NACK
|
||||
0x03 0x00 0x00 0x00 0x02 0x00
|
||||
0x04 0x01 0x01 0x00 0x03 NACK
|
||||
0x08 0x02 0x02 0x01 0x07 NACK
|
||||
0x0f 0x03 0x03 0x01 0x0e 0x03
|
||||
0x10 0x04 0x04 0x02 0x0f NACK
|
||||
0x11 0x02 0x04 0x02 0x10 NACK
|
||||
0x20 0x02 0x08 0x04 0x1f NACK
|
||||
0x21 0x08 0x08 0x04 0x20 0x08
|
||||
0x40 0x10 0x10 0x08 0x3f NACK
|
||||
0x41 0x10 0x10 0x08 0x40 0x10
|
||||
0x80 0x20 0x20 0x10 0x7f NACK
|
||||
0xa0 0x28 0x28 0x14 0x9f NACK
|
||||
0xa1 0x28 0x28 0x14 0xa0 0xe8
|
||||
0xaf 0xfa 0x2b 0x15 0xae 0xeb
|
||||
0xe1 0xf8 0x38 0x1c 0xe0 0xf8
|
||||
0xec 0xfb 0x3b 0x1d 0xeb NACK
|
||||
0xff 0x0f 0x3f 0x1f 0xfe 0xff
|
||||
|
||||
-- SPD after
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
SPD integrity: OK (4/4 serials unchanged)
|
||||
@@ -0,0 +1,38 @@
|
||||
== P2 Receive Byte probe at 0x27 — 2026-08-10T18:29:29+02:00
|
||||
driver: /lib/modules/7.0.0-28-generic/updates/dkms/i2c-imc-skylake.ko.zst
|
||||
rounds per channel: 8
|
||||
|
||||
-- SPD baseline
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
|
||||
-- ch0 (i2c-1) — receive_byte across addresses
|
||||
0x27 0xe1 0xe1 0xe1
|
||||
0x30 0xff 0xff 0xff
|
||||
0x31 0xff 0xff 0xff
|
||||
0x34 0xff 0xff 0xff
|
||||
0x35 0xff 0xff 0xff
|
||||
0x36 ERR:Error: Could not set address to 0x36: Device or resource busy ERR:Error: Could not set address to 0x36: Device or resource busy ERR:Error: Could not set address to 0x36: Device or resource busy
|
||||
0x37 ERR:Error: Could not set address to 0x37: Device or resource busy ERR:Error: Could not set address to 0x37: Device or resource busy ERR:Error: Could not set address to 0x37: Device or resource busy
|
||||
0x2a NACK NACK NACK
|
||||
0x2b NACK NACK NACK
|
||||
|
||||
-- ch1 (i2c-2) — receive_byte across addresses
|
||||
0x27 0xe1 0xe1 0xe1
|
||||
0x30 0xff 0xff 0xff
|
||||
0x31 0xff 0xff 0xff
|
||||
0x34 0xff 0xff 0xff
|
||||
0x35 0xff 0xff 0xff
|
||||
0x36 ERR:Error: Could not set address to 0x36: Device or resource busy ERR:Error: Could not set address to 0x36: Device or resource busy ERR:Error: Could not set address to 0x36: Device or resource busy
|
||||
0x37 ERR:Error: Could not set address to 0x37: Device or resource busy ERR:Error: Could not set address to 0x37: Device or resource busy ERR:Error: Could not set address to 0x37: Device or resource busy
|
||||
0x2a NACK NACK NACK
|
||||
0x2b NACK NACK NACK
|
||||
|
||||
-- SPD after
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
SPD integrity: OK (4/4 serials unchanged)
|
||||
@@ -0,0 +1,40 @@
|
||||
== P2 Receive Byte probe at 0x27 — 2026-08-10T18:30:45+02:00
|
||||
driver: /lib/modules/7.0.0-28-generic/updates/dkms/i2c-imc-skylake.ko.zst
|
||||
rounds per channel: 8
|
||||
|
||||
-- SPD baseline
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
|
||||
-- ch0 (i2c-1) — does receive_byte echo the last command byte?
|
||||
probe_reg read_byte_data receive_byte verdict
|
||||
0x33 0x0c 0x33 ECHOES the command byte
|
||||
0x55 0x15 0x55 ECHOES the command byte
|
||||
0x77 0x1d 0x77 ECHOES the command byte
|
||||
0xa5 0x29 0xa5 ECHOES the command byte
|
||||
0x0c 0xc1 0x0c ECHOES the command byte
|
||||
0x33 0x0c 0x33 ECHOES the command byte
|
||||
cross-address: command byte to 0x27, Receive Byte from 0x31
|
||||
0x27 reg 0x5a -> 0x16 then 0x31 receive_byte -> 0xff
|
||||
0x27 reg 0x3c -> 0x0f then 0x31 receive_byte -> 0xff
|
||||
|
||||
-- ch1 (i2c-2) — does receive_byte echo the last command byte?
|
||||
probe_reg read_byte_data receive_byte verdict
|
||||
0x33 0x0c 0x33 ECHOES the command byte
|
||||
0x55 0x15 0x55 ECHOES the command byte
|
||||
0x77 0x1d 0x77 ECHOES the command byte
|
||||
0xa5 0x29 0xa5 ECHOES the command byte
|
||||
0x0c 0xc3 0x0c ECHOES the command byte
|
||||
0x33 0x0c 0x33 ECHOES the command byte
|
||||
cross-address: command byte to 0x27, Receive Byte from 0x31
|
||||
0x27 reg 0x5a -> 0x16 then 0x31 receive_byte -> 0xff
|
||||
0x27 reg 0x3c -> 0x0f then 0x31 receive_byte -> 0xff
|
||||
|
||||
-- SPD after
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
SPD integrity: OK (4/4 serials unchanged)
|
||||
@@ -0,0 +1,47 @@
|
||||
== P2 Receive Byte probe at 0x27 — 2026-08-10T18:28:35+02:00
|
||||
driver: /lib/modules/7.0.0-28-generic/updates/dkms/i2c-imc-skylake.ko.zst
|
||||
rounds per channel: 12
|
||||
|
||||
-- SPD baseline
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
|
||||
-- ch0 (i2c-1)
|
||||
control read_byte_data(0x10) = 0x04 [after 1 retry] (poison-close read: NACK)
|
||||
round 1 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 2 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 3 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 4 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 5 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 6 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 7 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 8 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 9 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 10 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 11 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 12 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
|
||||
-- ch1 (i2c-2)
|
||||
control read_byte_data(0x10) = 0x04 [after 1 retry] (poison-close read: NACK)
|
||||
round 1 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 2 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 3 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 4 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 5 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 6 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 7 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 8 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 9 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 10 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 11 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
round 12 receive_byte = 0x10 retry: - (closer: 0x10 ) spd-live: 50E4333A
|
||||
|
||||
-- SPD after
|
||||
ch0.dimm0=50E4333A
|
||||
ch0.dimm1=1FE43236
|
||||
ch1.dimm0=50E434BA
|
||||
ch1.dimm1=1EE433B5
|
||||
|
||||
SPD integrity: OK (4/4 serials unchanged)
|
||||
@@ -13,6 +13,19 @@ raw numbers. Do not paraphrase them.
|
||||
| `dump-pcu-config.sh` | Which PCU registers move on their own? Is there a TSOD polling control? | no |
|
||||
| `probe-cf8-write.sh` | Is the CF8/CFC write really dropped, and does it raise an SMI? | **yes**, requires `--i-understand` |
|
||||
|
||||
Not part of the submission: `pre-poweroff-load.sh` — fixed, mixed SMBus load
|
||||
to run before every poweroff of the A5 hang observation window (TODO.md E1),
|
||||
identical in the `use_ecam=1` phase and the `use_ecam=0` counter-proof.
|
||||
3-minute blocks: software effect stream (~2760 writes/s) → rapid hw/sw switch
|
||||
burst → hardware effect → full SPD dump via ee1004 + serial integrity check.
|
||||
Fully automatic: saves run context + any driver warnings to a per-run file in
|
||||
`tools/pre-poweroff-runs/`, appends one summary line to
|
||||
`pre-poweroff-load.log`, then — **only with `--poweroff`** — powers the
|
||||
machine off (15 s grace, Ctrl-C aborts); without the flag it just reports and
|
||||
exits. Run: `sudo ./tools/pre-poweroff-load.sh [minutes] [--poweroff]`
|
||||
(default 30; re-execs under sudo by itself). The one exception: an SPD serial
|
||||
mismatch saves everything and does NOT power off — investigate first.
|
||||
|
||||
## 1. `watch-engine.sh` — foreign activity (read-only)
|
||||
|
||||
Samples the six per-channel registers and reports every change that this
|
||||
@@ -133,3 +146,50 @@ shown the encoding works.
|
||||
A channel found on page 1 is not a fault of this driver: `ee1004`, and
|
||||
OpenRGB's `DDR4DirectAccessor::set_page()`, both select a page and leave it
|
||||
selected.
|
||||
|
||||
## 8. `lore-thread-check.py` — "la patch è stata pubblicata? qualcuno ha risposto?"
|
||||
|
||||
Niente hardware: interroga l'archivio **NNTP** di lore, `nntp.lore.kernel.org:119`,
|
||||
e dice (a) se un message-id è nell'archivio della lista, con gli header veri, e
|
||||
(b) quali articoli successivi appartengono allo stesso thread. Sola lettura, il
|
||||
server rifiuta il posting comunque.
|
||||
|
||||
```bash
|
||||
./tools/lore-thread-check.py [email protected]
|
||||
./tools/lore-thread-check.py <msgid> --group org.kernel.vger.linux-kernel
|
||||
```
|
||||
|
||||
Esce `0` se il messaggio è archiviato, `1` se non c'è, `2` su errore.
|
||||
|
||||
> ⚠️ Esiste perché dal browser o con `curl` la risposta è ingannevole:
|
||||
> `lore.kernel.org` e `patchwork.kernel.org` stanno dietro il bot-check **Anubis**
|
||||
> e restituiscono 403 o la pagina *"Making sure you're not a bot!"* al posto del
|
||||
> messaggio — che si legge facilmente come "la patch non è mai stata pubblicata".
|
||||
> Il mirror NNTP non ha il challenge. Per patchwork non esiste equivalente: lì
|
||||
> serve un browser vero.
|
||||
|
||||
## 9. `probe-ene-receive-byte.sh` — la Receive Byte a 0x27 (read-only)
|
||||
|
||||
L'esperimento di `TODO.md` §P2, e le sue varianti diagnostiche. Nessuna
|
||||
scrittura, su nessun indirizzo: solo Receive Byte e Read Byte Data verso `0x27`
|
||||
e letture SPD dai nodi sysfs di `ee1004`. Le quattro seriali SPD sono
|
||||
confrontate prima e dopo a ogni run e una differenza è errore fatale.
|
||||
|
||||
```bash
|
||||
sudo systemctl stop furyd # lo script rifiuta di partire con furyd attivo
|
||||
sudo ./tools/probe-ene-receive-byte.sh --rounds 12 # l'esperimento
|
||||
sudo ./tools/probe-ene-receive-byte.sh --latch-test # eco del command byte + cross-address
|
||||
sudo ./tools/probe-ene-receive-byte.sh --map # ri-deriva l'artefatto N>>2
|
||||
sudo ./tools/probe-ene-receive-byte.sh --addr-sweep # stessa read su altri indirizzi
|
||||
sudo systemctl start furyd
|
||||
```
|
||||
|
||||
Esito del 2026-08-10 (log in `docs/evidence/2026-08-10-ene-*.log`): la Receive
|
||||
Byte restituisce l'**eco dell'ultimo command byte** mandato a quel chip (12/12,
|
||||
due canali), il latch è del device e non dell'engine (da `0x31` torna `0xff`),
|
||||
gli indirizzi vuoti NACKano. **L'ENE non ha stato leggibile** — ora misurato.
|
||||
|
||||
> ⚠️ Due trappole, valgono per qualunque misura su questo engine: la **prima**
|
||||
> transazione di un canale restituisce un valore stale (per questo lo script fa
|
||||
> un read di warm-up scartato), e la lettura successiva a una riuscita NACKa
|
||||
> spesso — un solo retry la pulisce sempre, come dice P1.
|
||||
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check on lore.kernel.org whether a posting was archived, and list its replies.
|
||||
|
||||
Why NNTP and not the web UI: since mid-2026 lore.kernel.org (and
|
||||
patchwork.kernel.org) sit behind an Anubis proof-of-work bot check, so plain
|
||||
HTTP from curl or a script gets the "Making sure you're not a bot!"
|
||||
interstitial instead of the message -- HTTP 403 without a browser User-Agent,
|
||||
HTTP 200 with the interstitial body if one is set. The NNTP mirror on
|
||||
nntp.lore.kernel.org:119 serves the same archive with no challenge, so it is
|
||||
the reliable way to answer "was my patch actually published, and did anyone
|
||||
answer" from a shell.
|
||||
|
||||
Read-only: it opens the group, asks for the article head, and scans the
|
||||
overview records after it. It never posts (the server refuses posting anyway).
|
||||
|
||||
Usage:
|
||||
./tools/lore-thread-check.py <message-id> [--group org.kernel.vger.linux-i2c]
|
||||
|
||||
The message-id is given without angle brackets, e.g.
|
||||
|
||||
./tools/lore-thread-check.py [email protected]
|
||||
|
||||
Exit status: 0 if the message is in the archive, 1 if it is not, 2 on error.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import socket
|
||||
import sys
|
||||
|
||||
DEFAULT_GROUP = "org.kernel.vger.linux-i2c"
|
||||
HOST = "nntp.lore.kernel.org"
|
||||
PORT = 119
|
||||
HEADERS_OF_INTEREST = (
|
||||
"from", "to", "cc", "subject", "date", "message-id",
|
||||
"in-reply-to", "references", "x-mailing-list",
|
||||
)
|
||||
|
||||
|
||||
class Nntp:
|
||||
def __init__(self, host, port, timeout=60):
|
||||
self.sock = socket.create_connection((host, port), timeout=timeout)
|
||||
self.fp = self.sock.makefile("rb")
|
||||
self.banner = self._line()
|
||||
|
||||
def _line(self):
|
||||
return self.fp.readline().decode("utf-8", "replace").rstrip("\r\n")
|
||||
|
||||
def cmd(self, text):
|
||||
self.sock.sendall((text + "\r\n").encode())
|
||||
return self._line()
|
||||
|
||||
def block(self):
|
||||
out = []
|
||||
while True:
|
||||
line = self._line()
|
||||
if line == ".":
|
||||
return out
|
||||
out.append(line[1:] if line.startswith("..") else line)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.cmd("QUIT")
|
||||
finally:
|
||||
self.sock.close()
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("msgid", help="message-id without angle brackets")
|
||||
ap.add_argument("--group", default=DEFAULT_GROUP,
|
||||
help="lore NNTP group (default: %(default)s)")
|
||||
ap.add_argument("--scan", type=int, default=0, metavar="N",
|
||||
help="also scan the N articles before the message "
|
||||
"(default: only the ones after it)")
|
||||
args = ap.parse_args()
|
||||
|
||||
msgid = args.msgid.strip().strip("<>")
|
||||
|
||||
try:
|
||||
nntp = Nntp(HOST, PORT)
|
||||
except OSError as exc:
|
||||
print(f"cannot reach {HOST}:{PORT}: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
try:
|
||||
nntp.cmd("MODE READER")
|
||||
resp = nntp.cmd("GROUP " + args.group)
|
||||
if not resp.startswith("211"):
|
||||
print(f"GROUP {args.group}: {resp}", file=sys.stderr)
|
||||
return 2
|
||||
_, _, low, high, _ = resp.split(None, 4)
|
||||
|
||||
resp = nntp.cmd(f"HEAD <{msgid}>")
|
||||
if not resp.startswith("221"):
|
||||
print(f"NOT in {args.group}: <{msgid}> ({resp})")
|
||||
return 1
|
||||
|
||||
article = resp.split()[1]
|
||||
print(f"archived: article {article} of {args.group} (range {low}-{high})")
|
||||
for line in nntp.block():
|
||||
if line[:1] in (" ", "\t"):
|
||||
continue
|
||||
if line.split(":", 1)[0].lower() in HEADERS_OF_INTEREST:
|
||||
print(" ", line)
|
||||
|
||||
first = max(int(low), int(article) - args.scan)
|
||||
resp = nntp.cmd(f"XOVER {first}-{high}")
|
||||
if not resp.startswith("224"):
|
||||
print(f"XOVER: {resp}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
replies = []
|
||||
for line in nntp.block():
|
||||
f = line.split("\t")
|
||||
if len(f) < 6:
|
||||
continue
|
||||
num, subject, sender, date, this_id, refs = f[:6]
|
||||
if num == article:
|
||||
continue
|
||||
if msgid in refs or msgid in this_id:
|
||||
replies.append((num, date, sender, subject))
|
||||
|
||||
print(f"\nsame thread, articles {first}-{high}: {len(replies)}")
|
||||
for num, date, sender, subject in replies:
|
||||
print(f" {num} | {date} | {sender} | {subject}")
|
||||
if not replies:
|
||||
print(" (none -- no reply and no follow-up patch of this thread)")
|
||||
return 0
|
||||
finally:
|
||||
nntp.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+334
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# P2 — Receive Byte at the ENE LED controller (0x27), both iMC channels.
|
||||
#
|
||||
# The question. Reads of 0x27 with a command byte return a deterministic
|
||||
# artefact *of the command byte we send*: even register N -> N>>2, odd N ->
|
||||
# N-1 (furypy research, 2026-06-10/12). That measurement is why "the ENE is
|
||||
# write-only" was concluded by elimination. A Receive Byte sends no command
|
||||
# byte at all (SMBus Read Byte, PNTR_SEL = BIT(18), added to this driver on
|
||||
# 2026-07-27), so whatever comes back cannot be a transform of a register that
|
||||
# never went on the wire.
|
||||
#
|
||||
# NACK, or a constant 0x00 -> write-only is a property of the ENE slave,
|
||||
# measured directly instead of inferred.
|
||||
# a stable value not derivable from the command word
|
||||
# -> device data; the read-back question is open
|
||||
# again and the follow-up is a known-value
|
||||
# write via fury, then Receive Byte.
|
||||
#
|
||||
# READ-ONLY. Every transaction is a read: Receive Byte and Read Byte Data at
|
||||
# 0x27, and SPD reads through the bound ee1004 sysfs nodes. Nothing is written
|
||||
# to any address, so the DDR4 page latch cannot move and no LED state changes.
|
||||
#
|
||||
# Design constraints, from TODO.md P1/P2:
|
||||
# * NACK poison: a read of 0x27 poisons the *next* read on the same channel
|
||||
# (~deterministic back-to-back; one retry clears it; the state is consumed
|
||||
# by any next transaction). Every measured read is therefore followed by a
|
||||
# discarded closing read, and the discarded result is reported separately
|
||||
# so it is never mistaken for a result.
|
||||
# * Control the artefact predicts: Read Byte Data of register 0x10 must
|
||||
# return 0x04 (N>>2). If it does not, the artefact changed and the whole
|
||||
# comparison is void — the script says so and stops.
|
||||
# * SPD integrity: the four serials are read before and after and compared.
|
||||
# A mismatch is a hard failure.
|
||||
# * furyd must not be driving the bus during the run (it holds its own lock
|
||||
# and this script deliberately does not take it).
|
||||
#
|
||||
# Usage: sudo ./tools/probe-ene-receive-byte.sh [--rounds N] [--allow-furyd]
|
||||
|
||||
set -u
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
# shellcheck source=tools/common.sh
|
||||
. tools/common.sh
|
||||
|
||||
ENE_ADDR=0x27
|
||||
CONTROL_REG=0x10
|
||||
CONTROL_EXPECT=0x04
|
||||
BUSES=("1:ch0" "2:ch1")
|
||||
SPD_NODES=(
|
||||
"ch0.dimm0:/sys/bus/i2c/devices/1-0050/eeprom"
|
||||
"ch0.dimm1:/sys/bus/i2c/devices/1-0052/eeprom"
|
||||
"ch1.dimm0:/sys/bus/i2c/devices/2-0050/eeprom"
|
||||
"ch1.dimm1:/sys/bus/i2c/devices/2-0052/eeprom"
|
||||
)
|
||||
rounds=8
|
||||
allow_furyd=0
|
||||
map_only=0
|
||||
force=0
|
||||
addr_sweep=0
|
||||
latch_test=0
|
||||
MAP_REGS=(0x00 0x01 0x02 0x03 0x04 0x08 0x0f 0x10 0x11 0x20 0x21 0x40 0x41
|
||||
0x80 0xa0 0xa1 0xaf 0xe1 0xec 0xff)
|
||||
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--rounds)
|
||||
rounds=${2:?--rounds needs a number}
|
||||
shift 2
|
||||
;;
|
||||
--addr-sweep)
|
||||
# Same Receive Byte, other addresses with known read-only semantics —
|
||||
# tells a device answer apart from an engine-wide constant.
|
||||
addr_sweep=1
|
||||
shift
|
||||
;;
|
||||
--latch-test)
|
||||
# Decisive test for "the Receive Byte answers with the last command
|
||||
# byte that went on the wire": send a Read Byte Data of a chosen
|
||||
# register, then a Receive Byte, and see whether it echoes that
|
||||
# register. Read-only.
|
||||
latch_test=1
|
||||
shift
|
||||
;;
|
||||
--map)
|
||||
# Re-derive the command-word artefact on this driver instead of
|
||||
# assuming the 2026-06 mapping. Read Byte Data only, read-only.
|
||||
map_only=1
|
||||
shift
|
||||
;;
|
||||
--force)
|
||||
# Run the Receive Byte even if the artefact control disagrees with
|
||||
# CONTROL_EXPECT — for when --map has established what the current
|
||||
# artefact is and the comparison is being made against that instead.
|
||||
force=1
|
||||
shift
|
||||
;;
|
||||
--allow-furyd)
|
||||
allow_furyd=1
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
sed -n '3,36p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown argument: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_root
|
||||
require_cmd i2cget i2cdetect
|
||||
driver_is_loaded || die "i2c_imc_skylake is not loaded"
|
||||
|
||||
if ((!allow_furyd)) && systemctl is-active --quiet furyd; then
|
||||
die "furyd is active and would share the bus. Stop it for the run
|
||||
(systemctl stop furyd; systemctl start furyd afterwards), or pass
|
||||
--allow-furyd if you accept the interleaving."
|
||||
fi
|
||||
|
||||
# SPD serial number: DDR4 bytes 325-328 (0x145-0x148), big-endian in the dump.
|
||||
spd_serial()
|
||||
{
|
||||
local node=$1
|
||||
od -An -tx1 -j 325 -N 4 "$node" 2>/dev/null | tr -d ' \n' | tr 'a-f' 'A-F'
|
||||
}
|
||||
|
||||
snapshot_spd()
|
||||
{
|
||||
local entry name node
|
||||
for entry in "${SPD_NODES[@]}"; do
|
||||
name=${entry%%:*}
|
||||
node=${entry#*:}
|
||||
printf '%s=%s\n' "$name" "$(spd_serial "$node")"
|
||||
done
|
||||
}
|
||||
|
||||
# read_ene <bus> [reg] -> prints "value" or "NACK"/"ERR:<text>"; never fails
|
||||
# the script, because a refusal is an answer here.
|
||||
read_ene()
|
||||
{
|
||||
local bus=$1 reg=${2:-} out rc
|
||||
if [[ -n $reg ]]; then
|
||||
out=$(i2cget -y "$bus" "$ENE_ADDR" "$reg" 2>&1)
|
||||
else
|
||||
out=$(i2cget -y "$bus" "$ENE_ADDR" 2>&1)
|
||||
fi
|
||||
rc=$?
|
||||
if ((rc == 0)); then
|
||||
printf '%s\n' "$out"
|
||||
return
|
||||
fi
|
||||
case $out in
|
||||
*"Read failed"*) printf 'NACK\n' ;;
|
||||
*) printf 'ERR:%s\n' "${out//$'\n'/ }" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo "== P2 Receive Byte probe at $ENE_ADDR — $(date -Is)"
|
||||
echo " driver: $(modinfo -F filename i2c-imc-skylake 2>/dev/null || echo '?')"
|
||||
echo " rounds per channel: $rounds"
|
||||
echo
|
||||
|
||||
echo "-- SPD baseline"
|
||||
spd_before=$(snapshot_spd)
|
||||
printf '%s\n' "$spd_before" | sed 's/^/ /'
|
||||
[[ -n $spd_before ]] || die "no SPD serials read — is ee1004 bound?"
|
||||
echo
|
||||
|
||||
if ((latch_test)); then
|
||||
for entry in "${BUSES[@]}"; do
|
||||
bus=${entry%%:*}
|
||||
label=${entry##*:}
|
||||
echo "-- $label (i2c-$bus) — does receive_byte echo the last command byte?"
|
||||
echo " probe_reg read_byte_data receive_byte verdict"
|
||||
for reg in 0x33 0x55 0x77 0xa5 0x0c 0x33; do
|
||||
# The byte-data read is the thing that puts a command byte on the
|
||||
# wire. One retry each, per the P1 poison policy.
|
||||
bd=$(read_ene "$bus" "$reg")
|
||||
[[ $bd == NACK ]] && bd=$(read_ene "$bus" "$reg")
|
||||
rb=$(read_ene "$bus")
|
||||
[[ $rb == NACK ]] && rb=$(read_ene "$bus")
|
||||
verdict="no"
|
||||
[[ $rb == "$(printf '0x%02x' "$((reg))")" ]] && verdict="ECHOES the command byte"
|
||||
printf ' %-10s %-15s %-13s %s\n' "$reg" "$bd" "$rb" "$verdict"
|
||||
done
|
||||
# Where does the latch live: in the ENE chip (a register pointer it
|
||||
# keeps) or in the iMC engine (its own last command byte)? Put a
|
||||
# command byte on the wire towards 0x27, then take the Receive Byte
|
||||
# from a *different* address that also ACKs (RPS0 at 0x31). An echo
|
||||
# there means the latch is the engine's and says nothing about any
|
||||
# chip; 0xff (what RPS answers on its own) means it is per-device.
|
||||
echo " cross-address: command byte to 0x27, Receive Byte from 0x31"
|
||||
for reg in 0x5a 0x3c; do
|
||||
bd=$(read_ene "$bus" "$reg")
|
||||
[[ $bd == NACK ]] && bd=$(read_ene "$bus" "$reg")
|
||||
rb=$(ENE_ADDR=0x31 read_ene "$bus")
|
||||
[[ $rb == NACK ]] && rb=$(ENE_ADDR=0x31 read_ene "$bus")
|
||||
printf ' 0x27 reg %-6s -> %-6s then 0x31 receive_byte -> %s\n' \
|
||||
"$reg" "$bd" "$rb"
|
||||
done
|
||||
read_ene "$bus" >/dev/null
|
||||
echo
|
||||
done
|
||||
echo "-- SPD after"
|
||||
spd_after=$(snapshot_spd)
|
||||
printf '%s\n' "$spd_after" | sed 's/^/ /'
|
||||
[[ $spd_before == "$spd_after" ]] &&
|
||||
echo "SPD integrity: OK (4/4 serials unchanged)" ||
|
||||
{ echo "SPD integrity: !! MISMATCH"; exit 4; }
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ((addr_sweep)); then
|
||||
# Is a stable Receive Byte answer at 0x27 device data, or the same thing
|
||||
# every address says on this engine? Compare against addresses whose
|
||||
# Receive Byte semantics are known and read-only: RPSn (0x30/0x31/0x34/
|
||||
# 0x35, JEDEC Read Protection Status — ACK = unprotected), RPA (0x36/0x37,
|
||||
# Read Page Address) and an address with nothing on it.
|
||||
for entry in "${BUSES[@]}"; do
|
||||
bus=${entry%%:*}
|
||||
label=${entry##*:}
|
||||
echo "-- $label (i2c-$bus) — receive_byte across addresses"
|
||||
for addr in 0x27 0x30 0x31 0x34 0x35 0x36 0x37 0x2a 0x2b; do
|
||||
a1=$(ENE_ADDR=$addr read_ene "$bus")
|
||||
a2=$(ENE_ADDR=$addr read_ene "$bus")
|
||||
a3=$(ENE_ADDR=$addr read_ene "$bus")
|
||||
printf ' %-6s %-6s %-6s %-6s\n' "$addr" "$a1" "$a2" "$a3"
|
||||
done
|
||||
echo
|
||||
done
|
||||
echo "-- SPD after"
|
||||
spd_after=$(snapshot_spd)
|
||||
printf '%s\n' "$spd_after" | sed 's/^/ /'
|
||||
[[ $spd_before == "$spd_after" ]] &&
|
||||
echo "SPD integrity: OK (4/4 serials unchanged)" ||
|
||||
{ echo "SPD integrity: !! MISMATCH"; exit 4; }
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ((map_only)); then
|
||||
for entry in "${BUSES[@]}"; do
|
||||
bus=${entry%%:*}
|
||||
label=${entry##*:}
|
||||
echo "-- $label (i2c-$bus) — artefact map, read_byte_data(reg)"
|
||||
echo " reg value N>>2 N>>3 N-1 closer"
|
||||
for reg in "${MAP_REGS[@]}"; do
|
||||
val=$(read_ene "$bus" "$reg")
|
||||
closer=$(read_ene "$bus" "$reg")
|
||||
n=$((reg))
|
||||
printf ' %-6s %-7s 0x%02x 0x%02x 0x%02x %s\n' \
|
||||
"$reg" "$val" "$((n >> 2))" "$((n >> 3))" \
|
||||
"$(((n - 1) & 0xff))" "$closer"
|
||||
done
|
||||
read_ene "$bus" >/dev/null
|
||||
echo
|
||||
done
|
||||
echo "-- SPD after"
|
||||
spd_after=$(snapshot_spd)
|
||||
printf '%s\n' "$spd_after" | sed 's/^/ /'
|
||||
[[ $spd_before == "$spd_after" ]] &&
|
||||
echo "SPD integrity: OK (4/4 serials unchanged)" ||
|
||||
{ echo "SPD integrity: !! MISMATCH"; exit 4; }
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for entry in "${BUSES[@]}"; do
|
||||
bus=${entry%%:*}
|
||||
label=${entry##*:}
|
||||
echo "-- $label (i2c-$bus)"
|
||||
|
||||
# 1. The artefact control. A warm-up read first: measured 2026-08-10, the
|
||||
# very first transaction on a channel after another process (furyd)
|
||||
# stopped touching it answers with a stale value — reg 0x10 returned
|
||||
# 0x02 as the session's first read and 0x04 on every later one, on both
|
||||
# channels (docs/evidence/2026-08-10-ene-artefact-map.log). Same shape
|
||||
# as the P1 poison: the engine hands back what the *previous*
|
||||
# transaction left. So the control only means something warm.
|
||||
#
|
||||
# The retry is P1's documented client policy, not a fudge: a read of
|
||||
# 0x27 poisons the *next* read on the same channel and one retry always
|
||||
# clears it. Both attempts are printed so the poison stays visible.
|
||||
read_ene "$bus" 0x00 >/dev/null
|
||||
ctl=$(read_ene "$bus" "$CONTROL_REG")
|
||||
ctl_retry=""
|
||||
if [[ $ctl == NACK ]]; then
|
||||
ctl_retry=$(read_ene "$bus" "$CONTROL_REG")
|
||||
ctl=$ctl_retry
|
||||
fi
|
||||
ctl_close=$(read_ene "$bus" "$CONTROL_REG")
|
||||
printf ' control read_byte_data(%s) = %-6s%s (poison-close read: %s)\n' \
|
||||
"$CONTROL_REG" "$ctl" \
|
||||
"$([[ -n $ctl_retry ]] && echo ' [after 1 retry]')" "$ctl_close"
|
||||
if [[ $ctl != "$CONTROL_EXPECT" ]]; then
|
||||
echo " !! artefact differs from the 2026-06 mapping:"
|
||||
echo " expected $CONTROL_EXPECT (N>>2) on reg $CONTROL_REG, got $ctl."
|
||||
if ((!force)); then
|
||||
echo " Run --map to re-derive it, then --force to compare"
|
||||
echo " against the current artefact instead. Stopping."
|
||||
exit 3
|
||||
fi
|
||||
echo " --force given: continuing against the current artefact."
|
||||
fi
|
||||
|
||||
# 2. The experiment itself.
|
||||
for ((i = 1; i <= rounds; i++)); do
|
||||
val=$(read_ene "$bus")
|
||||
retry=""
|
||||
[[ $val == NACK ]] && retry=$(read_ene "$bus")
|
||||
closer=$(read_ene "$bus")
|
||||
spd_live=$(spd_serial "${SPD_NODES[0]#*:}")
|
||||
printf ' round %-2d receive_byte = %-6s retry: %-6s (closer: %-6s) spd-live: %s\n' \
|
||||
"$i" "$val" "${retry:--}" "$closer" "${spd_live:-READ-FAILED}"
|
||||
done
|
||||
|
||||
# 3. Leave the channel clean: one more discarded read consumes any
|
||||
# poison left by the last measured one.
|
||||
read_ene "$bus" >/dev/null
|
||||
echo
|
||||
done
|
||||
|
||||
echo "-- SPD after"
|
||||
spd_after=$(snapshot_spd)
|
||||
printf '%s\n' "$spd_after" | sed 's/^/ /'
|
||||
echo
|
||||
|
||||
if [[ $spd_before == "$spd_after" ]]; then
|
||||
echo "SPD integrity: OK (4/4 serials unchanged)"
|
||||
else
|
||||
echo "SPD integrity: !! MISMATCH — investigate before anything else"
|
||||
diff <(printf '%s\n' "$spd_before") <(printf '%s\n' "$spd_after")
|
||||
exit 4
|
||||
fi
|
||||
Reference in New Issue
Block a user