DF-5617 / latch_dance.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | #!/usr/bin/env python3 # DF-5617 latch_dance.py — play the DW I2C ENABLE_STATUS latch from the host. # # ig4_iic.c set_controller() (lines 112-134) writes IG4_REG_I2C_EN (0x6C) and # polls IG4_REG_ENABLE_STATUS (0x9C) until bit0 (IG4_I2C_ENABLE) matches the # written value. The planted device's MMIO window is the SSDT's own pad # buffer (plain guest RAM), so the latch does not exist in "hardware"; this # script supplies it by flipping the dword at guest-physical # window+0x9C = 0xBFFE1E7C right after the kernel prints the "comp_version" # attach line (ig4_iic.c:536), just before the three set_controller() gates # at ig4_iic.c:632/637/642: # # bit0 must read 0 for set_controller(sc, 0) (seeded 0; passes fast) # bit0 must read 1 for set_controller(sc, ENABLE) (we set 1 -> passes) # bit0 must read 0 for set_controller(sc, 0) (we clear -> passes) # # Then ig4iic_attach() reaches lwkt_serialize_enter (ig4_iic.c:648) + # bus_setup_intr(GSI 4) which fails on register_int's exclusive-owner check # against the live sio0 handler (kern_intr.c:276/284) -> serializer leaked -> # ig4_acpi.c:134 ig4iic_acpi_detach() -> ig4_iic.c:678 lwkt_serialize_enter() # re-entry -> INVARIANTS KKASSERT panic (hard self-deadlock without). # # Physical write path: QEMU monitor "gpa2hva" + /proc/<qemu-pid>/mem # (ptrace_scope=0 on this host; qemu runs as the same uid). The hva is # resolved BEFORE the marker watch so the reaction latency after the serial # marker is a single pwrite (~microseconds). import socket, sys, time, os, struct, re, json CFG = json.load(open(os.path.join(os.path.dirname(__file__), "window.json"))) W0 = CFG["w0"] # first register-file window (guest-physical) GPA_STATUS = [W0 + 0x9C, W0 + 0x1000 + 0x9C] # both IG4_REG_ENABLE_STATUS dwords BOOTLOG = "dfbsd-qemu/boot.log" MONSOCK = "dfbsd-qemu/boot-mon.sock" MARKER = b"comp_version" def mon(cmds, wait=0.08): s = socket.socket(socket.AF_UNIX) s.connect(MONSOCK) time.sleep(0.08) try: s.recv(65536) except Exception: pass out = [] for c in cmds: s.sendall((c + "\n").encode()) time.sleep(wait) s.setblocking(False) buf = b"" try: while True: d = s.recv(65536) if not d: break buf += d except Exception: pass s.setblocking(True) out.append(buf.decode(errors="replace").replace("\x1b[K","").replace("\x1b[D","").replace("\r","")) s.close() return out def main(): pid = None with open("dfbsd-qemu/vm.pid") as f: pid = int(f.read().strip()) print(f"[dance] qemu pid {pid}; windows: " + ", ".join(hex(g) for g in GPA_STATUS), flush=True) # pre-resolve gpa -> hva for both status dwords (guest RAM exists from t=0) hvas = [] for g in GPA_STATUS: (hva,) = mon([f"gpa2hva {g:#x}"]) m = re.search(r"Host virtual address for.* is (0x[0-9a-f]+)", hva) if not m: print(f"[dance] gpa2hva failed for {g:#x}: {hva!r}", flush=True) return 1 hvas.append(int(m.group(1), 16)) print(f"[dance] hvas: " + ", ".join(hex(h) for h in hvas), flush=True) mem = open(f"/proc/{pid}/mem", "r+b", 0) def wr(val): for h in hvas: os.pwrite(mem.fileno(), struct.pack("<I", val), h) # wait for the attach marker on the serial console size = 0 t0 = time.time() while time.time() - t0 < 240: try: st = os.stat(BOOTLOG) if st.st_size < size: size = 0 # truncated -> new boot with open(BOOTLOG, "rb") as f: f.seek(size) data = f.read() size += len(data) if MARKER in data: print(f"[dance] marker seen at serial offset {size-len(data)}; " f"t={time.time()-t0:.1f}s", flush=True) break except FileNotFoundError: pass time.sleep(0.005) else: print("[dance] MARKER NOT SEEN", flush=True) return 1 # square wave, 120ms half period, 4.6s total: covers hz=100 (1s windows) # across TWO sequential device attaches. for _ in range(19): wr(1); time.sleep(0.12) wr(0); time.sleep(0.12) wr(0) print("[dance] latch sequence done; watching for setup-irq error/panic", flush=True) size2 = os.path.getsize(BOOTLOG) t0 = time.time() while time.time() - t0 < 60: time.sleep(0.5) with open(BOOTLOG, "rb") as f: f.seek(size2) data = f.read() size2 += len(data) if b"Unable to setup irq" in data: print("[dance] SEEN: Unable to setup irq (ig4_iic.c:652)", flush=True) if b"panic" in data or b"lwkt_serialize_enter" in data: print("[dance] SEEN: panic / serializer assert -- REPRODUCED", flush=True) return 0 if b"controller error during attach" in data: print("[dance] latch missed (clean ENXIO path) -- retry needed", flush=True) return 2 return 3 if __name__ == "__main__": sys.exit(main()) |