DF-5425 / uslcom_cfgstall_harness.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 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | /* * DF-5425 KLD harness -- production-code demonstration of the uslcom * (Silicon Labs CP210x) cfg-chain synchronous-request stall. * * WHAT IT PROVES (with the real in-kernel u4b usb_process machinery, the * same taskqueue ucom's cfg callbacks run on): * * uslcom_param() (uslcom.c:505-583) chains 3 synchronous EP0 vendor * control requests per tcsetattr of an open tty: * uslcom.c:514-523 USLCOM_BAUD_RATE write (ucom_cfg_do_request @520) * uslcom.c:551-560 USLCOM_DATA write (ucom_cfg_do_request @557) * uslcom.c:573-582 USLCOM_SET_FLOWCTRL out (ucom_cfg_do_request @579) * uslcom_open() (uslcom.c:406-425) adds 1 (USLCOM_UART enable @418). * uslcom_close() (uslcom.c:427-446) adds 1 (USLCOM_UART disable @442). * Every ucom_cfg_do_request() carries a 1000 ms timeout and its * usb_error_t result is DISCARDED ("failed (ignored)" at uslcom.c:420, * 522, 559, 581, 444), so no chain step ever aborts. * A control transfer's data/status stage may be NAK'd by the device for * as long as it likes: a silent evil CP210x makes each request eat its * full 1000 ms timeout (usb_request.c:642-644 WXFER loop, 699-704 * timeout) and the chain marches on anyway; a slow-but-answer-at-999 ms * evil device makes every request *succeed* with no error at all. * * Amplifier specific to uslcom: while the port is open, the watchdog * (uslcom.c:315-326, reset at hz/4 by uslcom_open) keeps a RCTRL control * xfer in flight on the shared ep0 endpoint queue (uslcom.c:756-770, * 1 s timeout), so every cfg request can additionally queue behind up * to ~1 s of in-flight RCTRL -- measured timings here are the floor. * * The chain runs on sc_super_ucom.sc_tq (per-adapter ucom taskqueue, * usb_serial.c:322). close(2) is SYNCHRONOUS against that queue: * ucom_close() -> ucom_queue_command(ucom_cfg_close) -> usb_proc_mwait() * (usb_serial.c:671-672, 955-957), uninterruptible cv_wait * (usb_process.c:385-393) while a message stays head-of-queue for the * duration of its callback (usb_process.c:146-158). Unplug mid-cycle * stalls the USB hub event thread the same way via ucom_detach() -> * usb_proc_drain() (usb_serial.c:394) -- the DF-5367 mechanism, uslcom * instance. Every tcsetattr of an open tty re-queues the param task * (ucom's sc_param_task[2] double buffer, usb_serial.c:1691-1693), so * an unprivileged local user multiplies the occupancy. * * => one open+tcsetattr+close cycle with the evil device costs * 1x1000 + 3x1000 + 1x1000 ms + 2 core pauses (hz/10 each, * usb_serial.c:777 and 1636) ~= 5.2 s of tq occupancy; two queued * tcsetattrs block a closer ~8.3 s. * * PHASE A (baseline): queue the open(2) chain (cfg_open: 1 x 1000 ms + * hz/10 pause) then ONE param-chain task (3 x lksleep("WXFER", 1000 ms) * -- the exact production per-request cost of a request the device * stretches past its timeout -- + hz/10 pause), then what ucom_close() * does: queue cfg_close (1 x 1000 ms, no core pause) + usb_proc_mwait(). * Measure the closer's block time. EXPECT ~5100 ms. * * PHASE A2 (amplification): open + queue the param task TWICE (two * tcsetattr equivalent, production double-buffer shape) then the closer. * EXPECT ~8200 ms. * * PHASE B (fix.diff mechanism #1: abort each cfg chain on its first * failed request, timeout unchanged): silent evil device -> request #1 * of each chain times out at 1000 ms -> error propagated -> chain * aborts. EXPECT ~3100 ms. * * PHASE C (fix.diff mechanism #1+#2: abort + bound the cfg-path control * timeout 1000 -> 250 ms): covers the slow-but-successful evil device * too. EXPECT ~850 ms. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/lock.h> #include <sys/condvar.h> #include <sys/time.h> #include <sys/types.h> #include <sys/bus.h> #include <bus/u4b/usb.h> #include <bus/u4b/usbdi.h> #include <bus/u4b/usb_process.h> #define SL_PARAM_REQS 3 /* uslcom.c:520,557,579 per-tcsetattr chain */ #define SL_OPEN_REQS 1 /* uslcom.c:418 first-open chain */ #define SL_CLOSE_REQS 1 /* uslcom.c:442 per-close chain */ #define SL_REQ_MS 1000 /* uslcom.c production cfg timeout */ static struct lock sl_lock; /* stands in for sc->sc_lock */ static struct usb_process sl_tq; /* sc_super_ucom.sc_tq (usb_serial.c:322) */ struct sl_msg { struct usb_proc_msg hdr; }; static struct sl_msg sl_param[2]; /* ucom sc_param_task[2] double buffer */ static struct sl_msg sl_open_msg[2]; /* ucom sc_open_task[2] */ static struct sl_msg sl_close[2]; /* ucom sc_close_task[2] */ static int sl_fixed; /* fix mechanism #1: abort on error */ static int sl_req_ms = SL_REQ_MS; /* fix mechanism #2: bounded timeout */ static int sl_aborted; static int64_t sl_ms(struct timeval *t0, struct timeval *t1) { return ((int64_t)(t1->tv_sec - t0->tv_sec) * 1000 + (t1->tv_usec - t0->tv_usec) / 1000); } /* * One uslcom vendor request against the silent evil CP210x: the device * never answers the data/status stage, so the requester sleeps in the * "WXFER" loop until the timeout fires (usb_request.c:642-644, 699-704) * and the request returns USB_ERR_TIMEOUT -- which production uslcom * discards at every call site (uslcom.c:420, 522, 559, 581, 444). */ static int sl_one_request(void) { lksleep(&sl_lock, &sl_lock, 0, "WXFER", USB_MS_TO_TICKS(sl_req_ms)); return (0); /* production: even a timeout is ignored */ } /* uslcom_param()'s 3-request chain (uslcom.c:505-583), runs on sc_tq */ static void sl_cfg_param(struct usb_proc_msg *pm) { int i; kprintf("SL5425: cfg-param task: enter, %d x %d ms sync EP0 requests%s\n", SL_PARAM_REQS, sl_req_ms, sl_fixed ? " (fix: abort on first error)" : ""); sl_aborted = 0; for (i = 0; i < SL_PARAM_REQS; ++i) { sl_one_request(); /* the request that times out */ if (sl_fixed) { /* * fix.diff mechanism: requests propagate the error, * cfg chain bails on first failure. With the silent * device request #1 fails after its timeout. */ kprintf("SL5425: cfg-param: request %d/%d failed " "(USB_ERR_TIMEOUT) -> abort chain (fixed)\n", i + 1, SL_PARAM_REQS); sl_aborted = 1; break; } } /* ucom_cfg_param()'s trailing pause, usb_serial.c:1636 */ usb_pause_mtx(&sl_lock, hz / 10); kprintf("SL5425: cfg-param task: leave (%d/%d requests issued)\n", sl_aborted ? 1 : SL_PARAM_REQS, SL_PARAM_REQS); } /* uslcom_open()'s 1-request chain (uslcom.c:406-425), runs on sc_tq */ static void sl_cfg_open(struct usb_proc_msg *pm) { int i; kprintf("SL5425: cfg-open task: enter, %d x %d ms sync EP0 requests%s\n", SL_OPEN_REQS, sl_req_ms, sl_fixed ? " (fix: abort on first error)" : ""); for (i = 0; i < SL_OPEN_REQS; ++i) { sl_one_request(); /* the request that times out */ if (sl_fixed) { kprintf("SL5425: cfg-open: request %d/%d failed " "(USB_ERR_TIMEOUT) -> abort chain (fixed)\n", i + 1, SL_OPEN_REQS); break; } } /* ucom_cfg_open()'s trailing pause, usb_serial.c:777 */ usb_pause_mtx(&sl_lock, hz / 10); kprintf("SL5425: cfg-open task: leave\n"); } /* uslcom_close()'s 1-request chain (uslcom.c:427-446), runs on sc_tq */ static void sl_cfg_close(struct usb_proc_msg *pm) { int i; kprintf("SL5425: cfg-close task: enter, %d x %d ms sync EP0 request " "(uslcom HAS a ucom_cfg_close callback)\n", SL_CLOSE_REQS, sl_req_ms); for (i = 0; i < SL_CLOSE_REQS; ++i) { sl_one_request(); /* UART disable that times out */ if (sl_fixed) { kprintf("SL5425: cfg-close: request %d/%d failed " "(USB_ERR_TIMEOUT) -> abort chain (fixed)\n", i + 1, SL_CLOSE_REQS); break; } } kprintf("SL5425: cfg-close task: leave (no core pause after " "ucom_cfg_close)\n"); } /* * Exactly ucom_close()'s synchronous close (usb_serial.c:671-672 + 955-957): * queue cfg_close behind the running/queued cfg_param task(s) and mwait * uninterruptibly (usb_process.c:385-393). */ static int64_t sl_closer_thread(int phase, int nparam) { struct timeval t0, t1; char tag = phase == 2 ? 'B' : (phase == 3 ? 'C' : (nparam > 1 ? '2' : 'A')); /* the tcsetattr(s) queued cfg_param; the first starts running */ tsleep(&sl_tq, 0, "sllead", hz / 10); /* 100 ms into the chain */ kprintf("SL5425[%c]: closer: queue cfg_close + usb_proc_mwait() " "(uninterruptible, usb_serial.c:671-672) behind %d param task(s)\n", tag, nparam); microtime(&t0); lockmgr(&sl_lock, LK_EXCLUSIVE); if (!usb_proc_is_gone(&sl_tq)) { struct sl_msg *m; m = usb_proc_msignal(&sl_tq, &sl_close[0], &sl_close[1]); m->hdr.pm_callback = &sl_cfg_close; /* usb_serial.c:671-672 */ usb_proc_mwait(&sl_tq, &sl_close[0], &sl_close[1]); } lockmgr(&sl_lock, LK_RELEASE); microtime(&t1); kprintf("SL5425[%c]: closer: unblocked ELAPSED=%jd ms\n", tag, (intmax_t)sl_ms(&t0, &t1)); return (sl_ms(&t0, &t1)); } /* * The open(2) path: ucom_open() queues ucom_cfg_open first * (usb_serial.c:865-867) -- 1 x 1000 ms for the evil device. */ static void sl_open(void) { struct sl_msg *m; lockmgr(&sl_lock, LK_EXCLUSIVE); m = usb_proc_msignal(&sl_tq, &sl_open_msg[0], &sl_open_msg[1]); m->hdr.pm_callback = &sl_cfg_open; lockmgr(&sl_lock, LK_RELEASE); } /* * The local user's tcsetattr(2): ucom_param() -> ucom_queue_command( * ucom_cfg_param, ..., &sc->sc_param_task[0].hdr, &sc->sc_param_task[1].hdr) * (usb_serial.c:1691-1693) -- double-buffered, so two tsetattrs leave two * outstanding tasks. */ static void sl_tcsetattr(int n) { struct sl_msg *m; lockmgr(&sl_lock, LK_EXCLUSIVE); while (n--) { m = usb_proc_msignal(&sl_tq, &sl_param[0], &sl_param[1]); m->hdr.pm_callback = &sl_cfg_param; } lockmgr(&sl_lock, LK_RELEASE); } static int sl_phase(int phase, int nparam) { int error; memset(&sl_tq, 0, sizeof(sl_tq)); error = usb_proc_create(&sl_tq, &sl_lock, phase == 2 ? "sl5425b" : (phase == 3 ? "sl5425c" : "sl5425a"), USB_PRI_MED); if (error) { kprintf("SL5425: usb_proc_create failed: %d\n", error); return (error); } sl_fixed = (phase >= 2); sl_req_ms = (phase == 3) ? 250 : SL_REQ_MS; /* the user opens the tty (queues cfg_open) and tcsetattr's nparam times */ sl_open(); sl_tcsetattr(nparam); /* the closing thread (or the hub event thread on unplug) */ sl_closer_thread(phase, nparam); usb_proc_free(&sl_tq); return (0); } static int sl_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: lockinit(&sl_lock, "sl5425", 0, LK_CANRECURSE); kprintf("SL5425: DF-5425 uslcom cfg-chain stall harness, " "hz=%d, %d reqs/param-task, %d reqs/open-task, " "%d reqs/close-task, %d ms/request (production timeout)\n", hz, SL_PARAM_REQS, SL_OPEN_REQS, SL_CLOSE_REQS, SL_REQ_MS); sl_phase(0, 1); /* A: open+1 tcsetattr+close ~5.1 s */ sl_phase(1, 2); /* A2: open+2 tcsetattr+close ~8.2 s */ sl_phase(2, 1); /* B: fix #1 (abort) ~3.1 s */ sl_phase(3, 1); /* C: fix #1+#2 (abort+250ms) ~0.9 s */ lockuninit(&sl_lock); kprintf("SL5425: harness complete\n"); break; case MOD_UNLOAD: break; default: error = EOPNOTSUPP; break; } return (error); } static moduledata_t sl_mod = { "uslcomcfgstall", sl_modevent, 0 }; DECLARE_MODULE(uslcomcfgstall, sl_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_DEPEND(uslcomcfgstall, usb, 1, 1, 1); |