DF-5616 / icuintro.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 | /* DF-5616 โ live ICU introspection + icu_ioapic_extint(-1) harness (DragonFly pc64 KLD) * * Loaded as root via kldload(8) on the audit QEMU guest (X86_64_GENERIC, * ICU is the live MachIntrABI โ every device irq flows through the 8259). * * Phases (all on CPU0, destructive phases with IF=0): * D0: live ABI type, icu_imen shadow vs IMR ports coherence, ELCR read * P3: compute uint8_t mask = ~(1 << irq) for irq = -1 (the exact * expression of icu_ioapic_extint, icu.c:170) โ documents the * C-undefined-shift result on the live kernel/compiler * P2: ICU_INTRDIS/ICU_INTREN mask cycle on an unused masked irq, ELCR * read before/after โ proves trigger-mode persistence across masks * P5: full ICW1..ICW4+IMR reprogram of ICU2 (what icu_init does), * ELCR high byte before/after โ empirical "does ICW clobber ELCR" * P4: THE test: call the real icu_ioapic_extint(-1, 0x20) (icu.c:158) * on the live 8259, read back IMR1, then restore via a manual ICW * sequence (icu_init twin; icu_definit() would KKASSERT-panic * under the IOAPIC ABI, icu.c:109). Runs on any ABI. * P6: liveness: sleep 2 ticks, print ticks delta + final coherence. */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/types.h> #include <sys/machintr.h> #include <machine/cpufunc.h> /* kernel symbols (all .globl) */ extern int icu_imen; /* icu_ipl.s:94 */ extern void ICU_INTREN(int); /* icu_ipl.s:127 */ extern void ICU_INTRDIS(int); /* icu_ipl.s:108 */ extern int icu_ioapic_extint(int, int); /* icu.c:158 */ extern int ioapic_enable; /* ioapic.c:84 */ #define IO_ICU1 0x20 /* bus/isa/isareg.h */ #define IO_ICU2 0xA0 #define ICU_IMR_OFFSET 1 #define ELCR_PORT 0x4D0 /* elcr.c:56 */ #define IDT_OFFSET 0x20 static int icuintro_ident; static int read_elcr(void) { return (inb(ELCR_PORT) | (inb(ELCR_PORT + 1) << 8)); } static int icuintro_modevent(module_t mod, int type, void *data) { int imr1, imr2, elcr, shadow, hw; int i, u, rc, imen_saved; int e_b, e_a, e_icw; int imr1_after, imr1_r, imr2_r, elcr_mid; volatile int nirq; uint8_t m; register_t ef; switch (type) { case MOD_LOAD: break; case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } /* NOTE: v2 does NOT pin to CPU0 โ lwkt_setcpu_self() from a process * thread violates usched_dfly's lwp_qcpu invariant (v1 panicked in * dfly_acquire_curproc). On this IOAPIC-ABI guest the 8259 is an * idle legacy device; no concurrent IMR writers exist. */ shadow = icu_imen; imr1 = inb(IO_ICU1 + ICU_IMR_OFFSET); imr2 = inb(IO_ICU2 + ICU_IMR_OFFSET); elcr = read_elcr(); hw = (imr2 << 8) | imr1; kprintf("D0: ncpu=%d mycpuid=%d MachIntrABI.type=%d (MACHINTR_ICU=%d)\n", ncpus, mycpuid, MachIntrABI.type, MACHINTR_ICU); kprintf("D1: icu_imen(shadow)=%04x hw_imr=%04x coherent=%s\n", shadow & 0xffff, hw & 0xffff, ((shadow & 0xffff) == (hw & 0xffff)) ? "YES" : "NO"); kprintf("D2: ELCR=%04x (bit=1 -> level)\n", elcr & 0xffff); /* P3: the exact icu.c:170 expression with the unguarded value */ nirq = -1; m = ~(1 << nirq); kprintf("P3: (uint8_t)~(1 << -1) = 0x%02x (C99 UB shift; x86: 1<<31)\n", m); nirq = 3; m = ~(1 << nirq); kprintf("P3: (uint8_t)~(1 << 3) = 0x%02x (well-defined case)\n", m); /* P2: mask cycle on an already-masked, unused master irq (prefer 7) */ u = -1; for (i = 7; i >= 0; --i) { if ((shadow & (1 << i)) && i != 2) u = i; } if (u >= 0) { e_b = read_elcr(); ICU_INTRDIS(u); i = inb(IO_ICU1 + ICU_IMR_OFFSET); ICU_INTREN(u); rc = inb(IO_ICU1 + ICU_IMR_OFFSET); ICU_INTRDIS(u); imr1 = inb(IO_ICU1 + ICU_IMR_OFFSET); e_a = read_elcr(); kprintf("P2: irq%d mask-cycle IMR1 %02x(en-dis path) -> %02x -> %02x; " "shadow now %04x; ELCR %04x->%04x persist=%s\n", u, i, rc, imr1, icu_imen & 0xffff, e_b & 0xffff, e_a & 0xffff, (e_b == e_a) ? "YES" : "NO"); } else { kprintf("P2: no masked master irq found (unexpected)\n"); } /* P5: does a full ICW reprogram of ICU2 clobber ELCR? (icu_init twin) */ ef = read_rflags(); cpu_disable_intr(); e_b = inb(ELCR_PORT + 1); i = inb(IO_ICU2 + ICU_IMR_OFFSET); /* save IMR2 */ outb(IO_ICU2, 0x11); /* ICW1 */ outb(IO_ICU2 + ICU_IMR_OFFSET, IDT_OFFSET + 8); /* ICW2 */ outb(IO_ICU2 + ICU_IMR_OFFSET, 2); /* ICW3 */ outb(IO_ICU2 + ICU_IMR_OFFSET, 1); /* ICW4 */ outb(IO_ICU2 + ICU_IMR_OFFSET, i); /* IMR */ e_icw = inb(ELCR_PORT + 1); write_rflags(ef); kprintf("P5: ICU2 ICW cycle, ELCR_HI %02x -> %02x (%s)\n", e_b, e_icw, (e_b == e_icw) ? "persists across ICW" : "CLEARED by ICW"); /* P4: the real thing โ icu_ioapic_extint(-1, 0x20) on the live 8259. * v2 restore is a manual ICW sequence (icu_init() twin) because * icu_definit() would KKASSERT-panic when MachIntrABI != ICU * (icu.c:109). Restores the exact boot configuration: * cascade mode, ICU1 ICW4 = auto-EOI|8086 (ioapic_enable boot), * ICU2 ICW4 = 8086, saved IMRs, icu_init's OCW2/OCW3 tail. */ ef = read_rflags(); cpu_disable_intr(); imen_saved = icu_imen; e_b = read_elcr(); imr1 = inb(IO_ICU1 + ICU_IMR_OFFSET); imr2 = inb(IO_ICU2 + ICU_IMR_OFFSET); rc = icu_ioapic_extint(-1, 0x20); imr1_after = inb(IO_ICU1 + ICU_IMR_OFFSET); elcr_mid = read_elcr(); /* manual restore โ mirrors icu_init() (icu.c:62-102) */ outb(IO_ICU1, 0x11); /* ICW1 cascade */ outb(IO_ICU1 + ICU_IMR_OFFSET, IDT_OFFSET); /* ICW2 0x20 */ outb(IO_ICU1 + ICU_IMR_OFFSET, 1 << 2); /* ICW3 slave 2 */ outb(IO_ICU1 + ICU_IMR_OFFSET, 0x03); /* ICW4 aEOI|8086 */ outb(IO_ICU1 + ICU_IMR_OFFSET, imr1); /* IMR restore */ outb(IO_ICU1, 0x0a); /* OCW3: IRR */ outb(IO_ICU1, 0xc0 | 2); /* OCW2: prio */ outb(IO_ICU2, 0x11); outb(IO_ICU2 + ICU_IMR_OFFSET, IDT_OFFSET + 8); outb(IO_ICU2 + ICU_IMR_OFFSET, 2); outb(IO_ICU2 + ICU_IMR_OFFSET, 1); outb(IO_ICU2 + ICU_IMR_OFFSET, imr2); outb(IO_ICU2, 0x0a); imr1_r = inb(IO_ICU1 + ICU_IMR_OFFSET); imr2_r = inb(IO_ICU2 + ICU_IMR_OFFSET); e_a = read_elcr(); write_rflags(ef); kprintf("P4: ioapic_enable=%d icu_ioapic_extint(-1,0x20)=%d " "IMR1_after=0x%02x (expect 0xff: all-masked, benign) " "shadow_untouched=%s\n", ioapic_enable, rc, imr1_after, (icu_imen == imen_saved) ? "YES" : "NO"); kprintf("P4: restored IMR={%02x,%02x} want={%02x,%02x} shadow=%04x " "ELCR %04x->%04x->%04x\n", imr1_r, imr2_r, imr1, imr2, icu_imen & 0xffff, e_b & 0xffff, elcr_mid & 0xffff, e_a & 0xffff); /* P6: liveness โ clock still ticking, mask protocol still coherent */ i = ticks; tsleep(&icuintro_ident, 0, "icuintro", 2 * hz); kprintf("P6: alive, ticks delta=%d (want >=2); final icu_imen=%04x " "hw=%04x coherent=%s\n", ticks - i, icu_imen & 0xffff, ((inb(IO_ICU2 + ICU_IMR_OFFSET) << 8) | inb(IO_ICU1 + ICU_IMR_OFFSET)) & 0xffff, ((icu_imen & 0xffff) == (((inb(IO_ICU2 + ICU_IMR_OFFSET) << 8) | inb(IO_ICU1 + ICU_IMR_OFFSET)) & 0xffff)) ? "YES" : "NO"); return (0); } static moduledata_t icuintro_mod = { "icuintro", icuintro_modevent, NULL }; DECLARE_MODULE(icuintro, icuintro_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(icuintro, 1); |