Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d0faba8d7 | ||
|
|
f75d13dec9 | ||
|
|
2bc5145cf2 | ||
|
|
5dc45f7b91 | ||
|
|
01685016df | ||
|
|
078c1b1414 | ||
|
|
a4fa04a9f9 | ||
|
|
39783c8a76 | ||
|
|
d671034e8b | ||
|
|
d1b48b2b16 | ||
|
|
2c7675a104 | ||
|
|
d7bd8068b4 | ||
|
|
1a897d7d24 | ||
|
|
8a85d19e50 | ||
|
|
637717dad0 | ||
|
|
308d99c5db | ||
|
|
8da98fe1c7 | ||
|
|
aa2ef6bd7b |
@@ -42,7 +42,15 @@ Thumbs.db
|
||||
patches/
|
||||
|
||||
# Internal working documents (not for public repo)
|
||||
# Dal 2026-08-14 i file qui sotto sono symlink verso private/, repo git a parte
|
||||
# e senza remote: erano ignorati e quindi non versionati da nessuna parte.
|
||||
# Vedi private/README.md. I nomi restano ignorati identici a prima.
|
||||
# 2026-08-17: la cartella era `.private/`, rinominata senza punto perche' Obsidian
|
||||
# non indicizza ne' i symlink ne' le dot-dir, quindi i wikilink ai doc interni
|
||||
# erano morti (wiki/TODO.md W60). Il punto era estetica, l'ignore fa il lavoro.
|
||||
private/
|
||||
TODO.md
|
||||
STORICO.md
|
||||
docs/archive-submission-analysis.md
|
||||
README.local.md
|
||||
AGENTS.md
|
||||
|
||||
+1060
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -0,0 +1,163 @@
|
||||
# Cross-check col reverse engineering vendor ASUS (`aura-re`) — 2026-08-26
|
||||
|
||||
> **Esito: nessuna divergenza. Il binario ASUS usa la stessa mappa registri del driver,
|
||||
> registro per registro.** Il valore di questo confronto non è una correzione: è la
|
||||
> **seconda conferma indipendente** su un'interfaccia che il datasheet Intel non documenta
|
||||
> per la PCU function 5 — la prima era PawnIO.Modules (`SmbusIntelSkylakeIMC.p`,
|
||||
> `TODO.md` §Notes) — più tre dati che nei nostri doc non c'erano.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Fonte** | `/media/simone/OS/Users/Simone/Desktop/aura-re` — analisi statica dei binari Armoury Crate, prodotta il **2026-08-26** (cartella non-git, sulla partizione Windows) |
|
||||
| **Grado di fiducia** | claim di terzi, ma **verificabile**: `raw/` ha i binari, `disasm/` il disassemblato completo |
|
||||
| **Spot-check fatto** | 2 su 2 riscontrati sul disassemblato primario, non sul solo testo — vedi §4 |
|
||||
| **Ricaduta sul codice** | **nessuna modifica necessaria** |
|
||||
|
||||
---
|
||||
|
||||
## 1. Confermato — mappa registri identica
|
||||
|
||||
Confronto fra `aura-re/03-smbus-backends.md` (classe `gs::IntelSKLXSmbus`) e
|
||||
`i2c-imc-skylake.c`.
|
||||
|
||||
| Elemento | ASUS `gs::IntelSKLXSmbus` | Nostro driver | Esito |
|
||||
|---|---|---|---|
|
||||
| Device PCI | DID `0x2085` | `PCU_DEVICE 0x2085` (`:95`) | identico |
|
||||
| Canale 0 — CMD / STAT / DATA | `0x9C` / `0xA8` / `0xB4` | `:110-112` | identico |
|
||||
| Canale 1 — CMD / STAT / DATA | `0xA0` / `0xAC` / `0xB8` | `:113-115` | identico |
|
||||
| CMD `[7:0]` | BA, offset nel device | `GENMASK(15,0)` low byte (`:132`) | identico |
|
||||
| CMD `[14:8]` | SA, indirizzo 7 bit | `:130-131` | identico |
|
||||
| CMD `[15]` | WRT, 1 = scrittura | `WRITE_OPERATION BIT(15)` (`:154`) | identico |
|
||||
| CMD `[17]` | WORD, accesso 16 bit | `WORD_BIT BIT(17)` (`:138`) | identico |
|
||||
| CMD `[19]` | TRIGGER | `GO_BIT BIT(19)` (`:136`) | identico |
|
||||
| CMD `[20]` | TSOD poll *(loro: `[inferenza]`)* | `TSOD_ACTIVE_BIT BIT(20)` (`:137`) | concorde |
|
||||
| STATUS, letto come **byte** | `[0]` BUSY `[1]` ERROR `[2]` read done `[3]` write done | `:164-167` | identico |
|
||||
| DATA — scrittura byte | `[23:16]` | `raw << 16` con `raw` a 8 bit (`:571-572`) | identico |
|
||||
| DATA — scrittura word | `[31:16]` | `swab16(*value) << 16` (`:571-572`) | identico |
|
||||
| DATA — lettura | `[15:0]` | `raw & 0xffff` / `& 0xff` (`:599-600`) | identico |
|
||||
|
||||
> **Perché conta.** La cover letter dice che il datasheet Intel (rif. 614073 §3.1.8)
|
||||
> descrive un'altra interfaccia, sulla channel function, e **non** copre la function 5.
|
||||
> Il HAL ASUS pilota la function 5 esattamente con la nostra codifica: la mappa non è più
|
||||
> solo "misurata sul mio hardware".
|
||||
|
||||
## 2. Differenze di condotta — nessuna è un difetto nostro
|
||||
|
||||
- **Bit 29** — ASUS **non lo scrive mai**: legge il CMD e lo maschera con
|
||||
`and dword ptr [r8], 0xfffe0000`, cioè conserva tutti i bit ≥ 17 e ricostruisce solo
|
||||
`[16:0]`. Il bit 29 sopravvive perché **lo ha già messo il firmware**. Noi lo scriviamo
|
||||
esplicitamente (`COMMAND_PREFIX`, `:155`) e abbiamo misurato che azzerandolo la
|
||||
transazione fallisce con ERROR. Le due osservazioni sono coerenti; è un secondo dato per
|
||||
la domanda "cos'è il bit 29" se un reviewer la fa.
|
||||
- **Gate TSOD** — ASUS si limita a **rifiutare** la transazione se `CMD bit20 == 1`
|
||||
(funzione `0x14001D720`, chiamata una sola volta via `std::call_once`). Noi facciamo di
|
||||
più: check di `SMBCNTL.TSOD_POLL_EN` a probe (`:783`) **e** azzeramento + ripristino del
|
||||
bit 20 su tutti i canali per ogni transfer (`imc_prepare_xfer`, `:517-548`). Il nostro
|
||||
è strettamente più cauto — niente da importare.
|
||||
- **`PNTR_SEL` (bit 18)** — ASUS non lo usa affatto. La scoperta del 27/07 resta solo
|
||||
nostra: nessuna conferma, ma nemmeno contraddizione.
|
||||
- **Timeout** — ASUS: 1000 iterazioni con attesa di ~237 µs (`0xED`) ≈ 237 ms.
|
||||
Noi: poll 10 µs, timeout 50 ms idle / 200 ms GO / 50 ms done. Nessuna azione.
|
||||
|
||||
## 3. Dati nuovi, che nei nostri doc non c'erano
|
||||
|
||||
- 🚨 **Tabella CPUID → Device ID del vendor** (`0x140001510`, tab. in
|
||||
`03-smbus-backends.md`):
|
||||
|
||||
→ tabella «cpuid e i device id cercati» qui sotto, fuori dall'elenco (W161)
|
||||
|
||||
| CPUID `>>4` | Famiglia | DID cercato |
|
||||
|---|---|---|
|
||||
| `0x306F` | Haswell-E / EP | `0x2FA8` |
|
||||
| `0x406F` | Broadwell-E / EP | `0x6FA8` |
|
||||
| `0x5065` | Skylake-X / Cascade Lake-X | `0x2085` |
|
||||
|
||||
**Tabella — cpuid e i device id cercati**
|
||||
|
||||
- **Il vendor conferma che i due layout non sono intercambiabili.** Il ramo
|
||||
`gs::IntelImcSmbus` (Haswell/Broadwell) usa `SMBSTAT 0x180` / `SMBCMD 0x184` /
|
||||
`SMBCNTL 0x188` con TRIGGER a `[31]`, PNTR_SEL a `[30]`, WORD a `[29]`, WRITE a `[27]` —
|
||||
**identico a `i2c-imc` di Lutomirski**; il ramo SKL-X è la codifica compatta che usiamo
|
||||
noi. È l'argomento tecnico del perché serve un driver nuovo e non un'estensione di
|
||||
`i2c-imc` (fonte: `aura-re/08-domande-aperte.md`, §"`IntelImcSmbus` contro `i2c-imc`").
|
||||
- **ENE a `0x27`: il percorso vendor è write-only**, e ora lo si legge nel codice ASUS,
|
||||
non in un commento di OpenRGB. Sessione `0xE1=0x01` + `0xE4=0x09`, colori
|
||||
`0xEC`/`0xED`/`0xEE`, commit `0xDD=0x64`, **50 µs di attesa attiva fra una scrittura e
|
||||
l'altra**, `Sleep(1 ms)` attorno all'apertura/chiusura, chiusura `0xE1=0x02` poi `0x03`.
|
||||
Corrobora **P2** (chiusa il 10/08) da una fonte indipendente.
|
||||
- **Gli indirizzi RGB dei moduli vengono *assegnati*, non sono di fabbrica**
|
||||
(`EneRGBSDK::Dram_AssignAddress`, base `0x48`; `GskillLogic::ChangeAddress`). Quindi
|
||||
`0x27` è "l'indirizzo dopo l'inizializzazione", non un dato di targa — da tenere
|
||||
presente in ogni futura misura sull'ENE.
|
||||
|
||||
## 4. Spot-check sul disassemblato primario
|
||||
|
||||
Due claim presi a campione e riscontrati sul `.asm`, non sul testo del doc:
|
||||
|
||||
```
|
||||
disasm/AacKingstonDramHal_x64.asm:337
|
||||
0x000140001550 b885200000 mov eax, 0x2085 # tabella CPUID->DID, voce SKL-X
|
||||
|
||||
disasm/AacKingstonDramHal_x64.asm:33438
|
||||
0x00014001d29f 4181200000feff and dword ptr [r8], 0xfffe0000 # ReadByte: conserva
|
||||
# i bit >=17 (29 e 20)
|
||||
```
|
||||
|
||||
## 5. 🚨 Vincolo: questo materiale non va su LKML
|
||||
|
||||
Deriva dal disassemblato di binari ASUS proprietari. Le affermazioni della patch e della
|
||||
cover letter restano fondate **sulle nostre misure su hardware**, che ci sono già e bastano.
|
||||
Il cross-check serve internamente (e in `docs/submission/review-answers.md`, gitignored),
|
||||
non come citazione nel thread upstream.
|
||||
|
||||
## 6. Ricadute fuori da questo progetto
|
||||
|
||||
- **furyrs** — Kingston FURY: RGB a `0x58` (DDR4, base `0x57`+1) e `0x60` (DDR5, base
|
||||
`0x5F`+1); mode register `0x08`: `0x53` `'S'` = controllo software, `0x44` `'D'` =
|
||||
default; detect = ReadWord `0x05`/`0x06`/`0x88`/`0x26` con 20 ms fra i passi. Detect
|
||||
del modulo RGB via SPD: manufacturer `0x0198` in `SPD[0x140/0x141]`, flag RGB =
|
||||
**`SPD[0x168] bit 4`** (pagina 1, SPA1 `0x37`). Fonte: `aura-re/04-dram-kingston.md`.
|
||||
- **tuf-x299-aura** — i LED della mobo **non stanno sull'i801**: stanno sul controller
|
||||
SMBus del Super I/O **Nuvoton** (`0x2E`/`0x2F`, LDN `0x0B`, base da `0x62`/`0x63`), con
|
||||
controller **AURA82** a `0x9C` 8 bit = **`0x4E` 7 bit** e pacchetti da 65 byte con marker
|
||||
`0xEC`. Spiegherebbe sia lo scan i801 muto sia OpenRGB a zero controller.
|
||||
Fonte: `aura-re/05-mobo-aura.md`, `06-porting-linux.md`.
|
||||
|
||||
## 7. Cosa ho già letto nel disassemblato (per non rifarlo)
|
||||
|
||||
Analisi mia del 26/08 sulla vtable di `gs::IntelSKLXSmbus`
|
||||
(`disasm/AacKingstonDramHal_x64.rtti.txt:17-30`, 13 slot, base `gs::Smbus` ne ha 11 con
|
||||
7 `_purecall`):
|
||||
|
||||
| Slot | VA | Cosa fa davvero | Ricaduta sul driver |
|
||||
|---|---|---|---|
|
||||
| 1-4 | `0x14001D310` / `D240` / `D3E0` / `D4A0` | read/write byte e word | già nostri |
|
||||
| 6 | `0x14001CD60` | **block read emulata**: ciclo che chiama lo slot 4 (`call [rax+0x20]` = ReadWord) e impila 2 byte per giro in uno `std::vector` | **niente block hardware** |
|
||||
| 7 | `0x14001CEB0` | **block write emulata**: ciclo che chiama lo slot 3 (`call [rax+0x18]` = WriteWord), impacchettando 2 byte per giro | idem |
|
||||
| 8 | `0x14001D560` | write "nuda": `CMD &= 0xfffc00ff` (azzera `[17:8]`, **conserva BA `[7:0]`, bit 18, 20, 29**), poi `SA` in `[14:8]`, `WRT` in `[15]`, `GO` in `[19]`; nessun dato messo in DATA; indirizzo = `arg1 + arg2` a 8 bit; chiude con `WaitDone(isWrite=1)`. È il **page-select SPD** (SPA0/SPA1) che serve a leggere gli offset > `0xFF` | equivalente al nostro Send Byte, ma **senza** usare `PNTR_SEL` |
|
||||
| 12 | `0x14001D5D0` | `slot % 6` → indice in tabella a `0x14007F938`, passo 4 | mappa slot logico → indirizzo |
|
||||
|
||||
Costruttore (`0x14001A3C5`-`0x14001A496`): crea **esattamente due** istanze, con
|
||||
`(status, cmd, data)` = `(0xA8, 0x9C, 0xB4)` e `(0xAC, 0xA0, 0xB8)`. 🚨 **Nessun altro
|
||||
offset del config space della function 5 viene toccato** in questo percorso: niente
|
||||
registro nascosto da aggiungere al driver.
|
||||
|
||||
**Conclusione: dal materiale letto finora non esce nessuna feature hardware che ci
|
||||
manchi.** Le domande che potrebbero ancora cambiare il driver sono in `TODO.md` §V,
|
||||
item `V3`.
|
||||
|
||||
## 8. 🚨 Un claim di `aura-re` è sbagliato — verificato sul primario
|
||||
|
||||
`aura-re/06-porting-linux.md` §"Percorso 1" dice che *«esiste già un'implementazione Linux
|
||||
funzionante: OpenRGB ha un driver SMBus per il Super I/O Nuvoton»* e cita
|
||||
`i2c_smbus/i2c_smbus_nuvoton_nct6775.cpp`. **Quel file non esiste.** Su
|
||||
`hw/OpenRGB-src` a `master` `8c19428b` (2026-08-08) l'unico backend Nuvoton è
|
||||
`i2c_smbus/MacOS/i2c_smbus_nct6775.cpp` — **solo macOS**, esattamente come dice già
|
||||
`hw/tuf-x299-aura/TODO.md` **B2**. Su Linux la strada resta il modulo out-of-tree
|
||||
`i2c-nct6775` (`openrgb-dkms`) che espone un adapter i2c, non un backend interno di
|
||||
OpenRGB: meccanismo diverso, e in `tuf-x299-aura` è bloccato per decisione di Simone
|
||||
finché `W27` è aperta.
|
||||
|
||||
Morale operativa: `aura-re` è forte dove disassembla (i claim con indirizzo VA reggono lo
|
||||
spot-check), debole dove riassume l'ecosistema Linux. Prendere i primi, verificare i
|
||||
secondi.
|
||||
+188
-5
@@ -25,6 +25,41 @@
|
||||
* The two channels are independent SMBus buses (DIMMs 1,2 on ch0; DIMMs 3,4 on
|
||||
* ch1), hence two i2c_adapter instances exposed as separate /dev/i2c-* nodes.
|
||||
*
|
||||
* DOWNSTREAM BRANCH (guard). Not what is submitted upstream. Two local
|
||||
* additions:
|
||||
*
|
||||
* - SMBus Quick is approximated with a byte-data read of register 0 so
|
||||
* that i2cdetect can scan these buses. The engine always puts a
|
||||
* register byte on the bus, so Quick cannot be implemented faithfully;
|
||||
* a read against a known bus is acceptable here, not as a generic
|
||||
* kernel facility.
|
||||
*
|
||||
* - protect_spd (default on) rejects SMBus writes to the SPD EEPROMs
|
||||
* (0x50-0x57) and to the DDR4 protection command addresses SWPn/CWP
|
||||
* (0x30-0x35) with -EPERM. RSWP cannot be set in-system on this
|
||||
* platform (it needs VHV on SA0), so this is the strongest available
|
||||
* protection against stray SPD writes. The page-select addresses
|
||||
* 0x36/0x37 stay writable: ee1004 needs them for its Send Byte page
|
||||
* select. Toggle at runtime via
|
||||
* /sys/module/i2c_imc_skylake/parameters/protect_spd for deliberate
|
||||
* SPD maintenance.
|
||||
*
|
||||
* - use_ecam (default on): reach the engine registers through a private
|
||||
* ECAM (MMCONFIG) mapping instead of the kernel's legacy config
|
||||
* cycles. For standard config space the kernel uses conf1: two I/O
|
||||
* instructions, index on 0xCF8 and data on 0xCFC. pci_config_lock
|
||||
* serialises that pair against the rest of the kernel but not against
|
||||
* SMM: an SMI landing between the two, whose handler runs its own
|
||||
* CF8/CFC dialogue without restoring the index, silently redirects
|
||||
* the data phase to whatever register the firmware addressed last.
|
||||
* A misdirected read only perturbs one poll iteration and the CMD
|
||||
* write is covered by the landing check below, but the DATA-register
|
||||
* write has no readback, so its payload would land unnoticed in a
|
||||
* foreign config register. A single MMIO access cannot be split by
|
||||
* an SMI, so the window does not exist on the ECAM path. Suspected
|
||||
* (not proven) trigger of the ws1 poweroff hang. Falls back to
|
||||
* config cycles when MCFG has no entry for the device.
|
||||
*
|
||||
* Bus arbitration / concurrency with firmware:
|
||||
* The driver cannot coordinate with SMM, a BMC, or the iMC's own closed-loop
|
||||
* thermal throttling (CLTT), and the PCI ID alone does not prove that none of
|
||||
@@ -44,6 +79,7 @@
|
||||
* to be arbitrated against.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
@@ -148,6 +184,32 @@ module_param(allow_unsafe_access, bool, 0444);
|
||||
MODULE_PARM_DESC(allow_unsafe_access,
|
||||
"allow access without firmware/SMM arbitration (unsafe)");
|
||||
|
||||
/*
|
||||
* Runtime-writable so the handback can be A/B tested against the previous
|
||||
* do-nothing shutdown without rebuilding or reloading: with the parameter
|
||||
* clear, imc_pci_shutdown() is a no-op, byte for byte the pre-1.0.4
|
||||
* behaviour.
|
||||
*/
|
||||
static bool quiesce_on_shutdown = true;
|
||||
module_param(quiesce_on_shutdown, bool, 0644);
|
||||
MODULE_PARM_DESC(quiesce_on_shutdown,
|
||||
"hand the engine back to the firmware quiesced at shutdown");
|
||||
|
||||
static bool protect_spd = true;
|
||||
module_param(protect_spd, bool, 0644);
|
||||
MODULE_PARM_DESC(protect_spd,
|
||||
"reject SMBus writes to SPD addresses 0x30-0x35 and 0x50-0x57 (default on)");
|
||||
|
||||
/*
|
||||
* Load-time only: the accessors pick their path from s->ecam, captured at
|
||||
* probe. Flipping this at runtime would race in-flight transfers for no
|
||||
* benefit; the A/B lever is a reboot away, like the pre-ECAM baseline.
|
||||
*/
|
||||
static bool use_ecam = true;
|
||||
module_param(use_ecam, bool, 0444);
|
||||
MODULE_PARM_DESC(use_ecam,
|
||||
"reach the engine via an ECAM mapping instead of CF8/CFC config cycles (default on)");
|
||||
|
||||
struct imc_chan {
|
||||
unsigned int cmd, data, stat;
|
||||
int idx; /* channel index 0 or 1 */
|
||||
@@ -161,6 +223,8 @@ static const struct imc_chan imc_chans[2] = {
|
||||
/* one driver state object, shared by both per-channel adapters */
|
||||
struct imc_smbus {
|
||||
struct pci_dev *pdev; /* config space carrying the engine */
|
||||
void __iomem *ecam; /* this function's config page, or NULL
|
||||
to fall back to config cycles */
|
||||
struct device *dev; /* &pdev->dev, for dev_*() logging */
|
||||
struct mutex lock; /* serialises all SMBus transactions */
|
||||
/* needed: both channels share one engine */
|
||||
@@ -176,9 +240,69 @@ struct imc_xfer_state {
|
||||
};
|
||||
|
||||
/*
|
||||
* Register accessors. A config-space failure is recorded in s->io_err and
|
||||
* reported once, at the end of the transfer, so the transaction logic does not
|
||||
* have to check a return value on every register touch. A failed read yields
|
||||
* Locate this function's configuration page in the ECAM window described by
|
||||
* the ACPI MCFG table and map it. The arch code keeps its own MCFG mapping
|
||||
* for extended config space but exposes no way to route standard config
|
||||
* space through it, hence the private lookup. Mapping the page a second
|
||||
* time is harmless: it is device MMIO, not RAM.
|
||||
*/
|
||||
static void __iomem *imc_map_ecam(struct pci_dev *pdev)
|
||||
{
|
||||
struct acpi_table_mcfg *mcfg;
|
||||
struct acpi_mcfg_allocation *alloc;
|
||||
void __iomem *map = NULL;
|
||||
unsigned int bus = pdev->bus->number;
|
||||
int i, n;
|
||||
|
||||
if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_MCFG, 0,
|
||||
(struct acpi_table_header **)&mcfg)))
|
||||
return NULL;
|
||||
|
||||
n = (mcfg->header.length - sizeof(*mcfg)) / sizeof(*alloc);
|
||||
alloc = (struct acpi_mcfg_allocation *)(mcfg + 1);
|
||||
for (i = 0; i < n; i++, alloc++) {
|
||||
resource_size_t addr;
|
||||
|
||||
if (alloc->pci_segment != pci_domain_nr(pdev->bus) ||
|
||||
bus < alloc->start_bus_number ||
|
||||
bus > alloc->end_bus_number)
|
||||
continue;
|
||||
|
||||
addr = alloc->address +
|
||||
((resource_size_t)(bus - alloc->start_bus_number) << 20) +
|
||||
((resource_size_t)pdev->devfn << 12);
|
||||
map = devm_ioremap(&pdev->dev, addr, SZ_4K);
|
||||
break;
|
||||
}
|
||||
|
||||
acpi_put_table(&mcfg->header);
|
||||
if (!map)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* A wrong page here would make every engine write land on some other
|
||||
* function - the exact failure mode this path exists to remove.
|
||||
* Word 0 is the one register whose value is known in advance; require
|
||||
* it before trusting the mapping with writes.
|
||||
*/
|
||||
if (readl(map) != (PCU_DEVICE << 16 | PCI_VENDOR_ID_INTEL)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"ECAM page does not identify as %04x:%04x; not using it\n",
|
||||
PCI_VENDOR_ID_INTEL, PCU_DEVICE);
|
||||
devm_iounmap(&pdev->dev, map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register accessors. On the ECAM path a register touch is a single MMIO
|
||||
* access; it has no failure channel, and the function sits on the CPU die,
|
||||
* so it cannot vanish and produce the all-ones of a master abort. On the
|
||||
* config-cycle fallback a failure is recorded in s->io_err and reported
|
||||
* once, at the end of the transfer, so the transaction logic does not have
|
||||
* to check a return value on every register touch. A failed read yields
|
||||
* all-ones, which no polling loop mistakes for a completed transaction.
|
||||
*/
|
||||
static u32 imc_reg(struct imc_smbus *s, unsigned int off)
|
||||
@@ -186,6 +310,9 @@ static u32 imc_reg(struct imc_smbus *s, unsigned int off)
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (s->ecam)
|
||||
return readl(s->ecam + off);
|
||||
|
||||
ret = pci_read_config_dword(s->pdev, off, &val);
|
||||
if (ret) {
|
||||
s->io_err = pcibios_err_to_errno(ret);
|
||||
@@ -199,6 +326,11 @@ static void imc_set(struct imc_smbus *s, unsigned int off, u32 val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (s->ecam) {
|
||||
writel(val, s->ecam + off);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = pci_write_config_dword(s->pdev, off, val);
|
||||
if (ret)
|
||||
s->io_err = pcibios_err_to_errno(ret);
|
||||
@@ -503,6 +635,25 @@ static s32 imc_smbus_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
if (addr > 0x7f)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Downstream-only, see the note at the top of the file: approximate
|
||||
* Quick with a byte-data read of register 0 so that i2cdetect can
|
||||
* scan. Read rather than write, so a probe of an unknown address
|
||||
* cannot store anything.
|
||||
*/
|
||||
if (size == I2C_SMBUS_QUICK) {
|
||||
mutex_lock(&s->lock);
|
||||
s->io_err = 0;
|
||||
ret = imc_access(s, c, addr, 0, I2C_SMBUS_READ,
|
||||
I2C_SMBUS_BYTE_DATA, &val);
|
||||
if (s->io_err)
|
||||
ret = s->io_err;
|
||||
mutex_unlock(&s->lock);
|
||||
dev_dbg(s->dev, "ch%d QUICK addr=%02x (ret %d)\n",
|
||||
c->idx, addr, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (size != I2C_SMBUS_BYTE && size != I2C_SMBUS_BYTE_DATA &&
|
||||
size != I2C_SMBUS_WORD_DATA)
|
||||
return -EOPNOTSUPP;
|
||||
@@ -515,6 +666,22 @@ static s32 imc_smbus_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
*/
|
||||
reg = size == I2C_SMBUS_BYTE ? 0 : command;
|
||||
|
||||
/*
|
||||
* Downstream-only: refuse writes to the SPD EEPROMs and to the DDR4
|
||||
* protection command addresses. Send Byte writes to 0x50-0x57 are
|
||||
* caught too; on an EE1004 they would only move the address pointer,
|
||||
* but there is no caller that needs them. 0x36/0x37 (page select)
|
||||
* stay writable.
|
||||
*/
|
||||
if (protect_spd && read_write == I2C_SMBUS_WRITE &&
|
||||
((addr >= 0x50 && addr <= 0x57) ||
|
||||
(addr >= 0x30 && addr <= 0x35))) {
|
||||
dev_warn_ratelimited(s->dev,
|
||||
"ch%d write to addr 0x%02x reg 0x%02x blocked by protect_spd\n",
|
||||
c->idx, addr, reg);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
mutex_lock(&s->lock);
|
||||
s->io_err = 0;
|
||||
|
||||
@@ -577,8 +744,8 @@ static s32 imc_smbus_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
|
||||
static u32 imc_func(struct i2c_adapter *adap)
|
||||
{
|
||||
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_WORD_DATA;
|
||||
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
||||
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm imc_algo = {
|
||||
@@ -713,6 +880,9 @@ static void imc_pci_shutdown(struct pci_dev *pdev)
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
if (!quiesce_on_shutdown)
|
||||
return;
|
||||
|
||||
/* refuse new transfers, then drain the one possibly in flight */
|
||||
for (i = 0; i < ARRAY_SIZE(s->adap); i++)
|
||||
i2c_mark_adapter_suspended(&s->adap[i]);
|
||||
@@ -764,6 +934,19 @@ static int imc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Before the first register touch, so that even the pristine-word
|
||||
* snapshot below goes through the SMI-safe path.
|
||||
*/
|
||||
if (use_ecam) {
|
||||
s->ecam = imc_map_ecam(pdev);
|
||||
if (!s->ecam)
|
||||
dev_warn(&pdev->dev,
|
||||
"no usable MCFG entry; falling back to CF8/CFC config cycles\n");
|
||||
}
|
||||
dev_dbg(&pdev->dev, "engine access via %s\n",
|
||||
s->ecam ? "ECAM MMIO" : "CF8/CFC config cycles");
|
||||
|
||||
/*
|
||||
* Pristine command words: the state the firmware left in the engine at
|
||||
* boot, captured before this driver's first transfer. They are handed
|
||||
|
||||
@@ -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())
|
||||
@@ -0,0 +1,2 @@
|
||||
2026-08-09 11:01 use_ecam=Y srcversion=C1EC295E03F1D2060EBBB3B blocks=10 warns=0 spd_ok=1
|
||||
2026-08-09 12:49 use_ecam=Y srcversion=C1EC295E03F1D2060EBBB3B blocks=10 warns=0 spd_ok=1
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/bin/bash
|
||||
# pre-poweroff-load.sh - fixed SMBus load protocol for the A5 hang observation
|
||||
# window (TODO.md E1), fully automatic: runs the load, saves observations,
|
||||
# powers the machine off.
|
||||
#
|
||||
# Mixed, deterministic workload so that every poweroff in the window carries
|
||||
# the same, high and *diverse* bus exposure - both in the use_ecam=1 phase and
|
||||
# in the use_ecam=0 counter-proof phase. Each 3-minute block:
|
||||
#
|
||||
# 1. software effect (~2760 SMBus writes/s, steady stream) ~100 s
|
||||
# 2. rapid hw/sw switch burst (6 alternations back-to-back:
|
||||
# one-shot config-write bursts + apply latches) ~20 s
|
||||
# 3. hardware effect (engine-side, bus quiet) ~30 s
|
||||
# 4. full SPD dump of all 4 DIMMs via ee1004 (reads +
|
||||
# page-selects) + serial integrity check ~30 s
|
||||
#
|
||||
# Effects rotate through fixed lists, so run N of phase A and run N of phase B
|
||||
# execute the identical sequence.
|
||||
#
|
||||
# End of run: kernel-journal warnings and run context are saved to a per-run
|
||||
# file in tools/pre-poweroff-runs/, one summary line is appended to
|
||||
# pre-poweroff-load.log, and - only with --poweroff - the machine POWERS OFF
|
||||
# (15 s grace, Ctrl-C to abort). The last software effect is left running into
|
||||
# the poweroff on purpose: exposure should cover the shutdown path too; the
|
||||
# systemd boot scene restores the normal LED state at next boot.
|
||||
#
|
||||
# The one exception: an SPD serial mismatch saves everything and does NOT
|
||||
# power off - investigate before shutting down.
|
||||
#
|
||||
# Usage: sudo ./pre-poweroff-load.sh [minutes] [--poweroff]
|
||||
# (default 30 minutes, in 3-min blocks; re-execs itself under sudo if
|
||||
# started as a plain user; powers off at the end ONLY with --poweroff)
|
||||
|
||||
set -eu
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || exec sudo "$0" "$@"
|
||||
|
||||
MINUTES=30
|
||||
POWEROFF=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--poweroff) POWEROFF=1 ;;
|
||||
*) MINUTES="$arg" ;;
|
||||
esac
|
||||
done
|
||||
BLOCKS=$(((MINUTES + 2) / 3))
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
LOG="$DIR/pre-poweroff-load.log"
|
||||
RUNDIR="$DIR/pre-poweroff-runs"
|
||||
START="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
RUNFILE="$RUNDIR/$(date '+%Y%m%d-%H%M').log"
|
||||
|
||||
ANIMS=(aurora wave_rainbow plasma ripple gradient_pulse fire)
|
||||
FX=(respiro arcobaleno cometa battito ciclico lampeggio)
|
||||
# SPD EEPROMs at 0x50/0x52 (ee1004); i2c bus numbers depend on module load
|
||||
# order, so discover them instead of hardcoding bus IDs.
|
||||
mapfile -t EEPROMS < <(ls /sys/bus/i2c/devices/*-005[02]/eeprom 2>/dev/null | sort)
|
||||
[ "${#EEPROMS[@]}" -eq 4 ] || { echo "ERROR: expected 4 SPD EEPROMs, found ${#EEPROMS[@]}"; exit 1; }
|
||||
|
||||
MODE="$(cat /sys/module/i2c_imc_skylake/parameters/use_ecam 2>/dev/null || echo '?')"
|
||||
SRCV="$(cat /sys/module/i2c_imc_skylake/srcversion 2>/dev/null || echo '?')"
|
||||
|
||||
mkdir -p "$RUNDIR"
|
||||
|
||||
spd_serials() {
|
||||
local e
|
||||
for e in "${EEPROMS[@]}"; do
|
||||
dd if="$e" bs=1 skip=325 count=4 2>/dev/null | xxd -p
|
||||
done
|
||||
}
|
||||
|
||||
echo "== pre-poweroff load: ${BLOCKS} blocks (~$((BLOCKS * 3)) min), use_ecam=${MODE}, srcversion=${SRCV}"
|
||||
if [ "$POWEROFF" -eq 1 ]; then
|
||||
echo "== POWERS OFF at the end. Run file: ${RUNFILE}"
|
||||
else
|
||||
echo "== no poweroff at the end (use --poweroff). Run file: ${RUNFILE}"
|
||||
fi
|
||||
|
||||
REF_SERIALS="$(spd_serials)"
|
||||
echo "== SPD serial reference: $(echo "$REF_SERIALS" | tr '\n' ' ')"
|
||||
|
||||
SPD_BAD=0
|
||||
for ((b = 0; b < BLOCKS; b++)); do
|
||||
anim="${ANIMS[b % ${#ANIMS[@]}]}"
|
||||
fx="${FX[b % ${#FX[@]}]}"
|
||||
echo "-- block $((b + 1))/${BLOCKS}: anim=${anim} fx=${fx}"
|
||||
|
||||
furyrs anim "$anim"
|
||||
sleep 100
|
||||
|
||||
# rapid hw/sw alternation burst
|
||||
for ((i = 0; i < 3; i++)); do
|
||||
furyrs effect "${FX[(b + i) % ${#FX[@]}]}"
|
||||
sleep 2
|
||||
furyrs anim "${ANIMS[(b + i) % ${#ANIMS[@]}]}"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
furyrs effect "$fx"
|
||||
sleep 30
|
||||
|
||||
# full SPD dump (reads + ee1004 page-selects) + integrity check
|
||||
for e in "${EEPROMS[@]}"; do
|
||||
dd if="$e" bs=64 2>/dev/null >/dev/null
|
||||
done
|
||||
if [ "$(spd_serials)" != "$REF_SERIALS" ]; then
|
||||
SPD_BAD=1
|
||||
echo "!! SPD SERIAL MISMATCH in block $((b + 1)) - stopping, will NOT power off"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# leave a steady software effect running into the poweroff
|
||||
furyrs anim "${ANIMS[0]}"
|
||||
|
||||
# Only warning-and-up: the dev_dbg per-transfer traffic is not an anomaly.
|
||||
WARNS="$(journalctl -k -p warning --since "$START" --no-pager 2>/dev/null | grep -i 'i2c-imc' || true)"
|
||||
N="$(printf '%s' "$WARNS" | grep -c . || true)"
|
||||
|
||||
{
|
||||
echo "start: ${START}"
|
||||
echo "end: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "use_ecam: ${MODE}"
|
||||
echo "srcversion: ${SRCV}"
|
||||
echo "blocks: ${BLOCKS}"
|
||||
echo "spd_ok: $((1 - SPD_BAD))"
|
||||
echo "spd_serials_ref:"
|
||||
echo "$REF_SERIALS"
|
||||
echo "driver_warnings: ${N}"
|
||||
[ "$N" -gt 0 ] && echo "$WARNS"
|
||||
} >"$RUNFILE"
|
||||
|
||||
echo "$(date '+%Y-%m-%d %H:%M') use_ecam=${MODE} srcversion=${SRCV} blocks=${BLOCKS} warns=${N} spd_ok=$((1 - SPD_BAD))" >>"$LOG"
|
||||
[ -n "${SUDO_UID:-}" ] && chown "$SUDO_UID:${SUDO_GID:-$SUDO_UID}" "$RUNFILE" "$LOG" "$RUNDIR"
|
||||
sync
|
||||
|
||||
if [ "$SPD_BAD" -ne 0 ]; then
|
||||
echo "== SPD integrity FAILED - observations in ${RUNFILE} - NOT powering off."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$N" -gt 0 ]; then
|
||||
echo "== ${N} driver warning(s) - SIGNAL, saved in ${RUNFILE}:"
|
||||
echo "$WARNS"
|
||||
else
|
||||
echo "== clean run, no driver warnings."
|
||||
fi
|
||||
|
||||
if [ "$POWEROFF" -eq 1 ]; then
|
||||
echo "== powering off in 15 s (Ctrl-C to abort) ..."
|
||||
sleep 15
|
||||
systemctl poweroff
|
||||
else
|
||||
echo "== done, NOT powering off (pass --poweroff to shut down at the end)."
|
||||
fi
|
||||
@@ -0,0 +1,12 @@
|
||||
start: 2026-08-09 10:38:00
|
||||
end: 2026-08-09 11:01:56
|
||||
use_ecam: Y
|
||||
srcversion: C1EC295E03F1D2060EBBB3B
|
||||
blocks: 10
|
||||
spd_ok: 1
|
||||
spd_serials_ref:
|
||||
50e4333a
|
||||
1fe43236
|
||||
50e434ba
|
||||
1ee433b5
|
||||
driver_warnings: 0
|
||||
@@ -0,0 +1,12 @@
|
||||
start: 2026-08-09 12:25:06
|
||||
end: 2026-08-09 12:49:02
|
||||
use_ecam: Y
|
||||
srcversion: C1EC295E03F1D2060EBBB3B
|
||||
blocks: 10
|
||||
spd_ok: 1
|
||||
spd_serials_ref:
|
||||
50e4333a
|
||||
1fe43236
|
||||
50e434ba
|
||||
1ee433b5
|
||||
driver_warnings: 0
|
||||
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