DF-5533 / dfharness.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 | /* * DF-5532 / DF-5533 PoC harness — exercises the *production* * sys/bus/u4b/usb_process.c machinery (usb_proc_create / msignal / * mwait / drain) from a KLD, the same evidence class as the * usb-cfg-stall family rigs (DF-5367..DF-5504). * * DF-5532 (mwait starvation, production-faithful mode): * - "churn" message pair whose callback does a *minimal* amount of * work (drops the lock for 1 tick, like any real cfg callback * doing a USB request) and re-queues itself => the usb_process * queue is never empty, forever, with NO slow callback anywhere. * - "victim" message pair that the waiter thread mwait()s on; the * victim callback runs and completes almost immediately. * - usb_proc_mwait() still does not return until the churn is * stopped, because the ONLY up_drain broadcasts are the * empty-queue transition (usb_process.c:163-167) and drain * teardown (:445-447) => the waiter sleeps uninterruptibly * (cv_wait, no PCATCH: kern_condvar.c _cv_timedwait wakesig=0) * long after its own messages have completed. * - churn_ticks=0 selects the extreme spin mode: an instantly * self-requeuing message monopolizes the usb_process thread AND * its lock (observed: 8.9e9 callbacks / 275 s; even lockmgr * acquisition by other threads starves). * * DF-5533 (cold-path drain hang): * - sysctl kern.dfcold.fire=1 sets cold=1, then calls * usb_proc_drain() on a live usb_process: the cold branch * (usb_process.c:433-439) calls suspend_kproc() on a thread * whose kthread_suspend_check() is #if 0'd out * (usb_process.c:86-89) => tsleep(td,0,...,timo=0) forever, * bus lock held. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/lock.h> #include <sys/condvar.h> #include <sys/thread.h> #include <sys/kthread.h> #include <sys/sysctl.h> #include <sys/time.h> #include <bus/u4b/usb.h> #include <bus/u4b/usbdi.h> #include <bus/u4b/usb_process.h> struct hmsg { struct usb_proc_msg hdr; int id; }; static struct lock hlock; static struct usb_process up; static struct hmsg vm[2]; /* victim pair */ static struct hmsg cm[2]; /* churn pair */ static volatile int churn_on; static int churn_ticks = 1; /* 0 = spin mode */ static volatile long churn_count; static volatile long victim_ran; static volatile long victim_ran_ms; static volatile long waiter_woke_ms; static volatile int waiter_done; static volatile long started_ms; static struct thread *waiter_td; static long now_ms(void) { struct timespec ts; getnanotime(&ts); return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000); } static void churn_cb(struct usb_proc_msg *pm) { /* entered with hlock held (dispatcher convention) */ churn_count++; if (churn_on) usb_proc_msignal(&up, &cm[0].hdr, &cm[1].hdr); if (churn_ticks > 0) { /* * Stand-in for the lock-drop every real cfg callback * performs while doing its USB request (cf. * usbd_do_request_flags() in usb_request.c): the lock * is released for 1 tick, then re-acquired before * returning to the dispatcher. */ lockmgr(&hlock, LK_RELEASE); tsleep(curthread, 0, "churn", churn_ticks); lockmgr(&hlock, LK_EXCLUSIVE); } } static void victim_cb(struct usb_proc_msg *pm) { victim_ran++; victim_ran_ms = now_ms(); } static void waiter_thread(void *arg) { /* * Stand-in for the unprivileged-syscall waiters of the * production consumers: ucom_queue_command() * (usb_serial.c:655-672, tcsetattr/close on /dev/ttyU*) and * usbd_transfer_unsetup() (usb_transfer.c:1286). */ tsleep(curthread, 0, "d0", hz / 2); /* let churn establish */ lockmgr(&hlock, LK_EXCLUSIVE); usb_proc_msignal(&up, &vm[0].hdr, &vm[1].hdr); /* queue victim */ usb_proc_mwait(&up, &vm[0].hdr, &vm[1].hdr); /* sleep on up_drain */ waiter_woke_ms = now_ms(); waiter_done = 1; lockmgr(&hlock, LK_RELEASE); wakeup(&waiter_done); kthread_exit(); } static int sysctl_report(SYSCTL_HANDLER_ARGS) { char buf[512]; ksnprintf(buf, sizeof(buf), "started_ms=%ld victim_ran=%ld victim_ran_ms=%ld " "churn_count=%ld waiter_done=%d waiter_woke_ms=%ld now_ms=%ld " "starve_ms=%ld\n", started_ms, victim_ran, victim_ran_ms, churn_count, waiter_done, waiter_woke_ms, now_ms(), waiter_done ? (waiter_woke_ms - victim_ran_ms) : -1); sysctl_handle_string(oidp, buf, sizeof(buf), req); return (0); } static int sysctl_stop(SYSCTL_HANDLER_ARGS) { int v = 0; if (req->newptr == NULL) return (EINVAL); sysctl_handle_int(oidp, &v, 0, req); if (v == 1) churn_on = 0; /* let the queue drain */ return (0); } static int sysctl_fire_cold(SYSCTL_HANDLER_ARGS) { int v = 0; if (req->newptr == NULL) return (EINVAL); sysctl_handle_int(oidp, &v, 0, req); if (v != 1) return (0); /* * DF-5533: drain while "cold". Mirrors a USB controller * attach-failure during autoconf (cold==1) reaching * usb_proc_free()/usb_proc_drain() (usb_controller.c:227-236). * Expected: NEVER returns — suspend_kproc() tsleeps forever on a * thread that never runs kthread_suspend_check(). */ cold = 1; usb_proc_drain(&up); cold = 0; kprintf("df5533: drain RETURNED (unexpected)\n"); return (0); } static SYSCTL_NODE(_kern, OID_AUTO, dfstarv, CTLFLAG_RW, 0, "usb_process mwait starvation"); SYSCTL_PROC(_kern_dfstarv, OID_AUTO, report, CTLTYPE_STRING|CTLFLAG_RD, 0, 0, sysctl_report, "A", "starvation state"); SYSCTL_PROC(_kern_dfstarv, OID_AUTO, stop, CTLTYPE_INT|CTLFLAG_RW, 0, 0, sysctl_stop, "I", "stop the churn"); SYSCTL_PROC(_kern_dfstarv, OID_AUTO, churn_ticks, CTLTYPE_INT|CTLFLAG_RW, &churn_ticks, 0, sysctl_handle_int, "I", "churn sleep ticks (0=spin)"); SYSCTL_NODE(_kern, OID_AUTO, dfcold, CTLFLAG_RW, 0, "usb_process cold drain"); SYSCTL_PROC(_kern_dfcold, OID_AUTO, fire, CTLTYPE_INT|CTLFLAG_RW, 0, 0, sysctl_fire_cold, "I", "fire cold-path drain"); static int dfharness_modevent(module_t mod, int type, void *data) { int error; switch (type) { case MOD_LOAD: lockinit(&hlock, "dfhz", 0, 0); memset(&up, 0, sizeof(up)); memset(vm, 0, sizeof(vm)); memset(cm, 0, sizeof(cm)); vm[0].hdr.pm_callback = &victim_cb; vm[1].hdr.pm_callback = &victim_cb; cm[0].hdr.pm_callback = &churn_cb; cm[1].hdr.pm_callback = &churn_cb; error = usb_proc_create(&up, &hlock, "dfhz", USB_PRI_MED); if (error) { kprintf("dfharness: usb_proc_create failed %d\n", error); return (error); } started_ms = now_ms(); /* start the perpetual churn */ churn_on = 1; lockmgr(&hlock, LK_EXCLUSIVE); usb_proc_msignal(&up, &cm[0].hdr, &cm[1].hdr); lockmgr(&hlock, LK_RELEASE); /* spawn the waiter (queues victim, then mwaits) */ error = kthread_create(&waiter_thread, NULL, &waiter_td, "dfwait"); if (error) { kprintf("dfharness: kthread_create failed %d\n", error); return (error); } kprintf("dfharness: loaded; churn (ticks=%d) running, " "waiter will mwait the victim pair\n", churn_ticks); return (0); case MOD_UNLOAD: churn_on = 0; while (!waiter_done) tsleep(&waiter_done, 0, "dfw8", hz); usb_proc_free(&up); lockuninit(&hlock); return (0); default: return (EOPNOTSUPP); } } static moduledata_t dfharness_mod = { "dfharness", dfharness_modevent, 0 }; DECLARE_MODULE(dfharness, dfharness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(dfharness, 1); |