DF-5654 / elcrx.c
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | /* DF-5652 / DF-5653 / DF-5654 โ ELCR (sys/platform/pc64/icu/elcr.c) live * introspection + trigger-mode protocol harness (DragonFly pc64 KLD). * * Loaded as root via kldload(8) on the audit QEMU/i440fx guest * (X86_64_GENERIC, INVARIANTS kernel, IOAPIC MachIntrABI โ the ELCR * is a live boot-time device at ports 0x4d0/0x4d1 either way). * * Phases (destructive phases touch ONLY the ELCR ports and restore exactly): * * E0: environment โ ncpu, ABI type, elcr_found, hardware ELCR read, * shadow-vs-hardware coherence table via elcr_read_trigger(0..15). * * E1: DF-5652 read path โ the bound check at elcr.c:89 is * KASSERT(irq < ISA_IRQ_CNT) with NO signed lower bound and is * INVARIANTS-only anyway (sys/sys/systm.h:94-122). Negative irqs * are accepted even on THIS INVARIANTS kernel: elcr_read_trigger() * evaluates the C-undefined shift elcr_status & (1 << irq) * (elcr.c:57,90). -22 aliases bit 10 (1 << (-22 & 31) == 1 << 10 * on x86): trigger(-22) must equal trigger(10) โ self-proving. * Read path never touches ports -> zero hardware risk. * * E2: DF-5652 write path โ elcr_write_trigger(-27, LEVEL) passes the * assert (-27 < 16), materializes 1 << -27 as 1 << 5, mutates the * elcr_status shadow AND PROGRAMS THE LIVE ELCR PORT 0x4d0 * (elcr.c:103-117). IRQ5 is a masked, unused line on this rig; * state restored exactly afterwards. * * R: DF-5653 โ elcr_write_trigger() performs an unlocked * read-modify-write of the global elcr_status shadow * (elcr.c:103-117; caller icu_abi_legacy_intr_config holds no * lock either). Two kernel threads pinned to cpu0/cpu1 toggle * irq5/irq7 (same 32-bit shadow word, same 0x4d0 byte) * LEVEL<->EDGE N times each. A read-back != just-written mode * means the peer's stale-word store clobbered our bit: a lost * update, i.e. a silently dropped trigger-mode change. * * D: DF-5654 โ no resync path exists: elcr_status is read from the * hardware exactly once (elcr_probe, elcr.c:75) and never again. * Flip irq5's bit in the HARDWARE with a raw outb(0x4d0): * elcr_read_trigger(5) keeps serving the stale shadow (divergence * invisible to the kernel). Then call elcr_resume() (elcr.c:120, * ZERO in-tree callers โ dead code) and watch it repair the * hardware to the shadow exactly: the missing wiring, proven. * * E9: final coherence vs the E0 baseline, restore verification, * liveness (ticks advance). */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/types.h> #include <sys/bus.h> #include <sys/kthread.h> #include <machine/atomic.h> #include <sys/machintr.h> #include <sys/thread2.h> #include <machine/cpufunc.h> /* kernel symbols (all .globl; elcr_var.h decls inlined to avoid * machine_base include-path games in the kmod build) */ extern int elcr_found; /* elcr.c:59 */ extern enum intr_trigger elcr_read_trigger(int); /* elcr.c:85 */ extern void elcr_write_trigger(int, enum intr_trigger); /* elcr.c:100 */ extern void elcr_resume(void); /* elcr.c:120 */ #define ELCR_PORT 0x4d0 /* elcr.c:56 */ #define IRQ_A 5 /* masked/unused on rig */ #define IRQ_B 7 /* masked/unused on rig */ #define RACE_ITERS 300000 static volatile int race_done; static volatile int lost_a, lost_b; static volatile int final_a = -1, final_b = -1; static int race_arg_a = IRQ_A; static int race_arg_b = IRQ_B; static int read_elcr_hw(void) { return (inb(ELCR_PORT) | (inb(ELCR_PORT + 1) << 8)); } static const char * trig_str(enum intr_trigger t) { return (t == INTR_TRIGGER_LEVEL ? "L" : (t == INTR_TRIGGER_EDGE ? "E" : "?")); } /* * Race worker: toggle one irq's trigger mode RACE_ITERS times on a * pinned CPU. After every write, read the shadow back; a mismatch is a * lost update caused by the peer's unlocked stale-word store. * NOTE: no start barrier (v1 starved the flag-setting kldload thread on * cpu0 with never-yielding spin threads -> guest wedge, wedge_v1.log); * the 300k-iteration loops overlap naturally, and lwkt_yield() every * 16k iterations keeps the CPU available to the scheduler. */ static void race_fn(void *arg) { int irq = *(int *)arg; int i, mode, lost = 0; for (i = 0; i < RACE_ITERS; ++i) { if ((i & 16383) == 16384 - 1) lwkt_yield(); /* keep the CPU shareable */ mode = (i & 1) ? INTR_TRIGGER_EDGE : INTR_TRIGGER_LEVEL; elcr_write_trigger(irq, mode); if (elcr_read_trigger(irq) != mode) ++lost; /* our bit was clobbered */ } if (irq == IRQ_A) { lost_a = lost; final_a = mode; } else { lost_b = lost; final_b = mode; } atomic_add_int(&race_done, 1); wakeup((void *)(intptr_t)&race_done); kthread_exit(); } static int elcrx_modevent(module_t mod, int type, void *data) { int hw_base, hw_pre, hw_now, error; int b5, i, shadow_bits, waited; enum intr_trigger t, m5, m7; switch (type) { case MOD_LOAD: break; case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } /* ---------------- E0: environment + coherence baseline ---------- */ hw_base = read_elcr_hw(); shadow_bits = 0; for (i = 0; i < 16; ++i) if (elcr_read_trigger(i) == INTR_TRIGGER_LEVEL) shadow_bits |= 1 << i; kprintf("E0: ncpu=%d mycpuid=%d MachIntrABI.type=%d " "(ICU=1 IOAPIC=2) elcr_found=%d\n", ncpus, mycpuid, MachIntrABI.type, elcr_found); kprintf("E0: hw ELCR=0x%04x shadow LEVEL-bits=0x%04x coherent=%s\n", hw_base, shadow_bits, (hw_base & 0xffff) == shadow_bits ? "YES" : "NO"); /* ---------------- E1: DF-5652 read path, negative irq ------------ */ t = elcr_read_trigger(-1); /* 1<<(-1&31)=1<<31: expect E */ kprintf("E1: elcr_read_trigger(-1)=%s (no panic: negative irq " "ACCEPTED, assert only checks irq<16)\n", trig_str(t)); kprintf("E1: elcr_read_trigger(-22)=%s vs elcr_read_trigger(10)=%s " "-> %s (1<<-22 materialized as 1<<10 on x86)\n", trig_str(elcr_read_trigger(-22)), trig_str(elcr_read_trigger(10)), elcr_read_trigger(-22) == elcr_read_trigger(10) ? "ALIAS-PROVEN" : "differs"); kprintf("E1: hw still 0x%04x (read path touches no ports)\n", read_elcr_hw()); /* ---------------- E2: DF-5652 write path, negative irq ------------ */ b5 = elcr_read_trigger(IRQ_A); hw_pre = inb(ELCR_PORT); elcr_write_trigger(IRQ_A - 32, INTR_TRIGGER_LEVEL); /* -27 */ hw_now = inb(ELCR_PORT); kprintf("E2: elcr_write_trigger(-27, LEVEL): hw0x4d0 0x%02x -> 0x%02x " "(bit5 %s), shadow trigger(5)=%s\n", hw_pre, hw_now, ((hw_now >> IRQ_A) & 1) ? "SET" : "clear", trig_str(elcr_read_trigger(IRQ_A))); elcr_write_trigger(IRQ_A, (enum intr_trigger)b5); /* restore */ kprintf("E2: restored: hw0x4d0=0x%02x (was 0x%02x) trigger(5)=%s " "(was %s) ok=%s\n", inb(ELCR_PORT), hw_pre, trig_str(elcr_read_trigger(IRQ_A)), trig_str(b5), (inb(ELCR_PORT) == hw_pre && elcr_read_trigger(IRQ_A) == b5) ? "YES" : "NO"); /* ---------------- R: DF-5653 unlocked shadow RMW race ------------- */ m5 = elcr_read_trigger(IRQ_A); m7 = elcr_read_trigger(IRQ_B); hw_pre = read_elcr_hw(); lost_a = lost_b = 0; final_a = final_b = -1; race_done = 0; kprintf("R0: baseline trigger(5)=%s trigger(7)=%s hw=0x%04x " "iters/thread=%d\n", trig_str(m5), trig_str(m7), hw_pre, RACE_ITERS); error = kthread_create_cpu(race_fn, &race_arg_a, NULL, 0, "elcrx_a"); if (error == 0) error = kthread_create_cpu(race_fn, &race_arg_b, NULL, 1, "elcrx_b"); if (error) { kprintf("R: kthread_create_cpu failed: %d\n", error); goto phase_d; } waited = 0; while (race_done < 2 && waited < 120) { /* <=120 s guard */ tsleep((void *)(intptr_t)&race_done, 0, "elcrxw", hz); ++waited; } kprintf("R1: done=%d waited=%ds lost_irq5=%d lost_irq7=%d " "(read-back mismatches = lost trigger-mode updates)\n", race_done, waited, lost_a, lost_b); kprintf("R1: final shadow trigger(5)=%s (last written %s) " "trigger(7)=%s (last written %s)\n", trig_str(elcr_read_trigger(IRQ_A)), trig_str((enum intr_trigger)final_a), trig_str(elcr_read_trigger(IRQ_B)), trig_str((enum intr_trigger)final_b)); elcr_write_trigger(IRQ_A, m5); /* restore baseline */ elcr_write_trigger(IRQ_B, m7); kprintf("R2: restored: hw=0x%04x (was 0x%04x) trigger(5)=%s " "trigger(7)=%s ok=%s\n", read_elcr_hw(), hw_pre, trig_str(elcr_read_trigger(IRQ_A)), trig_str(elcr_read_trigger(IRQ_B)), (read_elcr_hw() == hw_pre && elcr_read_trigger(IRQ_A) == m5 && elcr_read_trigger(IRQ_B) == m7) ? "YES" : "NO"); phase_d: /* ---------------- D: DF-5654 no-resync / dead elcr_resume --------- */ b5 = inb(ELCR_PORT); /* shadow==hw here */ outb(ELCR_PORT, b5 ^ (1 << IRQ_A)); /* HW-only divergence */ kprintf("D1: raw outb flipped hw bit5: hw0x4d0=0x%02x but " "elcr_read_trigger(5)=%s -> kernel serves STALE shadow " "(no resync path exists; elcr_status read once at probe)\n", inb(ELCR_PORT), trig_str(elcr_read_trigger(IRQ_A))); elcr_resume(); /* ZERO in-tree callers โ dead code */ kprintf("D2: after elcr_resume(): hw=0x%04x baseline=0x%04x " "repaired=%s (the dead function is exactly the missing fix)\n", read_elcr_hw(), hw_base, read_elcr_hw() == hw_base ? "YES" : "NO"); /* ---------------- E9: final state --------------------------------- */ kprintf("E9: final hw=0x%04x baseline=0x%04x coherent=%s " "ticks=%d -> guest %s\n", read_elcr_hw(), hw_base, read_elcr_hw() == hw_base ? "YES" : "NO", ticks, read_elcr_hw() == hw_base ? "HEALTHY" : "DIRTY"); return (0); } static moduledata_t elcrx_mod = { "elcrx", elcrx_modevent, NULL }; DECLARE_MODULE(elcrx, elcrx_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |