xref: /plan9-contrib/sys/src/cmd/usb/usbd/usbd.c (revision 906943f9f6b8411972abb5e3a03ed19f74be7ccc)
1 #include <u.h>
2 #include <libc.h>
3 #include <thread.h>
4 #include <fcall.h>
5 #include "usb.h"
6 #include "usbfs.h"
7 #include "usbd.h"
8 
9 static Channel *portc;
10 static int win;
11 static int verbose;
12 
13 int mainstacksize = Stack;
14 static Hub *hubs;
15 static int nhubs;
16 static int mustdump;
17 static int pollms = Pollms;
18 
19 static char *dsname[] = { "disabled", "attached", "configed" };
20 
21 static int
22 hubfeature(Hub *h, int port, int f, int on)
23 {
24 	int cmd;
25 
26 	if(on)
27 		cmd = Rsetfeature;
28 	else
29 		cmd = Rclearfeature;
30 	return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0);
31 }
32 
33 /*
34  * This may be used to detect overcurrent on the hub
35  */
36 static void
37 checkhubstatus(Hub *h)
38 {
39 	uchar buf[4];
40 	int sts;
41 
42 	if(h->isroot)	/* not for root hubs */
43 		return;
44 	if(usbcmd(h->dev, Rd2h|Rclass|Rdev, Rgetstatus, 0, 0, buf, 4) < 0){
45 		dprint(2, "%s: get hub status: %r\n", h->dev->dir);
46 		return;
47 	}
48 	sts = GET2(buf);
49 	dprint(2, "hub %s: status %#ux\n", h->dev->dir, sts);
50 }
51 
52 static int
53 confighub(Hub *h)
54 {
55 	int type;
56 	uchar buf[128];	/* room for extra descriptors */
57 	int i;
58 	Usbdev *d;
59 	DHub *dd;
60 	Port *pp;
61 	int nr;
62 	int nmap;
63 	uchar *PortPwrCtrlMask;
64 	int offset;
65 	int mask;
66 
67 	d = h->dev->usb;
68 	for(i = 0; i < nelem(d->ddesc); i++)
69 		if(d->ddesc[i] == nil)
70 			break;
71 		else if(d->ddesc[i]->data.bDescriptorType == Dhub){
72 			dd = (DHub*)&d->ddesc[i]->data;
73 			nr = Dhublen;
74 			goto Config;
75 		}
76 	type = Rd2h|Rclass|Rdev;
77 	nr = usbcmd(h->dev, type, Rgetdesc, Dhub<<8|0, 0, buf, sizeof buf);
78 	if(nr < Dhublen){
79 		dprint(2, "%s: %s: getdesc hub: %r\n", argv0, h->dev->dir);
80 		return -1;
81 	}
82 	dd = (DHub*)buf;
83 Config:
84 	h->nport = dd->bNbrPorts;
85 	nmap = 1 + h->nport/8;
86 	if(nr < 7 + 2*nmap){
87 		fprint(2, "%s: %s: descr. too small\n", argv0, h->dev->dir);
88 		return -1;
89 	}
90 	h->port = emallocz((h->nport+1)*sizeof(Port), 1);
91 	h->pwrms = dd->bPwrOn2PwrGood*2;
92 	if(h->pwrms < Powerdelay)
93 		h->pwrms = Powerdelay;
94 	h->maxcurrent = dd->bHubContrCurrent;
95 	h->pwrmode = dd->wHubCharacteristics[0] & 3;
96 	h->compound = (dd->wHubCharacteristics[0] & (1<<2))!=0;
97 	h->leds = (dd->wHubCharacteristics[0] & (1<<7)) != 0;
98 	PortPwrCtrlMask = dd->DeviceRemovable + nmap;
99 	for(i = 1; i <= h->nport; i++){
100 		pp = &h->port[i];
101 		offset = i/8;
102 		mask = 1<<(i%8);
103 		pp->removable = (dd->DeviceRemovable[offset] & mask) != 0;
104 		pp->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0;
105 	}
106 	return 0;
107 }
108 
109 static void
110 configroothub(Hub *h)
111 {
112 	Dev *d;
113 	char buf[128];
114 	char *p;
115 	int nr;
116 
117 	d = h->dev;
118 	h->nport = 2;
119 	h->maxpkt = 8;
120 	seek(d->cfd, 0, 0);
121 	nr = read(d->cfd, buf, sizeof(buf)-1);
122 	if(nr < 0)
123 		goto Done;
124 	buf[nr] = 0;
125 
126 	p = strstr(buf, "ports ");
127 	if(p == nil)
128 		fprint(2, "%s: %s: no port information\n", argv0, d->dir);
129 	else
130 		h->nport = atoi(p+6);
131 	p = strstr(buf, "maxpkt ");
132 	if(p == nil)
133 		fprint(2, "%s: %s: no maxpkt information\n", argv0, d->dir);
134 	else
135 		h->maxpkt = atoi(p+7);
136 Done:
137 	h->port = emallocz((h->nport+1)*sizeof(Port), 1);
138 	dprint(2, "%s: %s: ports %d maxpkt %d\n", argv0, d->dir, h->nport, h->maxpkt);
139 }
140 
141 Hub*
142 newhub(char *fn, Dev *d)
143 {
144 	Hub *h;
145 	int i;
146 	Usbdev *ud;
147 
148 	h = emallocz(sizeof(Hub), 1);
149 	h->isroot = (d == nil);
150 	if(h->isroot){
151 		h->dev = opendev(fn);
152 		if(h->dev == nil){
153 			fprint(2, "%s: opendev: %s: %r", argv0, fn);
154 			goto Fail;
155 		}
156 		if(opendevdata(h->dev, ORDWR) < 0){
157 			fprint(2, "%s: opendevdata: %s: %r\n", argv0, fn);
158 			goto Fail;
159 		}
160 		configroothub(h);	/* never fails */
161 	}else{
162 		h->dev = d;
163 		if(confighub(h) < 0){
164 			fprint(2, "%s: %s: config: %r\n", argv0, fn);
165 			goto Fail;
166 		}
167 	}
168 	if(h->dev == nil){
169 		fprint(2, "%s: opendev: %s: %r\n", argv0, fn);
170 		goto Fail;
171 	}
172 	devctl(h->dev, "hub");
173 	ud = h->dev->usb;
174 	if(h->isroot)
175 		devctl(h->dev, "info roothub csp %#08ux ports %d",
176 			0x000009, h->nport);
177 	else{
178 		devctl(h->dev, "info hub csp %#08ulx ports %d %q %q",
179 			ud->csp, h->nport, ud->vendor, ud->product);
180 		for(i = 1; i <= h->nport; i++)
181 			if(hubfeature(h, i, Fportpower, 1) < 0)
182 				fprint(2, "%s: %s: power: %r\n", argv0, fn);
183 		sleep(h->pwrms);
184 		for(i = 1; i <= h->nport; i++)
185 			if(h->leds != 0)
186 				hubfeature(h, i, Fportindicator, 1);
187 	}
188 	h->next = hubs;
189 	hubs = h;
190 	nhubs++;
191 	dprint(2, "%s: hub %#p allocated:", argv0, h);
192 	dprint(2, " ports %d pwrms %d max curr %d pwrm %d cmp %d leds %d\n",
193 		h->nport, h->pwrms, h->maxcurrent,
194 		h->pwrmode, h->compound, h->leds);
195 	incref(h->dev);
196 	return h;
197 Fail:
198 	if(d != nil)
199 		devctl(d, "detach");
200 	free(h->port);
201 	free(h);
202 	dprint(2, "%s: hub %#p failed to start:", argv0, h);
203 	return nil;
204 }
205 
206 static void portdetach(Hub *h, int p);
207 
208 /*
209  * If during enumeration we get an I/O error the hub is gone or
210  * in pretty bad shape. Because of retries of failed usb commands
211  * (and the sleeps they include) it can take a while to detach all
212  * ports for the hub. This detaches all ports and makes the hub void.
213  * The parent hub will detect a detach (probably right now) and
214  * close it later.
215  */
216 static void
217 hubfail(Hub *h)
218 {
219 	int i;
220 
221 	for(i = 1; i <= h->nport; i++)
222 		portdetach(h, i);
223 	h->failed = 1;
224 }
225 
226 static void
227 closehub(Hub *h)
228 {
229 	Hub **hl;
230 
231 	dprint(2, "%s: closing hub %#p\n", argv0, h);
232 	for(hl = &hubs; *hl != nil; hl = &(*hl)->next)
233 		if(*hl == h)
234 			break;
235 	if(*hl == nil)
236 		sysfatal("closehub: no hub");
237 	*hl = h->next;
238 	nhubs--;
239 	hubfail(h);		/* detach all ports */
240 	free(h->port);
241 	assert(h->dev != nil);
242 	devctl(h->dev, "detach");
243 	closedev(h->dev);
244 	free(h);
245 }
246 
247 static int
248 portstatus(Hub *h, int p)
249 {
250 	Dev *d;
251 	uchar buf[4];
252 	int t;
253 	int sts;
254 	int dbg;
255 
256 	dbg = usbdebug;
257 	if(dbg != 0 && dbg < 4)
258 		usbdebug = 1;	/* do not be too chatty */
259 	d = h->dev;
260 	t = Rd2h|Rclass|Rother;
261 	if(usbcmd(d, t, Rgetstatus, 0, p, buf, sizeof(buf)) < 0)
262 		sts = -1;
263 	else
264 		sts = GET2(buf);
265 	usbdebug = dbg;
266 	return sts;
267 }
268 
269 static char*
270 stsstr(int sts)
271 {
272 	static char s[80];
273 	char *e;
274 
275 	e = s;
276 	if(sts&PSsuspend)
277 		*e++ = 'z';
278 	if(sts&PSreset)
279 		*e++ = 'r';
280 	if(sts&PSslow)
281 		*e++ = 'l';
282 	if(sts&PShigh)
283 		*e++ = 'h';
284 	if(sts&PSchange)
285 		*e++ = 'c';
286 	if(sts&PSenable)
287 		*e++ = 'e';
288 	if(sts&PSstatuschg)
289 		*e++ = 's';
290 	if(sts&PSpresent)
291 		*e++ = 'p';
292 	if(e == s)
293 		*e++ = '-';
294 	*e = 0;
295 	return s;
296 }
297 
298 static int
299 getmaxpkt(Dev *d, int islow)
300 {
301 	uchar buf[64];	/* More room to try to get device-specific descriptors */
302 	DDev *dd;
303 
304 	dd = (DDev*)buf;
305 	if(islow)
306 		dd->bMaxPacketSize0 = 8;
307 	else
308 		dd->bMaxPacketSize0 = 64;
309 	if(usbcmd(d, Rd2h|Rstd|Rdev, Rgetdesc, Ddev<<8|0, 0, buf, sizeof(buf)) < 0)
310 		return -1;
311 	return dd->bMaxPacketSize0;
312 }
313 
314 /*
315  * BUG: does not consider max. power avail.
316  */
317 static Dev*
318 portattach(Hub *h, int p, int sts)
319 {
320 	Dev *d;
321 	Port *pp;
322 	Dev *nd;
323 	char fname[80];
324 	char buf[40];
325 	char *sp;
326 	int mp;
327 	int nr;
328 
329 	d = h->dev;
330 	pp = &h->port[p];
331 	nd = nil;
332 	pp->state = Pattached;
333 	dprint(2, "%s: %s: port %d attach sts %#ux\n", argv0, d->dir, p, sts);
334 	sleep(Connectdelay);
335 	if(hubfeature(h, p, Fportenable, 1) < 0)
336 		dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
337 	sleep(Enabledelay);
338 	if(hubfeature(h, p, Fportreset, 1) < 0){
339 		dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);
340 		goto Fail;
341 	}
342 	sleep(Resetdelay);
343 	sts = portstatus(h, p);
344 	if(sts < 0)
345 		goto Fail;
346 	if((sts & PSenable) == 0){
347 		dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);
348 		hubfeature(h, p, Fportenable, 1);
349 		sts = portstatus(h, p);
350 		if((sts & PSenable) == 0)
351 			goto Fail;
352 	}
353 	sp = "full";
354 	if(sts & PSslow)
355 		sp = "low";
356 	if(sts & PShigh)
357 		sp = "high";
358 	dprint(2, "%s: %s: port %d: attached status %#ux\n", argv0, d->dir, p, sts);
359 
360 	if(devctl(d, "newdev %s %d", sp, p) < 0){
361 		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
362 		goto Fail;
363 	}
364 	seek(d->cfd, 0, 0);
365 	nr = read(d->cfd, buf, sizeof(buf)-1);
366 	if(nr == 0){
367 		fprint(2, "%s: %s: port %d: newdev: eof\n", argv0, d->dir, p);
368 		goto Fail;
369 	}
370 	if(nr < 0){
371 		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);
372 		goto Fail;
373 	}
374 	buf[nr] = 0;
375 	snprint(fname, sizeof(fname), "/dev/usb/%s", buf);
376 	nd = opendev(fname);
377 	if(nd == nil){
378 		fprint(2, "%s: %s: port %d: opendev: %r\n", argv0, d->dir, p);
379 		goto Fail;
380 	}
381 	if(usbdebug > 2)
382 		devctl(nd, "debug 1");
383 	if(opendevdata(nd, ORDWR) < 0){
384 		fprint(2, "%s: %s: opendevdata: %r\n", argv0, nd->dir);
385 		goto Fail;
386 	}
387 	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){
388 		dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);
389 		goto Fail;
390 	}
391 	if(devctl(nd, "address") < 0){
392 		dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);
393 		goto Fail;
394 	}
395 
396 	mp=getmaxpkt(nd, strcmp(sp, "low") == 0);
397 	if(mp < 0){
398 		dprint(2, "%s: %s: port %d: getmaxpkt: %r\n", argv0, d->dir, p);
399 		goto Fail;
400 	}else{
401 		dprint(2, "%s; %s: port %d: maxpkt %d\n", argv0, d->dir, p, mp);
402 		devctl(nd, "maxpkt %d", mp);
403 	}
404 	if((sts & PSslow) != 0 && strcmp(sp, "full") == 0)
405 		dprint(2, "%s: %s: port %d: %s is full speed when port is low\n",
406 			argv0, d->dir, p, nd->dir);
407 	if(configdev(nd) < 0){
408 		dprint(2, "%s: %s: port %d: configdev: %r\n", argv0, d->dir, p);
409 		goto Fail;
410 	}
411 	/*
412 	 * We always set conf #1. BUG.
413 	 */
414 	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){
415 		dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);
416 		unstall(nd, nd, Eout);
417 		if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)
418 			goto Fail;
419 	}
420 	dprint(2, "%s: %U", argv0, nd);
421 	pp->state = Pconfiged;
422 	dprint(2, "%s: %s: port %d: configed: %s\n",
423 			argv0, d->dir, p, nd->dir);
424 	return pp->dev = nd;
425 Fail:
426 	pp->state = Pdisabled;
427 	pp->sts = 0;
428 	if(pp->hub != nil)
429 		pp->hub = nil;	/* hub closed by enumhub */
430 	hubfeature(h, p, Fportenable, 0);
431 	if(nd != nil)
432 		devctl(nd, "detach");
433 	closedev(nd);
434 	return nil;
435 }
436 
437 static void
438 portdetach(Hub *h, int p)
439 {
440 	Dev *d;
441 	Port *pp;
442 	extern void usbfsgone(char*);
443 	d = h->dev;
444 	pp = &h->port[p];
445 
446 	/*
447 	 * Clear present, so that we detect an attach on reconnects.
448 	 */
449 	pp->sts &= ~(PSpresent|PSenable);
450 
451 	if(pp->state == Pdisabled)
452 		return;
453 	pp->state = Pdisabled;
454 	dprint(2, "%s: %s: port %d: detached\n", argv0, d->dir, p);
455 
456 	if(pp->hub != nil){
457 		closehub(pp->hub);
458 		pp->hub = nil;
459 	}
460 	if(pp->dev != nil){
461 		devctl(pp->dev, "detach");
462 		usbfsgone(pp->dev->dir);
463 		closedev(pp->dev);
464 		pp->dev = nil;
465 	}
466 }
467 
468 static int
469 portgone(Port *pp, int sts)
470 {
471 	if(sts < 0)
472 		return 1;
473 	/*
474 	 * If it was enabled and it's not now then it may be reconnect.
475 	 * We pretend it's gone and later we'll see it as attached.
476 	 */
477 	if((pp->sts & PSenable) != 0 && (sts & PSenable) == 0)
478 		return 1;
479 	return (pp->sts & PSpresent) != 0 && (sts & PSpresent) == 0;
480 }
481 
482 static int
483 enumhub(Hub *h, int p)
484 {
485 	int sts;
486 	Dev *d;
487 	Port *pp;
488 	int onhubs;
489 
490 	if(h->failed)
491 		return 0;
492 	d = h->dev;
493 	if(usbdebug > 3)
494 		fprint(2, "%s: %s: port %d enumhub\n", argv0, d->dir, p);
495 
496 	sts = portstatus(h, p);
497 	if(sts < 0){
498 		hubfail(h);		/* avoid delays on detachment */
499 		return -1;
500 	}
501 	pp = &h->port[p];
502 	onhubs = nhubs;
503 	if((sts & PSsuspend) != 0){
504 		if(hubfeature(h, p, Fportenable, 1) < 0)
505 			dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
506 		sleep(Enabledelay);
507 		sts = portstatus(h, p);
508 		fprint(2, "%s: %s: port %d: resumed (sts %#ux)\n", argv0, d->dir, p, sts);
509 	}
510 	if((pp->sts & PSpresent) == 0 && (sts & PSpresent) != 0){
511 		if(portattach(h, p, sts) != nil)
512 			if(startdev(pp) < 0)
513 				portdetach(h, p);
514 	}else if(portgone(pp, sts))
515 		portdetach(h, p);
516 	else if(pp->sts != sts){
517 		dprint(2, "%s: %s port %d: sts %s %#x ->",
518 			argv0, d->dir, p, stsstr(pp->sts), pp->sts);
519 		dprint(2, " %s %#x\n",stsstr(sts), sts);
520 	}
521 	pp->sts = sts;
522 	if(onhubs != nhubs)
523 		return -1;
524 	return 0;
525 }
526 
527 static void
528 dump(void)
529 {
530 	Hub *h;
531 	int i;
532 
533 	mustdump = 0;
534 	for(h = hubs; h != nil; h = h->next)
535 		for(i = 1; i <= h->nport; i++)
536 			fprint(2, "%s: hub %#p %s port %d: %U",
537 				argv0, h, h->dev->dir, i, h->port[i].dev);
538 	usbfsdirdump();
539 
540 }
541 
542 static void
543 work(void *a)
544 {
545 	Channel *portc;
546 	char *fn;
547 	Hub *h;
548 	int i;
549 
550 	portc = a;
551 	hubs = nil;
552 	/*
553 	 * Receive requests for root hubs
554 	 */
555 	while((fn = recvp(portc)) != nil){
556 		dprint(2, "%s: %s starting\n", argv0, fn);
557 		h = newhub(fn, nil);
558 		if(h == nil)
559 			fprint(2, "%s: %s: %r\n", argv0, fn);
560 		free(fn);
561 	}
562 	/*
563 	 * Enumerate (and acknowledge after first enumeration).
564 	 * Do NOT perform enumeration concurrently for the same
565 	 * controller. new devices attached respond to a default
566 	 * address (0) after reset, thus enumeration has to work
567 	 * one device at a time at least before addresses have been
568 	 * assigned.
569 	 * Do not use hub interrupt endpoint because we
570 	 * have to poll the root hub(s) in any case.
571 	 */
572 	for(;;){
573 	Again:
574 		for(h = hubs; h != nil; h = h->next)
575 			for(i = 1; i <= h->nport; i++)
576 				if(enumhub(h, i) < 0){
577 					/* changes in hub list; repeat */
578 					goto Again;
579 				}
580 		if(portc != nil){
581 			sendp(portc, nil);
582 			portc = nil;
583 		}
584 		sleep(pollms);
585 		if(mustdump)
586 			dump();
587 	}
588 
589 }
590 
591 static int
592 cfswalk(Usbfs*, Fid *, char *)
593 {
594 	werrstr(Enotfound);
595 	return -1;
596 }
597 
598 static int
599 cfsopen(Usbfs*, Fid *, int)
600 {
601 	return 0;
602 }
603 
604 static long
605 cfsread(Usbfs*, Fid *, void *, long , vlong )
606 {
607 	return 0;
608 }
609 
610 static void
611 setdrvargs(char *name, char *args)
612 {
613 	Devtab *dt;
614 	extern Devtab devtab[];
615 
616 	for(dt = devtab; dt->name != nil; dt++)
617 		if(strstr(dt->name, name) != nil)
618 			dt->args = estrdup(args);
619 }
620 
621 
622 static long
623 cfswrite(Usbfs*, Fid *, void *data, long cnt, vlong )
624 {
625 	char buf[80];
626 	char *toks[4];
627 
628 	if(cnt > sizeof(buf))
629 		cnt = sizeof(buf) - 1;
630 	strncpy(buf, data, cnt);
631 	buf[cnt] = 0;
632 	if(cnt > 0 && buf[cnt-1] == '\n')
633 		buf[cnt-1] = 0;
634 	if(strncmp(buf, "dump", 4) == 0){
635 		mustdump = 1;
636 		return cnt;
637 	}
638 	if(strncmp(buf, "reset", 5) == 0){
639 		werrstr("reset not implemented");
640 		return -1;
641 	}
642 	if(tokenize(buf, toks, nelem(toks)) != 2){
643 		werrstr("usage: debug|fsdebug n");
644 		return -1;
645 	}
646 	if(strcmp(toks[0], "debug") == 0)
647 		usbdebug = atoi(toks[1]);
648 	else if(strcmp(toks[0], "fsdebug") == 0)
649 		usbfsdebug = atoi(toks[1]);
650 	else if(strcmp(toks[0], "kbargs") == 0)
651 		setdrvargs("kb", toks[1]);
652 	else if(strcmp(toks[0], "diskargs") == 0)
653 		setdrvargs("disk", toks[1]);
654 	else{
655 		werrstr("unkown ctl '%s'", buf);
656 		return -1;
657 	}
658 	fprint(2, "%s: debug %d fsdebug %d\n", argv0, usbdebug, usbfsdebug);
659 	return cnt;
660 }
661 
662 static int
663 cfsstat(Usbfs* fs, Qid qid, Dir *d)
664 {
665 	d->qid = qid;
666 	d->qid.path |= fs->qid;
667 	d->qid.type = 0;
668 	d->qid.vers = 0;
669 	d->name = "usbdctl";
670 	d->length = 0;
671 	d->mode = 0664;
672 	return 0;
673 }
674 
675 static Usbfs ctlfs =
676 {
677 	.walk = cfswalk,
678 	.open = cfsopen,
679 	.read = cfsread,
680 	.write = cfswrite,
681 	.stat = cfsstat
682 };
683 
684 static void
685 args(void)
686 {
687 	char *s;
688 
689 	s = getenv("usbdebug");
690 	if(s != nil)
691 		usbdebug = atoi(s);
692 	free(s);
693 	s = getenv("usbfsdebug");
694 	if(s != nil)
695 		usbfsdebug = atoi(s);
696 	free(s);
697 	s = getenv("kbargs");
698 	if(s != nil)
699 		setdrvargs("kb", s);
700 	free(s);
701 	s = getenv("diskargs");
702 	if(s != nil)
703 		setdrvargs("disk", s);
704 	free(s);
705 }
706 
707 static void
708 usage(void)
709 {
710 	fprint(2, "usage: %s [-Dd] [-s srv] [-m mnt] [dev...]\n", argv0);
711 	threadexitsall("usage");
712 }
713 
714 extern void usbfsexits(int);
715 
716 void
717 threadmain(int argc, char **argv)
718 {
719 	int i;
720 	Dir *d;
721 	int fd;
722 	int nd;
723 	char *err;
724 	char *srv;
725 	char *mnt;
726 
727 	srv = "usb";
728 	mnt = "/dev";
729 	ARGBEGIN{
730 	case 'D':
731 		usbfsdebug++;
732 		break;
733 	case 'd':
734 		usbdebug++;
735 		break;
736 	case 's':
737 		srv = EARGF(usage());
738 		break;
739 	case 'i':
740 		pollms = atoi(EARGF(usage()));
741 		break;
742 	case 'm':
743 		mnt = EARGF(usage());
744 		break;
745 	default:
746 		usage();
747 	}ARGEND;
748 	if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
749 		sysfatal("#u: %r");
750 
751 	args();
752 
753 	fmtinstall('U', Ufmt);
754 	quotefmtinstall();
755 	rfork(RFNOTEG);
756 	portc = chancreate(sizeof(char *), 0);
757 	if(portc == nil)
758 		sysfatal("chancreate");
759 	proccreate(work, portc, Stack);
760 	if(argc == 0){
761 		fd = open("/dev/usb", OREAD);
762 		if(fd < 0)
763 			sysfatal("/dev/usb: %r");
764 		nd = dirreadall(fd, &d);
765 		close(fd);
766 		if(nd < 2)
767 			sysfatal("/dev/usb: no hubs");
768 		for(i = 0; i < nd; i++)
769 			if(strcmp(d[i].name, "ctl") != 0)
770 				sendp(portc, smprint("/dev/usb/%s", d[i].name));
771 		free(d);
772 	}else
773 		for(i = 0; i < argc; i++)
774 			sendp(portc, strdup(argv[i]));
775 	sendp(portc, nil);
776 	err = recvp(portc);
777 	chanfree(portc);
778 	usbfsexits(0);
779 	usbfsinit(srv, mnt, &usbdirfs, MAFTER);
780 	snprint(ctlfs.name, sizeof(ctlfs.name), "usbdctl");
781 	usbfsadd(&ctlfs);
782 	threadexits(err);
783 }
784