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/
|
patches/
|
||||||
|
|
||||||
# Internal working documents (not for public repo)
|
# 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
|
TODO.md
|
||||||
|
STORICO.md
|
||||||
docs/archive-submission-analysis.md
|
docs/archive-submission-analysis.md
|
||||||
README.local.md
|
README.local.md
|
||||||
AGENTS.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
|
* 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.
|
* 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:
|
* Bus arbitration / concurrency with firmware:
|
||||||
* The driver cannot coordinate with SMM, a BMC, or the iMC's own closed-loop
|
* 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
|
* thermal throttling (CLTT), and the PCI ID alone does not prove that none of
|
||||||
@@ -44,6 +79,7 @@
|
|||||||
* to be arbitrated against.
|
* to be arbitrated against.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/acpi.h>
|
||||||
#include <linux/bits.h>
|
#include <linux/bits.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
@@ -148,6 +184,32 @@ module_param(allow_unsafe_access, bool, 0444);
|
|||||||
MODULE_PARM_DESC(allow_unsafe_access,
|
MODULE_PARM_DESC(allow_unsafe_access,
|
||||||
"allow access without firmware/SMM arbitration (unsafe)");
|
"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 {
|
struct imc_chan {
|
||||||
unsigned int cmd, data, stat;
|
unsigned int cmd, data, stat;
|
||||||
int idx; /* channel index 0 or 1 */
|
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 */
|
/* one driver state object, shared by both per-channel adapters */
|
||||||
struct imc_smbus {
|
struct imc_smbus {
|
||||||
struct pci_dev *pdev; /* config space carrying the engine */
|
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 device *dev; /* &pdev->dev, for dev_*() logging */
|
||||||
struct mutex lock; /* serialises all SMBus transactions */
|
struct mutex lock; /* serialises all SMBus transactions */
|
||||||
/* needed: both channels share one engine */
|
/* 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
|
* Locate this function's configuration page in the ECAM window described by
|
||||||
* reported once, at the end of the transfer, so the transaction logic does not
|
* the ACPI MCFG table and map it. The arch code keeps its own MCFG mapping
|
||||||
* have to check a return value on every register touch. A failed read yields
|
* 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.
|
* all-ones, which no polling loop mistakes for a completed transaction.
|
||||||
*/
|
*/
|
||||||
static u32 imc_reg(struct imc_smbus *s, unsigned int off)
|
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;
|
u32 val;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (s->ecam)
|
||||||
|
return readl(s->ecam + off);
|
||||||
|
|
||||||
ret = pci_read_config_dword(s->pdev, off, &val);
|
ret = pci_read_config_dword(s->pdev, off, &val);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
s->io_err = pcibios_err_to_errno(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;
|
int ret;
|
||||||
|
|
||||||
|
if (s->ecam) {
|
||||||
|
writel(val, s->ecam + off);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ret = pci_write_config_dword(s->pdev, off, val);
|
ret = pci_write_config_dword(s->pdev, off, val);
|
||||||
if (ret)
|
if (ret)
|
||||||
s->io_err = pcibios_err_to_errno(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)
|
if (addr > 0x7f)
|
||||||
return -EINVAL;
|
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 &&
|
if (size != I2C_SMBUS_BYTE && size != I2C_SMBUS_BYTE_DATA &&
|
||||||
size != I2C_SMBUS_WORD_DATA)
|
size != I2C_SMBUS_WORD_DATA)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
@@ -515,6 +666,22 @@ static s32 imc_smbus_xfer(struct i2c_adapter *adap, u16 addr,
|
|||||||
*/
|
*/
|
||||||
reg = size == I2C_SMBUS_BYTE ? 0 : command;
|
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);
|
mutex_lock(&s->lock);
|
||||||
s->io_err = 0;
|
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)
|
static u32 imc_func(struct i2c_adapter *adap)
|
||||||
{
|
{
|
||||||
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
||||||
I2C_FUNC_SMBUS_WORD_DATA;
|
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct i2c_algorithm imc_algo = {
|
static const struct i2c_algorithm imc_algo = {
|
||||||
@@ -713,6 +880,9 @@ static void imc_pci_shutdown(struct pci_dev *pdev)
|
|||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!quiesce_on_shutdown)
|
||||||
|
return;
|
||||||
|
|
||||||
/* refuse new transfers, then drain the one possibly in flight */
|
/* refuse new transfers, then drain the one possibly in flight */
|
||||||
for (i = 0; i < ARRAY_SIZE(s->adap); i++)
|
for (i = 0; i < ARRAY_SIZE(s->adap); i++)
|
||||||
i2c_mark_adapter_suspended(&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)
|
if (ret)
|
||||||
return 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
|
* Pristine command words: the state the firmware left in the engine at
|
||||||
* boot, captured before this driver's first transfer. They are handed
|
* 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 |
|
| `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` |
|
| `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)
|
## 1. `watch-engine.sh` — foreign activity (read-only)
|
||||||
|
|
||||||
Samples the six per-channel registers and reports every change that this
|
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
|
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
|
OpenRGB's `DDR4DirectAccessor::set_page()`, both select a page and leave it
|
||||||
selected.
|
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