Files
i2c-imc-skylake/tools/lore-thread-check.py
T
Simone 2c7675a104 tools: measure the ENE read-back question instead of inferring it
P2 asked whether the ENE at 0x27 has readable state. The answer had been
reached twice by elimination, and the last link in that chain was a comment
in a third party's patched OpenRGB detector, not a measurement of ours.

probe-ene-receive-byte.sh runs the experiment the driver made possible on
2026-07-27, when PNTR_SEL (BIT(18)) gave us a bare Receive Byte: a read with
no command byte on the wire cannot return a transform of a register that was
never sent. It does, however, return the last command byte the chip received
— 12/12 on both channels — so the value is still ours, not the device's. A
Receive Byte taken from 0x31 after addressing 0x27 answers 0xff instead of
the echo, which places the latch in the device and not in the iMC engine;
empty addresses NACK, so the transaction is real.

Everything here is read-only: Receive Byte and Read Byte Data at 0x27, SPD
reads through ee1004 sysfs, and the four SPD serials compared before and
after every run.

Two engine behaviours the script has to work around, both measured:
the first transaction on a channel answers with a stale value (hence the
discarded warm-up read), and a read following a successful one usually
NACKs, which one retry always clears — the P1 policy, confirmed.

lore-thread-check.py is unrelated to the probe but shares the same motive:
lore and patchwork now sit behind an Anubis bot check, so a shell cannot
tell "not published" from "not allowed to look". The NNTP mirror can.
2026-08-10 18:49:56 +02:00

136 lines
4.5 KiB
Python
Executable File

#!/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())