DF-5546 / msileak.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 | /* * DF-5546 harness โ xhci_pci_attach() allocates MSI vectors via * pci_alloc_1intr() (xhci_pci.c:234) unconditionally, but when the * driver runs in forced-polling mode (hw.usb.xhci.use_polling=1 boot * tunable) the IRQ resource is never created (xhci_pci.c:253-268), and * xhci_pci_detach() only calls pci_release_msi() under * `if (sc->sc_irq_res)' (xhci_pci.c:366-371) โ the MSI vectors, the * rid map and the config-space MSI-enable bit LEAK on every * attach/detach cycle. * * Decisive observable: after a clean device_detach(xhci0) (children * detach fine โ no veto involved), calling pci_release_msi(xhci0) * returns 0 (ENODEV == "nothing to release" is what a correct driver * leave behind). The MSI cap control register still has * PCIM_MSICTRL_MSI_ENABLE set post-detach. The harness releases the * leaked vectors itself so the guest stays usable. * * Guest must be booted with a real xHCI controller AND the tunable: * /boot/loader.conf.local: hw.usb.xhci.use_polling=1 * QEMU_EXTRA="-device qemu-xhci" dfbsd-qemu/vm.sh up * * kldload /root/df5546/msileak.ko * sysctl hw.df5546=1 */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/sysctl.h> #include <sys/malloc.h> #include <sys/condvar.h> #include <sys/kthread.h> #include <sys/proc.h> #include <sys/unistd.h> #include <sys/errno.h> #include <bus/pci/pcireg.h> #include <bus/pci/pcivar.h> #include <bus/u4b/usb.h> #include <bus/u4b/usbdi.h> #include <bus/u4b/usb_core.h> #include <bus/u4b/usb_busdma.h> #include <bus/u4b/usb_process.h> #include <bus/u4b/usb_controller.h> #include <bus/u4b/usb_bus.h> #include <bus/u4b/controller/xhci.h> static device_t xhci_dev; static struct thread *df_td; static volatile int kick; static volatile int dead; static int saved_irq_rid; static void *saved_irq_res; static void *saved_intr_hdl; static int msi_cap_off = -1; static void df5546_find_msi_cap(device_t dev) { uint8_t ptr; msi_cap_off = -1; if (!(pci_read_config(dev, PCIR_STATUS, 2) & PCIM_STATUS_CAPPRESENT)) return; ptr = pci_read_config(dev, PCIR_CAP_PTR, 1) & ~0x3; while (ptr != 0 && ptr != 0xff) { if (pci_read_config(dev, ptr, 1) == PCIY_MSI) { msi_cap_off = ptr; return; } ptr = pci_read_config(dev, ptr + 1, 1) & ~0x3; } } static uint16_t df5546_msi_ctrl(device_t dev) { if (msi_cap_off < 0) return (0xffff); return (pci_read_config(dev, msi_cap_off + PCIR_MSI_CTRL, 2)); } static void df5546_cmd1(void) { struct xhci_softc *sc; uint16_t pre, post; int err, rel; if (!xhci_dev) { kprintf("DF5546: no xhci device\n"); return; } sc = device_get_softc(xhci_dev); saved_irq_rid = sc->sc_irq_rid; saved_irq_res = sc->sc_irq_res; saved_intr_hdl = sc->sc_intr_hdl; df5546_find_msi_cap(xhci_dev); pre = df5546_msi_ctrl(xhci_dev); kprintf("DF5546: xhci0 pre-detach: sc_irq_rid=%d sc_irq_res=%p " "sc_intr_hdl=%p (polling mode => MSI allocated by " "pci_alloc_1intr but NO IRQ resource)\n", saved_irq_rid, saved_irq_res, saved_intr_hdl); kprintf("DF5546: MSI cap @0x%02x ctrl=0x%04x (enable bit %s)\n", msi_cap_off, pre, (pre & PCIM_MSICTRL_MSI_ENABLE) ? "SET" : "clear"); kprintf("DF5546: calling device_detach(%s)\n", device_get_nameunit(xhci_dev)); err = device_detach(xhci_dev); kprintf("DF5546: device_detach(xhci0) returned %d\n", err); if (err != 0) { kprintf("DF5546: detach refused (children?) โ no leak test\n"); return; } post = df5546_msi_ctrl(xhci_dev); kprintf("DF5546: post-detach MSI ctrl=0x%04x (enable bit %s)\n", post, (post & PCIM_MSICTRL_MSI_ENABLE) ? "STILL SET" : "clear"); /* * The probe: on a correct detach there is nothing left to release. * 0 -> vectors WERE still allocated: the driver leaked them. * ENODEV -> nothing to release: the driver cleaned up. */ rel = pci_release_msi(xhci_dev); kprintf("DF5546: pci_release_msi(xhci0) probe returned %d (%s)\n", rel, rel == 0 ? "LEAK: driver left MSI vectors allocated" : rel == ENODEV ? "clean: nothing left to release" : "?"); } static void df5546_thread(void *arg) { int c; kprintf("DF5546: worker thread ready (sysctl hw.df5546=N)\n"); for (;;) { tsleep(&kick, 0, "df5546", 0); c = kick; kick = 0; switch (c) { case 1: df5546_cmd1(); break; default: break; } if (c == 9) break; } dead = 1; wakeup(&dead); kthread_exit(); } static int df5546_sysctl(SYSCTL_HANDLER_ARGS) { int error, cmd = 0; error = sysctl_handle_int(oidp, &cmd, 0, req); if (error || req->newptr == NULL) return (error); kick = cmd; wakeup(&kick); return (0); } SYSCTL_PROC(_hw, OID_AUTO, df5546, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df5546_sysctl, "I", "DF-5546 harness control (1=detach+leak-probe)"); static int msileak_modevent(module_t mod, int type, void *data) { devclass_t dc; switch (type) { case MOD_LOAD: dc = devclass_find("xhci"); if (dc == NULL) { kprintf("DF5546: no xhci devclass\n"); return (ENXIO); } xhci_dev = devclass_get_device(dc, 0); if (xhci_dev == NULL) { kprintf("DF5546: no xhci0 device\n"); return (ENXIO); } if (device_get_softc(xhci_dev) == NULL) { kprintf("DF5546: xhci0 has no driver bound\n"); return (ENXIO); } kprintf("DF5546: found %s (driver %s)\n", device_get_nameunit(xhci_dev), device_get_driver(xhci_dev) ? device_get_driver(xhci_dev)->name : "(none)"); dead = 0; kthread_create(df5546_thread, NULL, &df_td, "df5546_td"); kprintf("DF5546: harness loaded\n"); return (0); case MOD_UNLOAD: kick = 9; wakeup(&kick); while (!dead) tsleep(&dead, 0, "df5546x", hz / 4); kprintf("DF5546: module unload\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t msileak_mod = { "msileak", msileak_modevent, 0 }; DECLARE_MODULE(msileak, msileak_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(msileak, 1); |