DF-5517 / ub5517clearbreak_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 | /* * DF-5517 KLD harness -- production-code demonstration that * ubser_cfg_set_break() can NEVER clear a break condition: * VENDOR_CLEAR_BREAK (ubser.c:102) is referenced nowhere in the driver * and ubser_cfg_set_break() only issues a request when `onoff' is set * (ubser.c:470-486). The clear leg of TIOCCBRK, of ucom_cfg_line_state * (usb_serial.c:1401-1420) and of port close is a silent no-op: once a * break is set on a BWCT port the device's line stays broken until the * adapter is power-cycled, and the kernel never even attempts to tell * it otherwise. * * Rig (DF-5447 production-include class + the DF-5515 usb_process rig): * the UNMODIFIED production ubser.c is #included (newbus registration * neutered); the REAL static ubser_cfg_set_break() runs on a REAL * usb_process taskqueue (usb_proc_create, usb_serial.c:322); only the * EP0 sink (usbd_do_request_proc -- the expansion of the * ucom_cfg_do_request macro, usb_serial.h:220-221) is modeled, and it * only RECORDS the bRequest values it is asked to send. * * PHASE L (lifecycle): open -> TIOCSBRK -> TIOCCBRK -> close, exactly * the tasks ucom queues for them (usb_serial.c:842-880, 1190-1197, * 1356-1421, 671-672). BASELINE EXPECT: request sequence = { 0x02 * (VENDOR_SET_BREAK) } -- exactly ONE request for the entire * lifecycle; the clear leg issues NOTHING; 0x03 * (VENDOR_CLEAR_BREAK) is NEVER observed. * PHASE D (direct): the real ubser_cfg_set_break(ucom, 0) called * directly: issues NOTHING (the `if (onoff)' gate). * FIXED BUILD (fix.diff adds the else-leg sending VENDOR_CLEAR_BREAK): * PHASE L EXPECT = { 0x02, 0x03 } and direct clear = 0x03. */ #include <sys/param.h> #include <sys/types.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/bus.h> #include <sys/lock.h> /* The prototype for usbd_do_request_proc comes from usbdi_util.h * (line 42), which ubser.c itself includes at :88 -- visible at the * production call site inside ubser_cfg_set_break (ubser.c:480). */ MODULE_DEPEND(ub5517clrbrk, usb, 1, 1, 1); MODULE_DEPEND(ub5517clrbrk, ucom, 1, 1, 1); #undef DRIVER_MODULE #undef DRIVER_MODULE_ORDERED #undef MODULE_DEPEND #undef MODULE_VERSION #define DRIVER_MODULE(name, busname, driver, devclass, evh, arg) #define DRIVER_MODULE_ORDERED(name, busname, driver, devclass, evh, arg, order) #define MODULE_DEPEND(module, mdepend, vmin, vpref, vmax) #define MODULE_VERSION(module, version) /* THE PRODUCTION DRIVER, textually unmodified (md5-gated by build.sh). */ #include "../ubser.c" static struct ubser_softc ub_sc; /* recorded request sequence */ static char ub_seq[32]; static int ub_seq_n; static void ub_record(struct usb_device_request *req) { if (ub_seq_n < (int)sizeof(ub_seq)) ub_seq[ub_seq_n++] = (char)req->bRequest; } usb_error_t usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc, struct usb_device_request *req, void *data, uint16_t flags, uint16_t *actlen, usb_timeout_t timeout) { ub_record(req); kprintf("UB5517: EP0 SENT bRequest=0x%02x wValue=0x%02x " "(%s)%s\n", req->bRequest, req->wValue[0], req->bRequest == VENDOR_SET_BREAK ? "VENDOR_SET_BREAK" : req->bRequest == VENDOR_CLEAR_BREAK ? "VENDOR_CLEAR_BREAK" : "other", timeout == 1000 ? "" : " (timeout changed?)"); return (USB_ERR_NORMAL_COMPLETION); } struct ub_msg { struct usb_proc_msg hdr; }; static struct ub_msg ub_open_msg[2]; static struct ub_msg ub_line_msg[2]; static struct ub_msg ub_close_msg[2]; static int ub_ll_ready; static int ub_pls_curr, ub_pls_set, ub_pls_clr; #define UB_LS_BREAK 0x04 /* UCOM_LS_BREAK, usb_serial.h:208 */ static void ub_cfg_open_task(struct usb_proc_msg *pm) { ub_ll_ready = 1; } /* ucom_cfg_line_state (usb_serial.c:1356-1421), BREAK-only mask, * driving the REAL production ubser_cfg_set_break(). */ static void ub_cfg_line_task(struct usb_proc_msg *pm) { uint8_t notch_bits, any_bits, prev_value, last_value; if (!ub_ll_ready) return; /* usb_serial.c:1368-1370 */ notch_bits = (uint8_t)((ub_pls_set & ub_pls_clr) & UB_LS_BREAK); any_bits = (uint8_t)((ub_pls_set | ub_pls_clr) & UB_LS_BREAK); prev_value = (uint8_t)(ub_pls_curr ^ notch_bits); last_value = (uint8_t)ub_pls_curr; ub_pls_curr = 0; ub_pls_set = 0; ub_pls_clr = 0; if (notch_bits & UB_LS_BREAK) ubser_cfg_set_break(&ub_sc.sc_ucom[0], (prev_value & UB_LS_BREAK) ? 1 : 0); if (any_bits & UB_LS_BREAK) ubser_cfg_set_break(&ub_sc.sc_ucom[0], (last_value & UB_LS_BREAK) ? 1 : 0); } static void ub_cfg_close_task(struct usb_proc_msg *pm) { /* no ucom_cfg_close callback in ubser: issues nothing * (usb_serial.c:915-921) */ } static void ub_user_break(int on) { if (on) { ub_pls_curr |= UB_LS_BREAK; ub_pls_set |= UB_LS_BREAK; } else { ub_pls_curr &= ~UB_LS_BREAK; ub_pls_clr |= UB_LS_BREAK; } lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE); if (!usb_proc_is_gone(&ub_sc.sc_super_ucom.sc_tq)) { struct ub_msg *m = usb_proc_msignal( &ub_sc.sc_super_ucom.sc_tq, &ub_line_msg[0], &ub_line_msg[1]); m->hdr.pm_callback = &ub_cfg_line_task; } lockmgr(&ub_sc.sc_lock, LK_RELEASE); } static void ub_seq_dump(const char *what) { int i, cleared = 0; kprintf("UB5517: %s request sequence = { ", what); for (i = 0; i < ub_seq_n; i++) { kprintf("%s0x%02x", i ? ", " : "", (unsigned char)ub_seq[i]); if ((unsigned char)ub_seq[i] == VENDOR_CLEAR_BREAK) cleared = 1; } kprintf(" } (%d request(s); VENDOR_CLEAR_BREAK=0x03 %s)\n", ub_seq_n, cleared ? "OBSERVED" : "NEVER OBSERVED"); } static void ub_touch_statics(void) { if (ubser_devclass != NULL) kprintf("UB5517: unexpected devclass %p\n", ubser_devclass); kprintf("UB5517: production driver \"%s\" compiled into harness " "(registration neutered)\n", ubser_driver.name); } static void ub_setup(void) { memset(&ub_sc, 0, sizeof(ub_sc)); lockinit(&ub_sc.sc_lock, "ubser", 0, LK_CANRECURSE); ub_sc.sc_numser = 1; ub_sc.sc_udev = (void *)&ub_seq_n; /* model sink ignores it */ ub_sc.sc_ucom[0].sc_parent = &ub_sc; ub_sc.sc_ucom[0].sc_portno = 0; ub_sc.sc_ucom[0].sc_super = &ub_sc.sc_super_ucom; ub_sc.sc_ucom[0].sc_lock = &ub_sc.sc_lock; ub_sc.sc_ucom[0].sc_flag = UCOM_FLAG_LL_READY | UCOM_FLAG_HL_READY; ub_ll_ready = 0; ub_pls_curr = ub_pls_set = ub_pls_clr = 0; ub_seq_n = 0; } static void ub_teardown(void) { usb_proc_free(&ub_sc.sc_super_ucom.sc_tq); lockuninit(&ub_sc.sc_lock); } static int ub_lifecycle(char tag) { ub_setup(); if (usb_proc_create(&ub_sc.sc_super_ucom.sc_tq, &ub_sc.sc_lock, "ucom", USB_PRI_MED) != 0) { kprintf("UB5517: usb_proc_create failed\n"); lockuninit(&ub_sc.sc_lock); return (ENXIO); } kprintf("UB5517[%c]: lifecycle open -> TIOCSBRK -> TIOCCBRK -> " "close on the real production callback\n", tag); /* open(2): cfg_open sets LL_READY (0 requests) */ lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE); { struct ub_msg *m = usb_proc_msignal( &ub_sc.sc_super_ucom.sc_tq, &ub_open_msg[0], &ub_open_msg[1]); m->hdr.pm_callback = &ub_cfg_open_task; } lockmgr(&ub_sc.sc_lock, LK_RELEASE); tsleep(&ub_seq, 0, "ubopen", hz / 10); ub_user_break(1); /* TIOCSBRK (set) */ tsleep(&ub_seq, 0, "ubsbrk", hz / 10); ub_user_break(0); /* TIOCCBRK (clear) */ tsleep(&ub_seq, 0, "ubcbrk", hz / 10); /* close(2): synchronous mwait (usb_serial.c:671-672) */ lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE); { struct ub_msg *m = usb_proc_msignal( &ub_sc.sc_super_ucom.sc_tq, &ub_close_msg[0], &ub_close_msg[1]); m->hdr.pm_callback = &ub_cfg_close_task; usb_proc_mwait(&ub_sc.sc_super_ucom.sc_tq, &ub_close_msg[0], &ub_close_msg[1]); } lockmgr(&ub_sc.sc_lock, LK_RELEASE); kprintf("UB5517[%c]: lifecycle complete\n", tag); ub_teardown(); return (0); } static int ub_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: kprintf("UB5517: DF-5517 ubser CLEAR_BREAK harness; " "VENDOR_SET_BREAK=0x%02x VENDOR_CLEAR_BREAK=0x%02x " "(ubser.c:101-102)\n", VENDOR_SET_BREAK, VENDOR_CLEAR_BREAK); ub_touch_statics(); /* PHASE L: full lifecycle */ ub_lifecycle('L'); ub_seq_dump("PHASE L lifecycle"); /* PHASE D: direct set / clear on a LIVE port (tq up, so * the sink's usb_proc_is_gone gate, usb_request.c:771, * passes exactly as it would in production) */ ub_setup(); if (usb_proc_create(&ub_sc.sc_super_ucom.sc_tq, &ub_sc.sc_lock, "ucom", USB_PRI_MED) != 0) { kprintf("UB5517: usb_proc_create failed\n"); break; } kprintf("UB5517[D]: direct production calls: " "set_break(1); set_break(0)\n"); ubser_cfg_set_break(&ub_sc.sc_ucom[0], 1); ubser_cfg_set_break(&ub_sc.sc_ucom[0], 0); ub_seq_dump("PHASE D direct set+clear"); ub_teardown(); kprintf("UB5517: harness complete\n"); break; case MOD_UNLOAD: break; default: error = EOPNOTSUPP; break; } return (error); } static moduledata_t ub_mod = { "ub5517clrbrk", ub_modevent, 0 }; DECLARE_MODULE(ub5517clrbrk, ub_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |