xref: /plan9/sys/src/9/pc/devusb.c (revision 305b51b870592dafc6b4483fa85b31f528e7ab21)
1ade43d10SDavid du Colombier /*
284860c5dSDavid du Colombier  * USB device driver framework.
3906943f9SDavid du Colombier  *
4906943f9SDavid du Colombier  * This is in charge of providing access to actual HCIs
5906943f9SDavid du Colombier  * and providing I/O to the various endpoints of devices.
6906943f9SDavid du Colombier  * A separate user program (usbd) is in charge of
7906943f9SDavid du Colombier  * enumerating the bus, setting up endpoints and
8906943f9SDavid du Colombier  * starting devices (also user programs).
9906943f9SDavid du Colombier  *
10906943f9SDavid du Colombier  * The interface provided is a violation of the standard:
11906943f9SDavid du Colombier  * you're welcome.
12906943f9SDavid du Colombier  *
13906943f9SDavid du Colombier  * The interface consists of a root directory with several files
14906943f9SDavid du Colombier  * plus a directory (epN.M) with two files per endpoint.
15906943f9SDavid du Colombier  * A device is represented by its first endpoint, which
16906943f9SDavid du Colombier  * is a control endpoint automatically allocated for each device.
17906943f9SDavid du Colombier  * Device control endpoints may be used to create new endpoints.
18906943f9SDavid du Colombier  * Devices corresponding to hubs may also allocate new devices,
19906943f9SDavid du Colombier  * perhaps also hubs. Initially, a hub device is allocated for
20906943f9SDavid du Colombier  * each controller present, to represent its root hub. Those can
21906943f9SDavid du Colombier  * never be removed.
22906943f9SDavid du Colombier  *
23906943f9SDavid du Colombier  * All endpoints refer to the first endpoint (epN.0) of the device,
24906943f9SDavid du Colombier  * which keeps per-device information, and also to the HCI used
25906943f9SDavid du Colombier  * to reach them. Although all endpoints cache that information.
26906943f9SDavid du Colombier  *
27906943f9SDavid du Colombier  * epN.M/data files permit I/O and are considered DMEXCL.
28906943f9SDavid du Colombier  * epN.M/ctl files provide status info and accept control requests.
29906943f9SDavid du Colombier  *
30906943f9SDavid du Colombier  * Endpoints may be given file names to be listed also at #u,
31906943f9SDavid du Colombier  * for those drivers that have nothing to do after configuring the
32906943f9SDavid du Colombier  * device and its endpoints.
33906943f9SDavid du Colombier  *
34906943f9SDavid du Colombier  * Drivers for different controllers are kept at usb[oue]hci.c
35906943f9SDavid du Colombier  * It's likely we could factor out much from controllers into
36906943f9SDavid du Colombier  * a generic controller driver, the problem is that details
37906943f9SDavid du Colombier  * regarding how to handle toggles, tokens, Tds, etc. will
38906943f9SDavid du Colombier  * get in the way. Thus, code is probably easier the way it is.
39ade43d10SDavid du Colombier  */
40906943f9SDavid du Colombier 
419a747e4fSDavid du Colombier #include	"u.h"
429a747e4fSDavid du Colombier #include	"../port/lib.h"
439a747e4fSDavid du Colombier #include	"mem.h"
449a747e4fSDavid du Colombier #include	"dat.h"
459a747e4fSDavid du Colombier #include	"fns.h"
469a747e4fSDavid du Colombier #include	"io.h"
479a747e4fSDavid du Colombier #include	"../port/error.h"
4884860c5dSDavid du Colombier #include	"../port/usb.h"
499a747e4fSDavid du Colombier 
50906943f9SDavid du Colombier typedef struct Hcitype Hcitype;
519a747e4fSDavid du Colombier 
529a747e4fSDavid du Colombier enum
539a747e4fSDavid du Colombier {
54906943f9SDavid du Colombier 	/* Qid numbers */
55906943f9SDavid du Colombier 	Qdir = 0,		/* #u */
56906943f9SDavid du Colombier 	Qusbdir,			/* #u/usb */
57906943f9SDavid du Colombier 	Qctl,			/* #u/usb/ctl - control requests */
58906943f9SDavid du Colombier 
59906943f9SDavid du Colombier 	Qep0dir,			/* #u/usb/ep0.0 - endpoint 0 dir */
60906943f9SDavid du Colombier 	Qep0io,			/* #u/usb/ep0.0/data - endpoint 0 I/O */
61906943f9SDavid du Colombier 	Qep0ctl,		/* #u/usb/ep0.0/ctl - endpoint 0 ctl. */
62906943f9SDavid du Colombier 	Qep0dummy,		/* give 4 qids to each endpoint */
63906943f9SDavid du Colombier 
64906943f9SDavid du Colombier 	Qepdir = 0,		/* (qid-qep0dir)&3 is one of these */
65906943f9SDavid du Colombier 	Qepio,			/* to identify which file for the endpoint */
66906943f9SDavid du Colombier 	Qepctl,
67906943f9SDavid du Colombier 
68906943f9SDavid du Colombier 	/* ... */
69906943f9SDavid du Colombier 
70906943f9SDavid du Colombier 	/* Usb ctls. */
71906943f9SDavid du Colombier 	CMdebug = 0,		/* debug on|off */
72906943f9SDavid du Colombier 	CMdump,			/* dump (data structures for debug) */
73906943f9SDavid du Colombier 
74906943f9SDavid du Colombier 	/* Ep. ctls */
75906943f9SDavid du Colombier 	CMnew = 0,		/* new nb ctl|bulk|intr|iso r|w|rw (endpoint) */
76906943f9SDavid du Colombier 	CMnewdev,		/* newdev full|low|high portnb (allocate new devices) */
77906943f9SDavid du Colombier 	CMhub,			/* hub (set the device as a hub) */
78906943f9SDavid du Colombier 	CMspeed,		/* speed full|low|high|no */
79906943f9SDavid du Colombier 	CMmaxpkt,		/* maxpkt size */
80906943f9SDavid du Colombier 	CMntds,			/* ntds nb (max nb. of tds per µframe) */
81906943f9SDavid du Colombier 	CMclrhalt,		/* clrhalt (halt was cleared on endpoint) */
82906943f9SDavid du Colombier 	CMpollival,		/* pollival interval (interrupt/iso) */
83906943f9SDavid du Colombier 	CMhz,			/* hz n (samples/sec; iso) */
84906943f9SDavid du Colombier 	CMsamplesz,		/* samplesz n (sample size; iso) */
85906943f9SDavid du Colombier 	CMinfo,			/* info infostr (ke.ep info for humans) */
86906943f9SDavid du Colombier 	CMdetach,		/* detach (abort I/O forever on this ep). */
87906943f9SDavid du Colombier 	CMaddress,		/* address (address is assigned) */
88906943f9SDavid du Colombier 	CMdebugep,		/* debug n (set/clear debug for this ep) */
89906943f9SDavid du Colombier 	CMname,			/* name str (show up as #u/name as well) */
90d37e33ffSDavid du Colombier 	CMtmout,		/* timeout n (activate timeouts for ep) */
91a23bc242SDavid du Colombier 	CMpreset,		/* reset the port */
92906943f9SDavid du Colombier 
93906943f9SDavid du Colombier 	/* Hub feature selectors */
94906943f9SDavid du Colombier 	Rportenable	= 1,
95906943f9SDavid du Colombier 	Rportreset	= 4,
96906943f9SDavid du Colombier 
979a747e4fSDavid du Colombier };
989a747e4fSDavid du Colombier 
99906943f9SDavid du Colombier struct Hcitype
1009a747e4fSDavid du Colombier {
1019a747e4fSDavid du Colombier 	char*	type;
102906943f9SDavid du Colombier 	int	(*reset)(Hci*);
103906943f9SDavid du Colombier };
104906943f9SDavid du Colombier 
105906943f9SDavid du Colombier #define QID(q)	((int)(q).path)
106906943f9SDavid du Colombier 
107906943f9SDavid du Colombier static char Edetach[] = "device is detached";
108906943f9SDavid du Colombier static char Enotconf[] = "endpoint not configured";
109906943f9SDavid du Colombier char Estalled[] = "endpoint stalled";
110906943f9SDavid du Colombier 
111906943f9SDavid du Colombier static Cmdtab usbctls[] =
112906943f9SDavid du Colombier {
113906943f9SDavid du Colombier 	{CMdebug,	"debug",	2},
114906943f9SDavid du Colombier 	{CMdump,	"dump",		1},
115906943f9SDavid du Colombier };
116906943f9SDavid du Colombier 
117906943f9SDavid du Colombier static Cmdtab epctls[] =
118906943f9SDavid du Colombier {
119906943f9SDavid du Colombier 	{CMnew,		"new",		4},
120906943f9SDavid du Colombier 	{CMnewdev,	"newdev",	3},
121906943f9SDavid du Colombier 	{CMhub,		"hub",		1},
122906943f9SDavid du Colombier 	{CMspeed,	"speed",	2},
123906943f9SDavid du Colombier 	{CMmaxpkt,	"maxpkt",	2},
124906943f9SDavid du Colombier 	{CMntds,	"ntds",		2},
125906943f9SDavid du Colombier 	{CMpollival,	"pollival",	2},
126906943f9SDavid du Colombier 	{CMsamplesz,	"samplesz",	2},
127906943f9SDavid du Colombier 	{CMhz,		"hz",		2},
128906943f9SDavid du Colombier 	{CMinfo,	"info",		0},
129906943f9SDavid du Colombier 	{CMdetach,	"detach",	1},
130906943f9SDavid du Colombier 	{CMaddress,	"address",	1},
131906943f9SDavid du Colombier 	{CMdebugep,	"debug",	2},
132906943f9SDavid du Colombier 	{CMclrhalt,	"clrhalt",	1},
133906943f9SDavid du Colombier 	{CMname,	"name",		2},
134d37e33ffSDavid du Colombier 	{CMtmout,	"timeout",	2},
135a23bc242SDavid du Colombier 	{CMpreset,	"reset",	1},
136906943f9SDavid du Colombier };
137906943f9SDavid du Colombier 
138906943f9SDavid du Colombier static Dirtab usbdir[] =
139906943f9SDavid du Colombier {
140906943f9SDavid du Colombier 	"ctl",		{Qctl},		0,	0666,
141906943f9SDavid du Colombier };
142906943f9SDavid du Colombier 
143906943f9SDavid du Colombier char *usbmodename[] =
144906943f9SDavid du Colombier {
145906943f9SDavid du Colombier 	[OREAD]	"r",
146906943f9SDavid du Colombier 	[OWRITE]	"w",
147906943f9SDavid du Colombier 	[ORDWR]	"rw",
148906943f9SDavid du Colombier };
149906943f9SDavid du Colombier 
150906943f9SDavid du Colombier static char *ttname[] =
151906943f9SDavid du Colombier {
152906943f9SDavid du Colombier 	[Tnone]	"none",
153906943f9SDavid du Colombier 	[Tctl]	"control",
154906943f9SDavid du Colombier 	[Tiso]	"iso",
155906943f9SDavid du Colombier 	[Tintr]	"interrupt",
156906943f9SDavid du Colombier 	[Tbulk]	"bulk",
157906943f9SDavid du Colombier };
158906943f9SDavid du Colombier 
159906943f9SDavid du Colombier static char *spname[] =
160906943f9SDavid du Colombier {
161906943f9SDavid du Colombier 	[Fullspeed]	"full",
162906943f9SDavid du Colombier 	[Lowspeed]	"low",
163906943f9SDavid du Colombier 	[Highspeed]	"high",
164906943f9SDavid du Colombier 	[Nospeed]	"no",
165906943f9SDavid du Colombier };
166906943f9SDavid du Colombier 
167906943f9SDavid du Colombier static int	debug;
168906943f9SDavid du Colombier static Hcitype	hcitypes[Nhcis];
169906943f9SDavid du Colombier static Hci*	hcis[Nhcis];
170906943f9SDavid du Colombier static QLock	epslck;		/* add, del, lookup endpoints */
171906943f9SDavid du Colombier static Ep*	eps[Neps];	/* all endpoints known */
172906943f9SDavid du Colombier static int	epmax;		/* 1 + last endpoint index used  */
173906943f9SDavid du Colombier static int	usbidgen;	/* device address generator */
174906943f9SDavid du Colombier 
175906943f9SDavid du Colombier /*
176906943f9SDavid du Colombier  * Is there something like this in a library? should it be?
177906943f9SDavid du Colombier  */
178906943f9SDavid du Colombier char*
179906943f9SDavid du Colombier seprintdata(char *s, char *se, uchar *d, int n)
180906943f9SDavid du Colombier {
18184860c5dSDavid du Colombier 	int i, l;
182906943f9SDavid du Colombier 
183906943f9SDavid du Colombier 	s = seprint(s, se, " %#p[%d]: ", d, n);
184906943f9SDavid du Colombier 	l = n;
185906943f9SDavid du Colombier 	if(l > 10)
186906943f9SDavid du Colombier 		l = 10;
187906943f9SDavid du Colombier 	for(i=0; i<l; i++)
188906943f9SDavid du Colombier 		s = seprint(s, se, " %2.2ux", d[i]);
189906943f9SDavid du Colombier 	if(l < n)
190906943f9SDavid du Colombier 		s = seprint(s, se, "...");
191906943f9SDavid du Colombier 	return s;
192906943f9SDavid du Colombier }
193906943f9SDavid du Colombier 
194906943f9SDavid du Colombier static int
195906943f9SDavid du Colombier name2speed(char *name)
196906943f9SDavid du Colombier {
197906943f9SDavid du Colombier 	int i;
198906943f9SDavid du Colombier 
199906943f9SDavid du Colombier 	for(i = 0; i < nelem(spname); i++)
200906943f9SDavid du Colombier 		if(strcmp(name, spname[i]) == 0)
201906943f9SDavid du Colombier 			return i;
202906943f9SDavid du Colombier 	return Nospeed;
203906943f9SDavid du Colombier }
204906943f9SDavid du Colombier 
205906943f9SDavid du Colombier static int
206906943f9SDavid du Colombier name2ttype(char *name)
207906943f9SDavid du Colombier {
208906943f9SDavid du Colombier 	int i;
209906943f9SDavid du Colombier 
210906943f9SDavid du Colombier 	for(i = 0; i < nelem(ttname); i++)
211906943f9SDavid du Colombier 		if(strcmp(name, ttname[i]) == 0)
212906943f9SDavid du Colombier 			return i;
213906943f9SDavid du Colombier 	/* may be a std. USB ep. type */
214906943f9SDavid du Colombier 	i = strtol(name, nil, 0);
215906943f9SDavid du Colombier 	switch(i+1){
216906943f9SDavid du Colombier 	case Tctl:
217906943f9SDavid du Colombier 	case Tiso:
218906943f9SDavid du Colombier 	case Tbulk:
219906943f9SDavid du Colombier 	case Tintr:
220906943f9SDavid du Colombier 		return i+1;
221906943f9SDavid du Colombier 	default:
222906943f9SDavid du Colombier 		return Tnone;
223906943f9SDavid du Colombier 	}
224906943f9SDavid du Colombier }
225906943f9SDavid du Colombier 
226906943f9SDavid du Colombier static int
227906943f9SDavid du Colombier name2mode(char *mode)
228906943f9SDavid du Colombier {
229906943f9SDavid du Colombier 	int i;
230906943f9SDavid du Colombier 
231906943f9SDavid du Colombier 	for(i = 0; i < nelem(usbmodename); i++)
232906943f9SDavid du Colombier 		if(strcmp(mode, usbmodename[i]) == 0)
233906943f9SDavid du Colombier 			return i;
234906943f9SDavid du Colombier 	return -1;
235906943f9SDavid du Colombier }
236906943f9SDavid du Colombier 
237906943f9SDavid du Colombier static int
238906943f9SDavid du Colombier qid2epidx(int q)
239906943f9SDavid du Colombier {
240906943f9SDavid du Colombier 	q = (q-Qep0dir)/4;
241906943f9SDavid du Colombier 	if(q < 0 || q >= epmax || eps[q] == nil)
242906943f9SDavid du Colombier 		return -1;
243906943f9SDavid du Colombier 	return q;
244906943f9SDavid du Colombier }
245906943f9SDavid du Colombier 
246906943f9SDavid du Colombier static int
247906943f9SDavid du Colombier isqtype(int q, int type)
248906943f9SDavid du Colombier {
249906943f9SDavid du Colombier 	if(q < Qep0dir)
250906943f9SDavid du Colombier 		return 0;
251906943f9SDavid du Colombier 	q -= Qep0dir;
252906943f9SDavid du Colombier 	return (q & 3) == type;
253906943f9SDavid du Colombier }
2549a747e4fSDavid du Colombier 
2559a747e4fSDavid du Colombier void
256906943f9SDavid du Colombier addhcitype(char* t, int (*r)(Hci*))
2579a747e4fSDavid du Colombier {
2589a747e4fSDavid du Colombier 	static int ntype;
2599a747e4fSDavid du Colombier 
260906943f9SDavid du Colombier 	if(ntype == Nhcis)
2619a747e4fSDavid du Colombier 		panic("too many USB host interface types");
262906943f9SDavid du Colombier 	hcitypes[ntype].type = t;
263906943f9SDavid du Colombier 	hcitypes[ntype].reset = r;
2649a747e4fSDavid du Colombier 	ntype++;
2659a747e4fSDavid du Colombier }
2669a747e4fSDavid du Colombier 
267906943f9SDavid du Colombier static char*
268906943f9SDavid du Colombier seprintep(char *s, char *se, Ep *ep, int all)
2699a747e4fSDavid du Colombier {
270a23bc242SDavid du Colombier 	static char* dsnames[] = { "config", "enabled", "detached", "reset" };
2719a747e4fSDavid du Colombier 	Udev *d;
2729a747e4fSDavid du Colombier 	int i;
273906943f9SDavid du Colombier 	int di;
2749a747e4fSDavid du Colombier 
275906943f9SDavid du Colombier 	d = ep->dev;
276906943f9SDavid du Colombier 
277906943f9SDavid du Colombier 	qlock(ep);
2789a747e4fSDavid du Colombier 	if(waserror()){
279906943f9SDavid du Colombier 		qunlock(ep);
2809a747e4fSDavid du Colombier 		nexterror();
2819a747e4fSDavid du Colombier 	}
282906943f9SDavid du Colombier 	di = ep->dev->nb;
283906943f9SDavid du Colombier 	if(all)
284906943f9SDavid du Colombier 		s = seprint(s, se, "dev %d ep %d ", di, ep->nb);
285906943f9SDavid du Colombier 	s = seprint(s, se, "%s", dsnames[ep->dev->state]);
286906943f9SDavid du Colombier 	s = seprint(s, se, " %s", ttname[ep->ttype]);
287906943f9SDavid du Colombier 	assert(ep->mode == OREAD || ep->mode == OWRITE || ep->mode == ORDWR);
288906943f9SDavid du Colombier 	s = seprint(s, se, " %s", usbmodename[ep->mode]);
289906943f9SDavid du Colombier 	s = seprint(s, se, " speed %s", spname[d->speed]);
290906943f9SDavid du Colombier 	s = seprint(s, se, " maxpkt %ld", ep->maxpkt);
291906943f9SDavid du Colombier 	s = seprint(s, se, " pollival %ld", ep->pollival);
292906943f9SDavid du Colombier 	s = seprint(s, se, " samplesz %ld", ep->samplesz);
293906943f9SDavid du Colombier 	s = seprint(s, se, " hz %ld", ep->hz);
294906943f9SDavid du Colombier 	s = seprint(s, se, " hub %d", ep->dev->hub);
295906943f9SDavid du Colombier 	s = seprint(s, se, " port %d", ep->dev->port);
296906943f9SDavid du Colombier 	if(ep->inuse)
297906943f9SDavid du Colombier 		s = seprint(s, se, " busy");
298906943f9SDavid du Colombier 	else
299906943f9SDavid du Colombier 		s = seprint(s, se, " idle");
300906943f9SDavid du Colombier 	if(all){
301906943f9SDavid du Colombier 		s = seprint(s, se, " load %uld", ep->load);
302906943f9SDavid du Colombier 		s = seprint(s, se, " ref %ld addr %#p", ep->ref, ep);
303906943f9SDavid du Colombier 		s = seprint(s, se, " idx %d", ep->idx);
304906943f9SDavid du Colombier 		if(ep->name != nil)
305906943f9SDavid du Colombier 			s = seprint(s, se, " name '%s'", ep->name);
306d37e33ffSDavid du Colombier 		if(ep->tmout != 0)
307d37e33ffSDavid du Colombier 			s = seprint(s, se, " tmout");
308906943f9SDavid du Colombier 		if(ep == ep->ep0){
309906943f9SDavid du Colombier 			s = seprint(s, se, " ctlrno %#x", ep->hp->ctlrno);
310906943f9SDavid du Colombier 			s = seprint(s, se, " eps:");
311906943f9SDavid du Colombier 			for(i = 0; i < nelem(d->eps); i++)
312906943f9SDavid du Colombier 				if(d->eps[i] != nil)
313906943f9SDavid du Colombier 					s = seprint(s, se, " ep%d.%d", di, i);
3149a747e4fSDavid du Colombier 		}
315906943f9SDavid du Colombier 	}
316906943f9SDavid du Colombier 	if(ep->info != nil)
317c8cbc0e9SDavid du Colombier 		s = seprint(s, se, "\n%s %s\n", ep->info, ep->hp->type);
318906943f9SDavid du Colombier 	else
319906943f9SDavid du Colombier 		s = seprint(s, se, "\n");
320906943f9SDavid du Colombier 	qunlock(ep);
3219a747e4fSDavid du Colombier 	poperror();
322906943f9SDavid du Colombier 	return s;
323906943f9SDavid du Colombier }
324906943f9SDavid du Colombier 
325906943f9SDavid du Colombier static Ep*
326906943f9SDavid du Colombier epalloc(Hci *hp)
327906943f9SDavid du Colombier {
328906943f9SDavid du Colombier 	Ep *ep;
329906943f9SDavid du Colombier 	int i;
330906943f9SDavid du Colombier 
331*305b51b8SDavid du Colombier 	ep = smalloc(sizeof(Ep));
332906943f9SDavid du Colombier 	ep->ref = 1;
333906943f9SDavid du Colombier 	qlock(&epslck);
334906943f9SDavid du Colombier 	for(i = 0; i < Neps; i++)
335906943f9SDavid du Colombier 		if(eps[i] == nil)
336906943f9SDavid du Colombier 			break;
337906943f9SDavid du Colombier 	if(i == Neps){
338906943f9SDavid du Colombier 		qunlock(&epslck);
339906943f9SDavid du Colombier 		free(ep);
340906943f9SDavid du Colombier 		print("usb: bug: too few endpoints.\n");
341906943f9SDavid du Colombier 		return nil;
342906943f9SDavid du Colombier 	}
343906943f9SDavid du Colombier 	ep->idx = i;
344906943f9SDavid du Colombier 	if(epmax <= i)
345906943f9SDavid du Colombier 		epmax = i+1;
346906943f9SDavid du Colombier 	eps[i] = ep;
347906943f9SDavid du Colombier 	ep->hp = hp;
348906943f9SDavid du Colombier 	ep->maxpkt = 8;
349906943f9SDavid du Colombier 	ep->ntds = 1;
350906943f9SDavid du Colombier 	ep->samplesz = ep->pollival = ep->hz = 0; /* make them void */
351906943f9SDavid du Colombier 	qunlock(&epslck);
352906943f9SDavid du Colombier 	return ep;
353906943f9SDavid du Colombier }
354906943f9SDavid du Colombier 
355906943f9SDavid du Colombier static Ep*
356906943f9SDavid du Colombier getep(int i)
357906943f9SDavid du Colombier {
358906943f9SDavid du Colombier 	Ep *ep;
359906943f9SDavid du Colombier 
360906943f9SDavid du Colombier 	if(i < 0 || i >= epmax || eps[i] == nil)
361906943f9SDavid du Colombier 		return nil;
362906943f9SDavid du Colombier 	qlock(&epslck);
363906943f9SDavid du Colombier 	ep = eps[i];
364906943f9SDavid du Colombier 	if(ep != nil)
365906943f9SDavid du Colombier 		incref(ep);
366906943f9SDavid du Colombier 	qunlock(&epslck);
367906943f9SDavid du Colombier 	return ep;
3689a747e4fSDavid du Colombier }
3699a747e4fSDavid du Colombier 
3709a747e4fSDavid du Colombier static void
371906943f9SDavid du Colombier putep(Ep *ep)
372906943f9SDavid du Colombier {
373906943f9SDavid du Colombier 	Udev *d;
374906943f9SDavid du Colombier 
375906943f9SDavid du Colombier 	if(ep != nil && decref(ep) == 0){
376906943f9SDavid du Colombier 		d = ep->dev;
377906943f9SDavid du Colombier 		deprint("usb: ep%d.%d %#p released\n", d->nb, ep->nb, ep);
378906943f9SDavid du Colombier 		qlock(&epslck);
379906943f9SDavid du Colombier 		eps[ep->idx] = nil;
380906943f9SDavid du Colombier 		if(ep->idx == epmax-1)
381906943f9SDavid du Colombier 			epmax--;
382906943f9SDavid du Colombier 		if(ep == ep->ep0 && ep->dev != nil && ep->dev->nb == usbidgen)
383906943f9SDavid du Colombier 			usbidgen--;
384906943f9SDavid du Colombier 		qunlock(&epslck);
385906943f9SDavid du Colombier 		if(d != nil){
386906943f9SDavid du Colombier 			qlock(ep->ep0);
387906943f9SDavid du Colombier 			d->eps[ep->nb] = nil;
388906943f9SDavid du Colombier 			qunlock(ep->ep0);
389906943f9SDavid du Colombier 		}
390906943f9SDavid du Colombier 		if(ep->ep0 != ep){
391906943f9SDavid du Colombier 			putep(ep->ep0);
392906943f9SDavid du Colombier 			ep->ep0 = nil;
393906943f9SDavid du Colombier 		}
394906943f9SDavid du Colombier 		free(ep->info);
395906943f9SDavid du Colombier 		free(ep->name);
396906943f9SDavid du Colombier 		free(ep);
397906943f9SDavid du Colombier 	}
398906943f9SDavid du Colombier }
399906943f9SDavid du Colombier 
400906943f9SDavid du Colombier static void
401906943f9SDavid du Colombier dumpeps(void)
4029a747e4fSDavid du Colombier {
4039a747e4fSDavid du Colombier 	int i;
404906943f9SDavid du Colombier 	static char buf[512];
405906943f9SDavid du Colombier 	char *s;
406906943f9SDavid du Colombier 	char *e;
407906943f9SDavid du Colombier 	Ep *ep;
4089a747e4fSDavid du Colombier 
409906943f9SDavid du Colombier 	print("usb dump eps: epmax %d Neps %d (ref=1+ for dump):\n", epmax, Neps);
410906943f9SDavid du Colombier 	for(i = 0; i < epmax; i++){
411906943f9SDavid du Colombier 		s = buf;
412906943f9SDavid du Colombier 		e = buf+sizeof(buf);
413906943f9SDavid du Colombier 		ep = getep(i);
414906943f9SDavid du Colombier 		if(ep != nil){
415906943f9SDavid du Colombier 			if(waserror()){
416906943f9SDavid du Colombier 				putep(ep);
417906943f9SDavid du Colombier 				nexterror();
4189a747e4fSDavid du Colombier 			}
419906943f9SDavid du Colombier 			s = seprint(s, e, "ep%d.%d ", ep->dev->nb, ep->nb);
420906943f9SDavid du Colombier 			seprintep(s, e, ep, 1);
421906943f9SDavid du Colombier 			print("%s", buf);
422906943f9SDavid du Colombier 			ep->hp->seprintep(buf, e, ep);
423906943f9SDavid du Colombier 			print("%s", buf);
424906943f9SDavid du Colombier 			poperror();
425906943f9SDavid du Colombier 			putep(ep);
426906943f9SDavid du Colombier 		}
427906943f9SDavid du Colombier 	}
428906943f9SDavid du Colombier 	print("usb dump hcis:\n");
429906943f9SDavid du Colombier 	for(i = 0; i < Nhcis; i++)
430906943f9SDavid du Colombier 		if(hcis[i] != nil)
431906943f9SDavid du Colombier 			hcis[i]->dump(hcis[i]);
432906943f9SDavid du Colombier }
433906943f9SDavid du Colombier 
434906943f9SDavid du Colombier static int
435906943f9SDavid du Colombier newusbid(Hci *)
436906943f9SDavid du Colombier {
437906943f9SDavid du Colombier 	int id;
438906943f9SDavid du Colombier 
439906943f9SDavid du Colombier 	qlock(&epslck);
440906943f9SDavid du Colombier 	id = ++usbidgen;
441906943f9SDavid du Colombier 	if(id >= 0x7F)
442906943f9SDavid du Colombier 		print("#u: too many device addresses; reuse them more\n");
443906943f9SDavid du Colombier 	qunlock(&epslck);
444906943f9SDavid du Colombier 	return id;
445906943f9SDavid du Colombier }
446906943f9SDavid du Colombier 
447906943f9SDavid du Colombier /*
448906943f9SDavid du Colombier  * Create endpoint 0 for a new device
449906943f9SDavid du Colombier  */
450906943f9SDavid du Colombier static Ep*
451906943f9SDavid du Colombier newdev(Hci *hp, int ishub, int isroot)
452906943f9SDavid du Colombier {
453906943f9SDavid du Colombier 	Ep *ep;
454906943f9SDavid du Colombier 	Udev *d;
455906943f9SDavid du Colombier 
456906943f9SDavid du Colombier 	ep = epalloc(hp);
457*305b51b8SDavid du Colombier 	d = ep->dev = smalloc(sizeof(Udev));
458906943f9SDavid du Colombier 	d->nb = newusbid(hp);
459906943f9SDavid du Colombier 	d->eps[0] = ep;
460906943f9SDavid du Colombier 	ep->nb = 0;
461906943f9SDavid du Colombier 	ep->toggle[0] = ep->toggle[1] = 0;
462906943f9SDavid du Colombier 	d->ishub = ishub;
463906943f9SDavid du Colombier 	d->isroot = isroot;
464906943f9SDavid du Colombier 	if(hp->highspeed != 0)
465906943f9SDavid du Colombier 		d->speed = Highspeed;
466906943f9SDavid du Colombier 	else
467906943f9SDavid du Colombier 		d->speed = Fullspeed;
468906943f9SDavid du Colombier 	d->state = Dconfig;		/* address not yet set */
469906943f9SDavid du Colombier 	ep->dev = d;
470906943f9SDavid du Colombier 	ep->ep0 = ep;			/* no ref counted here */
471906943f9SDavid du Colombier 	ep->ttype = Tctl;
472d37e33ffSDavid du Colombier 	ep->tmout = Xfertmout;
473906943f9SDavid du Colombier 	ep->mode = ORDWR;
474906943f9SDavid du Colombier 	dprint("newdev %#p ep%d.%d %#p\n", d, d->nb, ep->nb, ep);
475906943f9SDavid du Colombier 	return ep;
476906943f9SDavid du Colombier }
477906943f9SDavid du Colombier 
478906943f9SDavid du Colombier /*
479906943f9SDavid du Colombier  * Create a new endpoint for the device
480906943f9SDavid du Colombier  * accessed via the given endpoint 0.
481906943f9SDavid du Colombier  */
482906943f9SDavid du Colombier static Ep*
483906943f9SDavid du Colombier newdevep(Ep *ep, int i, int tt, int mode)
484906943f9SDavid du Colombier {
485906943f9SDavid du Colombier 	Ep *nep;
486906943f9SDavid du Colombier 	Udev *d;
487906943f9SDavid du Colombier 
488906943f9SDavid du Colombier 	d = ep->dev;
489906943f9SDavid du Colombier 	if(d->eps[i] != nil)
490906943f9SDavid du Colombier 		error("endpoint already in use");
491906943f9SDavid du Colombier 	nep = epalloc(ep->hp);
492906943f9SDavid du Colombier 	incref(ep);
493906943f9SDavid du Colombier 	d->eps[i] = nep;
494906943f9SDavid du Colombier 	nep->nb = i;
495906943f9SDavid du Colombier 	nep->toggle[0] = nep->toggle[1] = 0;
496906943f9SDavid du Colombier 	nep->ep0 = ep;
497906943f9SDavid du Colombier 	nep->dev = ep->dev;
498906943f9SDavid du Colombier 	nep->mode = mode;
499906943f9SDavid du Colombier 	nep->ttype = tt;
500906943f9SDavid du Colombier 	nep->debug = ep->debug;
501d37e33ffSDavid du Colombier 	/* set defaults */
502d37e33ffSDavid du Colombier 	switch(tt){
503d37e33ffSDavid du Colombier 	case Tctl:
504d37e33ffSDavid du Colombier 		nep->tmout = Xfertmout;
505d37e33ffSDavid du Colombier 		break;
506d37e33ffSDavid du Colombier 	case Tintr:
507906943f9SDavid du Colombier 		nep->pollival = 10;
508d37e33ffSDavid du Colombier 		break;
509d37e33ffSDavid du Colombier 	case Tiso:
510d37e33ffSDavid du Colombier 		nep->tmout = Xfertmout;
511d37e33ffSDavid du Colombier 		nep->pollival = 10;
512906943f9SDavid du Colombier 		nep->samplesz = 4;
513906943f9SDavid du Colombier 		nep->hz = 44100;
514d37e33ffSDavid du Colombier 		break;
515906943f9SDavid du Colombier 	}
516906943f9SDavid du Colombier 	deprint("newdevep ep%d.%d %#p\n", d->nb, nep->nb, nep);
517906943f9SDavid du Colombier 	return ep;
518906943f9SDavid du Colombier }
519906943f9SDavid du Colombier 
520906943f9SDavid du Colombier static int
521906943f9SDavid du Colombier epdataperm(int mode)
522906943f9SDavid du Colombier {
523906943f9SDavid du Colombier 
524906943f9SDavid du Colombier 	switch(mode){
525906943f9SDavid du Colombier 	case OREAD:
526906943f9SDavid du Colombier 		return 0440|DMEXCL;
527906943f9SDavid du Colombier 		break;
528906943f9SDavid du Colombier 	case OWRITE:
529906943f9SDavid du Colombier 		return 0220|DMEXCL;
530906943f9SDavid du Colombier 		break;
531906943f9SDavid du Colombier 	default:
532906943f9SDavid du Colombier 		return 0660|DMEXCL;
5339a747e4fSDavid du Colombier 	}
5349a747e4fSDavid du Colombier }
5359a747e4fSDavid du Colombier 
5369a747e4fSDavid du Colombier static int
5379a747e4fSDavid du Colombier usbgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
5389a747e4fSDavid du Colombier {
5399a747e4fSDavid du Colombier 	Qid q;
540906943f9SDavid du Colombier 	Dirtab *dir;
541906943f9SDavid du Colombier 	int perm;
542906943f9SDavid du Colombier 	char *se;
543906943f9SDavid du Colombier 	Ep *ep;
544906943f9SDavid du Colombier 	int nb;
545906943f9SDavid du Colombier 	int mode;
5469a747e4fSDavid du Colombier 
547906943f9SDavid du Colombier 	if(0)ddprint("usbgen q %#x s %d...", QID(c->qid), s);
5489a747e4fSDavid du Colombier 	if(s == DEVDOTDOT){
549906943f9SDavid du Colombier 		if(QID(c->qid) <= Qusbdir){
550906943f9SDavid du Colombier 			mkqid(&q, Qdir, 0, QTDIR);
551906943f9SDavid du Colombier 			devdir(c, q, "#u", 0, eve, 0555, dp);
552906943f9SDavid du Colombier 		}else{
553906943f9SDavid du Colombier 			mkqid(&q, Qusbdir, 0, QTDIR);
554906943f9SDavid du Colombier 			devdir(c, q, "usb", 0, eve, 0555, dp);
555906943f9SDavid du Colombier 		}
556906943f9SDavid du Colombier 		if(0)ddprint("ok\n");
5579a747e4fSDavid du Colombier 		return 1;
5589a747e4fSDavid du Colombier 	}
5599a747e4fSDavid du Colombier 
560906943f9SDavid du Colombier 	switch(QID(c->qid)){
561906943f9SDavid du Colombier 	case Qdir:				/* list #u */
562906943f9SDavid du Colombier 		if(s == 0){
563906943f9SDavid du Colombier 			mkqid(&q, Qusbdir, 0, QTDIR);
564906943f9SDavid du Colombier 			devdir(c, q, "usb", 0, eve, 0555, dp);
565906943f9SDavid du Colombier 			if(0)ddprint("ok\n");
5669a747e4fSDavid du Colombier 			return 1;
5679a747e4fSDavid du Colombier 		}
568906943f9SDavid du Colombier 		s--;
569906943f9SDavid du Colombier 		if(s < 0 || s >= epmax)
570906943f9SDavid du Colombier 			goto Fail;
571906943f9SDavid du Colombier 		ep = getep(s);
572906943f9SDavid du Colombier 		if(ep == nil || ep->name == nil){
573906943f9SDavid du Colombier 			if(ep != nil)
574906943f9SDavid du Colombier 				putep(ep);
575906943f9SDavid du Colombier 			if(0)ddprint("skip\n");
5769a747e4fSDavid du Colombier 			return 0;
577906943f9SDavid du Colombier 		}
578906943f9SDavid du Colombier 		if(waserror()){
579906943f9SDavid du Colombier 			putep(ep);
580906943f9SDavid du Colombier 			nexterror();
581906943f9SDavid du Colombier 		}
582906943f9SDavid du Colombier 		mkqid(&q, Qep0io+s*4, 0, QTFILE);
583906943f9SDavid du Colombier 		devdir(c, q, ep->name, 0, eve, epdataperm(ep->mode), dp);
584906943f9SDavid du Colombier 		putep(ep);
585906943f9SDavid du Colombier 		poperror();
586906943f9SDavid du Colombier 		if(0)ddprint("ok\n");
5879a747e4fSDavid du Colombier 		return 1;
5889a747e4fSDavid du Colombier 
589906943f9SDavid du Colombier 	case Qusbdir:				/* list #u/usb */
590906943f9SDavid du Colombier 	Usbdir:
591906943f9SDavid du Colombier 		if(s < nelem(usbdir)){
592906943f9SDavid du Colombier 			dir = &usbdir[s];
593906943f9SDavid du Colombier 			mkqid(&q, dir->qid.path, 0, QTFILE);
594906943f9SDavid du Colombier 			devdir(c, q, dir->name, dir->length, eve, dir->perm, dp);
595906943f9SDavid du Colombier 			if(0)ddprint("ok\n");
5969a747e4fSDavid du Colombier 			return 1;
5979a747e4fSDavid du Colombier 		}
598906943f9SDavid du Colombier 		s -= nelem(usbdir);
599906943f9SDavid du Colombier 		if(s < 0 || s >= epmax)
600906943f9SDavid du Colombier 			goto Fail;
601906943f9SDavid du Colombier 		ep = getep(s);
602906943f9SDavid du Colombier 		if(ep == nil){
603906943f9SDavid du Colombier 			if(0)ddprint("skip\n");
6049a747e4fSDavid du Colombier 			return 0;
605906943f9SDavid du Colombier 		}
606906943f9SDavid du Colombier 		if(waserror()){
607906943f9SDavid du Colombier 			putep(ep);
608906943f9SDavid du Colombier 			nexterror();
609906943f9SDavid du Colombier 		}
610906943f9SDavid du Colombier 		se = up->genbuf+sizeof(up->genbuf);
611906943f9SDavid du Colombier 		seprint(up->genbuf, se, "ep%d.%d", ep->dev->nb, ep->nb);
612906943f9SDavid du Colombier 		mkqid(&q, Qep0dir+4*s, 0, QTDIR);
613906943f9SDavid du Colombier 		putep(ep);
614906943f9SDavid du Colombier 		poperror();
615906943f9SDavid du Colombier 		devdir(c, q, up->genbuf, 0, eve, 0755, dp);
616906943f9SDavid du Colombier 		if(0)ddprint("ok\n");
617906943f9SDavid du Colombier 		return 1;
618906943f9SDavid du Colombier 
619906943f9SDavid du Colombier 	case Qctl:
620906943f9SDavid du Colombier 		s = 0;
621906943f9SDavid du Colombier 		goto Usbdir;
622906943f9SDavid du Colombier 
623906943f9SDavid du Colombier 	default:				/* list #u/usb/epN.M */
624906943f9SDavid du Colombier 		nb = qid2epidx(QID(c->qid));
625906943f9SDavid du Colombier 		ep = getep(nb);
626906943f9SDavid du Colombier 		if(ep == nil)
627906943f9SDavid du Colombier 			goto Fail;
628906943f9SDavid du Colombier 		mode = ep->mode;
629906943f9SDavid du Colombier 		putep(ep);
630906943f9SDavid du Colombier 		if(isqtype(QID(c->qid), Qepdir)){
631906943f9SDavid du Colombier 		Epdir:
632906943f9SDavid du Colombier 			switch(s){
633906943f9SDavid du Colombier 			case 0:
634906943f9SDavid du Colombier 				mkqid(&q, Qep0io+nb*4, 0, QTFILE);
635906943f9SDavid du Colombier 				perm = epdataperm(mode);
636906943f9SDavid du Colombier 				devdir(c, q, "data", 0, eve, perm, dp);
6379a747e4fSDavid du Colombier 				break;
638906943f9SDavid du Colombier 			case 1:
639906943f9SDavid du Colombier 				mkqid(&q, Qep0ctl+nb*4, 0, QTFILE);
640906943f9SDavid du Colombier 				devdir(c, q, "ctl", 0, eve, 0664, dp);
6419a747e4fSDavid du Colombier 				break;
6429a747e4fSDavid du Colombier 			default:
643906943f9SDavid du Colombier 				goto Fail;
6449a747e4fSDavid du Colombier 			}
645906943f9SDavid du Colombier 		}else if(isqtype(QID(c->qid), Qepctl)){
646906943f9SDavid du Colombier 			s = 1;
647906943f9SDavid du Colombier 			goto Epdir;
648906943f9SDavid du Colombier 		}else{
649906943f9SDavid du Colombier 			s = 0;
650906943f9SDavid du Colombier 			goto Epdir;
651906943f9SDavid du Colombier 		}
652906943f9SDavid du Colombier 		if(0)ddprint("ok\n");
6539a747e4fSDavid du Colombier 		return 1;
6549a747e4fSDavid du Colombier 	}
655906943f9SDavid du Colombier Fail:
656906943f9SDavid du Colombier 	if(0)ddprint("fail\n");
657906943f9SDavid du Colombier 	return -1;
658906943f9SDavid du Colombier }
659906943f9SDavid du Colombier 
660906943f9SDavid du Colombier static Hci*
661906943f9SDavid du Colombier hciprobe(int cardno, int ctlrno)
6629a747e4fSDavid du Colombier {
663906943f9SDavid du Colombier 	Hci *hp;
664906943f9SDavid du Colombier 	char *type;
665e4ac449cSDavid du Colombier 	char name[64];
666906943f9SDavid du Colombier 	static int epnb = 1;	/* guess the endpoint nb. for the controller */
6679a747e4fSDavid du Colombier 
668906943f9SDavid du Colombier 	ddprint("hciprobe %d %d\n", cardno, ctlrno);
669*305b51b8SDavid du Colombier 	hp = smalloc(sizeof(Hci));
670906943f9SDavid du Colombier 	hp->ctlrno = ctlrno;
671906943f9SDavid du Colombier 	hp->tbdf = BUSUNKNOWN;
6729a747e4fSDavid du Colombier 
6739a747e4fSDavid du Colombier 	if(cardno < 0){
674906943f9SDavid du Colombier 		if(isaconfig("usb", ctlrno, hp) == 0){
675906943f9SDavid du Colombier 			free(hp);
6769a747e4fSDavid du Colombier 			return nil;
6779a747e4fSDavid du Colombier 		}
678906943f9SDavid du Colombier 		for(cardno = 0; cardno < Nhcis; cardno++){
679906943f9SDavid du Colombier 			if(hcitypes[cardno].type == nil)
680906943f9SDavid du Colombier 				break;
681906943f9SDavid du Colombier 			type = hp->type;
6829a747e4fSDavid du Colombier 			if(type==nil || *type==0)
6839a747e4fSDavid du Colombier 				type = "uhci";
684906943f9SDavid du Colombier 			if(cistrcmp(hcitypes[cardno].type, type) == 0)
6859a747e4fSDavid du Colombier 				break;
6869a747e4fSDavid du Colombier 		}
6879a747e4fSDavid du Colombier 	}
6889a747e4fSDavid du Colombier 
689c8cbc0e9SDavid du Colombier 	if(cardno >= Nhcis || hcitypes[cardno].type == nil){
690c8cbc0e9SDavid du Colombier 		free(hp);
691c8cbc0e9SDavid du Colombier 		return nil;
692c8cbc0e9SDavid du Colombier 	}
693c8cbc0e9SDavid du Colombier 	dprint("%s...", hcitypes[cardno].type);
694c8cbc0e9SDavid du Colombier 	if(hcitypes[cardno].reset(hp) < 0){
695906943f9SDavid du Colombier 		free(hp);
6969a747e4fSDavid du Colombier 		return nil;
6979a747e4fSDavid du Colombier 	}
6989a747e4fSDavid du Colombier 
6999a747e4fSDavid du Colombier 	/*
7009a747e4fSDavid du Colombier 	 * IRQ2 doesn't really exist, it's used to gang the interrupt
7019a747e4fSDavid du Colombier 	 * controllers together. A device set to IRQ2 will appear on
7029a747e4fSDavid du Colombier 	 * the second interrupt controller as IRQ9.
7039a747e4fSDavid du Colombier 	 */
704906943f9SDavid du Colombier 	if(hp->irq == 2)
705906943f9SDavid du Colombier 		hp->irq = 9;
706906943f9SDavid du Colombier 	snprint(name, sizeof(name), "usb%s", hcitypes[cardno].type);
707906943f9SDavid du Colombier 	intrenable(hp->irq, hp->interrupt, hp, hp->tbdf, name);
7089a747e4fSDavid du Colombier 
709e4ac449cSDavid du Colombier 	/*
710e4ac449cSDavid du Colombier 	 * modern machines have too many usb controllers to list on
711e4ac449cSDavid du Colombier 	 * the console.
712e4ac449cSDavid du Colombier 	 */
713a23bc242SDavid du Colombier 	dprint("#u/usb/ep%d.0: %s: port 0x%luX irq %d\n",
714a23bc242SDavid du Colombier 		epnb, hcitypes[cardno].type, hp->port, hp->irq);
715e4ac449cSDavid du Colombier 	epnb++;
716906943f9SDavid du Colombier 	return hp;
7179a747e4fSDavid du Colombier }
7189a747e4fSDavid du Colombier 
7199a747e4fSDavid du Colombier static void
7209a747e4fSDavid du Colombier usbreset(void)
7219a747e4fSDavid du Colombier {
7229a747e4fSDavid du Colombier 	int cardno, ctlrno;
723906943f9SDavid du Colombier 	Hci *hp;
7249a747e4fSDavid du Colombier 
725ea58ad6fSDavid du Colombier 	if(getconf("*nousbprobe"))
726ea58ad6fSDavid du Colombier 		return;
727906943f9SDavid du Colombier 	dprint("usbreset\n");
728ea58ad6fSDavid du Colombier 
729906943f9SDavid du Colombier 	for(ctlrno = 0; ctlrno < Nhcis; ctlrno++)
730906943f9SDavid du Colombier 		if((hp = hciprobe(-1, ctlrno)) != nil)
731906943f9SDavid du Colombier 			hcis[ctlrno] = hp;
7329a747e4fSDavid du Colombier 	cardno = ctlrno = 0;
733906943f9SDavid du Colombier 	while(cardno < Nhcis && ctlrno < Nhcis && hcitypes[cardno].type != nil)
734906943f9SDavid du Colombier 		if(hcis[ctlrno] != nil)
7359a747e4fSDavid du Colombier 			ctlrno++;
736906943f9SDavid du Colombier 		else{
737906943f9SDavid du Colombier 			hp = hciprobe(cardno, ctlrno);
738906943f9SDavid du Colombier 			if(hp == nil)
7399a747e4fSDavid du Colombier 				cardno++;
740906943f9SDavid du Colombier 			hcis[ctlrno++] = hp;
7419a747e4fSDavid du Colombier 		}
742906943f9SDavid du Colombier 	if(hcis[Nhcis-1] != nil)
74384860c5dSDavid du Colombier 		print("usbreset: bug: Nhcis (%d) too small\n", Nhcis);
7449a747e4fSDavid du Colombier }
7459a747e4fSDavid du Colombier 
746906943f9SDavid du Colombier static void
7479a747e4fSDavid du Colombier usbinit(void)
7489a747e4fSDavid du Colombier {
749906943f9SDavid du Colombier 	Hci *hp;
7509a747e4fSDavid du Colombier 	int ctlrno;
751906943f9SDavid du Colombier 	Ep *d;
752906943f9SDavid du Colombier 	char info[40];
7539a747e4fSDavid du Colombier 
754906943f9SDavid du Colombier 	dprint("usbinit\n");
755906943f9SDavid du Colombier 	for(ctlrno = 0; ctlrno < Nhcis; ctlrno++){
756906943f9SDavid du Colombier 		hp = hcis[ctlrno];
757906943f9SDavid du Colombier 		if(hp != nil){
758906943f9SDavid du Colombier 			if(hp->init != nil)
759906943f9SDavid du Colombier 				hp->init(hp);
760906943f9SDavid du Colombier 			d = newdev(hp, 1, 1);		/* new root hub */
761906943f9SDavid du Colombier 			d->dev->state = Denabled;	/* although addr == 0 */
762906943f9SDavid du Colombier 			d->maxpkt = 64;
763906943f9SDavid du Colombier 			snprint(info, sizeof(info), "ports %d", hp->nports);
764906943f9SDavid du Colombier 			kstrdup(&d->info, info);
765906943f9SDavid du Colombier 		}
7669a747e4fSDavid du Colombier 	}
7679a747e4fSDavid du Colombier }
7689a747e4fSDavid du Colombier 
769906943f9SDavid du Colombier static Chan*
7709a747e4fSDavid du Colombier usbattach(char *spec)
7719a747e4fSDavid du Colombier {
772906943f9SDavid du Colombier 	return devattach(L'u', spec);
7739a747e4fSDavid du Colombier }
7749a747e4fSDavid du Colombier 
7759a747e4fSDavid du Colombier static Walkqid*
7769a747e4fSDavid du Colombier usbwalk(Chan *c, Chan *nc, char **name, int nname)
7779a747e4fSDavid du Colombier {
7789a747e4fSDavid du Colombier 	return devwalk(c, nc, name, nname, nil, 0, usbgen);
7799a747e4fSDavid du Colombier }
7809a747e4fSDavid du Colombier 
7819a747e4fSDavid du Colombier static int
7829a747e4fSDavid du Colombier usbstat(Chan *c, uchar *db, int n)
7839a747e4fSDavid du Colombier {
7849a747e4fSDavid du Colombier 	return devstat(c, db, n, nil, 0, usbgen);
7859a747e4fSDavid du Colombier }
7869a747e4fSDavid du Colombier 
787906943f9SDavid du Colombier /*
788906943f9SDavid du Colombier  * µs for the given transfer, for bandwidth allocation.
789906943f9SDavid du Colombier  * This is a very rough worst case for what 5.11.3
790906943f9SDavid du Colombier  * of the usb 2.0 spec says.
791906943f9SDavid du Colombier  * Also, we are using maxpkt and not actual transfer sizes.
792906943f9SDavid du Colombier  * Only when we are sure we
793906943f9SDavid du Colombier  * are not exceeding b/w might we consider adjusting it.
794906943f9SDavid du Colombier  */
795906943f9SDavid du Colombier static ulong
796906943f9SDavid du Colombier usbload(int speed, int maxpkt)
797906943f9SDavid du Colombier {
798906943f9SDavid du Colombier 	enum{ Hostns = 1000, Hubns = 333 };
799906943f9SDavid du Colombier 	ulong l;
800906943f9SDavid du Colombier 	ulong bs;
801906943f9SDavid du Colombier 
802906943f9SDavid du Colombier 	l = 0;
803906943f9SDavid du Colombier 	bs = 10UL * maxpkt;
804906943f9SDavid du Colombier 	switch(speed){
805906943f9SDavid du Colombier 	case Highspeed:
806906943f9SDavid du Colombier 		l = 55*8*2 + 2 * (3 + bs) + Hostns;
807906943f9SDavid du Colombier 		break;
808906943f9SDavid du Colombier 	case Fullspeed:
809906943f9SDavid du Colombier 		l = 9107 + 84 * (4 + bs) + Hostns;
810906943f9SDavid du Colombier 		break;
811906943f9SDavid du Colombier 	case Lowspeed:
812906943f9SDavid du Colombier 		l = 64107 + 2 * Hubns + 667 * (3 + bs) + Hostns;
813906943f9SDavid du Colombier 		break;
814906943f9SDavid du Colombier 	default:
815906943f9SDavid du Colombier 		print("usbload: bad speed %d\n", speed);
816906943f9SDavid du Colombier 		/* let it run */
817906943f9SDavid du Colombier 	}
818906943f9SDavid du Colombier 	return l / 1000UL;	/* in µs */
819906943f9SDavid du Colombier }
820906943f9SDavid du Colombier 
821906943f9SDavid du Colombier static Chan*
8229a747e4fSDavid du Colombier usbopen(Chan *c, int omode)
8239a747e4fSDavid du Colombier {
824906943f9SDavid du Colombier 	int q;
825906943f9SDavid du Colombier 	Ep *ep;
826906943f9SDavid du Colombier 	int mode;
8279a747e4fSDavid du Colombier 
828906943f9SDavid du Colombier 	mode = openmode(omode);
829906943f9SDavid du Colombier 	q = QID(c->qid);
830906943f9SDavid du Colombier 
831906943f9SDavid du Colombier 	if(q >= Qep0dir && qid2epidx(q) < 0)
832906943f9SDavid du Colombier 		error(Eio);
833906943f9SDavid du Colombier 	if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
8349a747e4fSDavid du Colombier 		return devopen(c, omode, nil, 0, usbgen);
8359a747e4fSDavid du Colombier 
836906943f9SDavid du Colombier 	ep = getep(qid2epidx(q));
837906943f9SDavid du Colombier 	if(ep == nil)
838906943f9SDavid du Colombier 		error(Eio);
839906943f9SDavid du Colombier 	deprint("usbopen q %#x fid %d omode %d\n", q, c->fid, mode);
8409a747e4fSDavid du Colombier 	if(waserror()){
841906943f9SDavid du Colombier 		putep(ep);
8429a747e4fSDavid du Colombier 		nexterror();
8439a747e4fSDavid du Colombier 	}
844906943f9SDavid du Colombier 	qlock(ep);
845906943f9SDavid du Colombier 	if(ep->inuse){
846906943f9SDavid du Colombier 		qunlock(ep);
8479a747e4fSDavid du Colombier 		error(Einuse);
848906943f9SDavid du Colombier 	}
849906943f9SDavid du Colombier 	ep->inuse = 1;
850906943f9SDavid du Colombier 	qunlock(ep);
851906943f9SDavid du Colombier 	if(waserror()){
852906943f9SDavid du Colombier 		ep->inuse = 0;
853906943f9SDavid du Colombier 		nexterror();
854906943f9SDavid du Colombier 	}
855906943f9SDavid du Colombier 	if(mode != OREAD && ep->mode == OREAD)
856906943f9SDavid du Colombier 		error(Eperm);
857906943f9SDavid du Colombier 	if(mode != OWRITE && ep->mode == OWRITE)
858906943f9SDavid du Colombier 		error(Eperm);
859906943f9SDavid du Colombier 	if(ep->ttype == Tnone)
860906943f9SDavid du Colombier 		error(Enotconf);
861906943f9SDavid du Colombier 	ep->clrhalt = 0;
862906943f9SDavid du Colombier 	ep->rhrepl = -1;
863906943f9SDavid du Colombier 	if(ep->load == 0)
864906943f9SDavid du Colombier 		ep->load = usbload(ep->dev->speed, ep->maxpkt);
865906943f9SDavid du Colombier 	ep->hp->epopen(ep);
8669a747e4fSDavid du Colombier 
867906943f9SDavid du Colombier 	poperror();	/* ep->inuse */
868906943f9SDavid du Colombier 	poperror();	/* don't putep(): ref kept for fid using the ep. */
869ade43d10SDavid du Colombier 
870906943f9SDavid du Colombier 	c->mode = mode;
8719a747e4fSDavid du Colombier 	c->flag |= COPEN;
8729a747e4fSDavid du Colombier 	c->offset = 0;
873906943f9SDavid du Colombier 	c->aux = nil;	/* paranoia */
8749a747e4fSDavid du Colombier 	return c;
8759a747e4fSDavid du Colombier }
8769a747e4fSDavid du Colombier 
877906943f9SDavid du Colombier static void
878906943f9SDavid du Colombier epclose(Ep *ep)
8799a747e4fSDavid du Colombier {
880906943f9SDavid du Colombier 	qlock(ep);
8819a747e4fSDavid du Colombier 	if(waserror()){
882906943f9SDavid du Colombier 		qunlock(ep);
8839a747e4fSDavid du Colombier 		nexterror();
8849a747e4fSDavid du Colombier 	}
885906943f9SDavid du Colombier 	if(ep->inuse){
886906943f9SDavid du Colombier 		ep->hp->epclose(ep);
887906943f9SDavid du Colombier 		ep->inuse = 0;
888906943f9SDavid du Colombier 	}
889906943f9SDavid du Colombier 	qunlock(ep);
890906943f9SDavid du Colombier 	poperror();
891906943f9SDavid du Colombier }
892906943f9SDavid du Colombier 
893906943f9SDavid du Colombier static void
894906943f9SDavid du Colombier usbclose(Chan *c)
895906943f9SDavid du Colombier {
896906943f9SDavid du Colombier 	int q;
897906943f9SDavid du Colombier 	Ep *ep;
898906943f9SDavid du Colombier 
899906943f9SDavid du Colombier 	q = QID(c->qid);
900906943f9SDavid du Colombier 	if(q < Qep0dir || isqtype(q, Qepctl) || isqtype(q, Qepdir))
901906943f9SDavid du Colombier 		return;
902906943f9SDavid du Colombier 
903906943f9SDavid du Colombier 	ep = getep(qid2epidx(q));
904906943f9SDavid du Colombier 	if(ep == nil)
905906943f9SDavid du Colombier 		return;
906906943f9SDavid du Colombier 	deprint("usbclose q %#x fid %d ref %ld\n", q, c->fid, ep->ref);
907906943f9SDavid du Colombier 	if(waserror()){
908906943f9SDavid du Colombier 		putep(ep);
909906943f9SDavid du Colombier 		nexterror();
910906943f9SDavid du Colombier 	}
9119a747e4fSDavid du Colombier 	if(c->flag & COPEN){
912906943f9SDavid du Colombier 		free(c->aux);
913906943f9SDavid du Colombier 		c->aux = nil;
914906943f9SDavid du Colombier 		epclose(ep);
915906943f9SDavid du Colombier 		putep(ep);	/* release ref kept since usbopen */
916906943f9SDavid du Colombier 		c->flag &= ~COPEN;
9179a747e4fSDavid du Colombier 	}
9189a747e4fSDavid du Colombier 	poperror();
919906943f9SDavid du Colombier 	putep(ep);
9209a747e4fSDavid du Colombier }
9219a747e4fSDavid du Colombier 
922906943f9SDavid du Colombier static long
923906943f9SDavid du Colombier ctlread(Chan *c, void *a, long n, vlong offset)
9249a747e4fSDavid du Colombier {
925906943f9SDavid du Colombier 	int q;
926906943f9SDavid du Colombier 	char *s;
927906943f9SDavid du Colombier 	char *us;
928906943f9SDavid du Colombier 	char *se;
929906943f9SDavid du Colombier 	Ep *ep;
930906943f9SDavid du Colombier 	int i;
9319a747e4fSDavid du Colombier 
932906943f9SDavid du Colombier 	q = QID(c->qid);
933906943f9SDavid du Colombier 	us = s = smalloc(READSTR);
934906943f9SDavid du Colombier 	se = s + READSTR;
935906943f9SDavid du Colombier 	if(waserror()){
936906943f9SDavid du Colombier 		free(us);
937906943f9SDavid du Colombier 		nexterror();
9389a747e4fSDavid du Colombier 	}
939906943f9SDavid du Colombier 	if(q == Qctl)
940906943f9SDavid du Colombier 		for(i = 0; i < epmax; i++){
941906943f9SDavid du Colombier 			ep = getep(i);
942906943f9SDavid du Colombier 			if(ep != nil){
943906943f9SDavid du Colombier 				if(waserror()){
944906943f9SDavid du Colombier 					putep(ep);
945906943f9SDavid du Colombier 					nexterror();
946906943f9SDavid du Colombier 				}
947906943f9SDavid du Colombier 				s = seprint(s, se, "ep%d.%d ", ep->dev->nb, ep->nb);
948906943f9SDavid du Colombier 				s = seprintep(s, se, ep, 0);
949906943f9SDavid du Colombier 				poperror();
950906943f9SDavid du Colombier 			}
951906943f9SDavid du Colombier 			putep(ep);
952906943f9SDavid du Colombier 		}
953906943f9SDavid du Colombier 	else{
954906943f9SDavid du Colombier 		ep = getep(qid2epidx(q));
955906943f9SDavid du Colombier 		if(ep == nil)
956906943f9SDavid du Colombier 			error(Eio);
957906943f9SDavid du Colombier 		if(waserror()){
958906943f9SDavid du Colombier 			putep(ep);
959906943f9SDavid du Colombier 			nexterror();
960906943f9SDavid du Colombier 		}
961906943f9SDavid du Colombier 		if(c->aux != nil){
962906943f9SDavid du Colombier 			/* After a new endpoint request we read
963906943f9SDavid du Colombier 			 * the new endpoint name back.
964906943f9SDavid du Colombier 			 */
965906943f9SDavid du Colombier 			strecpy(s, se, c->aux);
966906943f9SDavid du Colombier 			free(c->aux);
967906943f9SDavid du Colombier 			c->aux = nil;
968906943f9SDavid du Colombier 		}else
969906943f9SDavid du Colombier 			seprintep(s, se, ep, 0);
970906943f9SDavid du Colombier 		poperror();
971906943f9SDavid du Colombier 		putep(ep);
972906943f9SDavid du Colombier 	}
973906943f9SDavid du Colombier 	n = readstr(offset, a, n, us);
974906943f9SDavid du Colombier 	poperror();
975906943f9SDavid du Colombier 	free(us);
976906943f9SDavid du Colombier 	return n;
9779a747e4fSDavid du Colombier }
9789a747e4fSDavid du Colombier 
979906943f9SDavid du Colombier /*
980906943f9SDavid du Colombier  * Fake root hub emulation.
981906943f9SDavid du Colombier  */
982906943f9SDavid du Colombier static long
983906943f9SDavid du Colombier rhubread(Ep *ep, void *a, long n)
984906943f9SDavid du Colombier {
985906943f9SDavid du Colombier 	char *b;
986906943f9SDavid du Colombier 
987906943f9SDavid du Colombier 	if(ep->dev->isroot == 0 || ep->nb != 0 || n < 2)
988906943f9SDavid du Colombier 		return -1;
989906943f9SDavid du Colombier 	if(ep->rhrepl < 0)
990906943f9SDavid du Colombier 		return -1;
991906943f9SDavid du Colombier 
992906943f9SDavid du Colombier 	b = a;
993906943f9SDavid du Colombier 	memset(b, 0, n);
994906943f9SDavid du Colombier 	PUT2(b, ep->rhrepl);
995906943f9SDavid du Colombier 	ep->rhrepl = -1;
996906943f9SDavid du Colombier 	return n;
997906943f9SDavid du Colombier }
998906943f9SDavid du Colombier 
999906943f9SDavid du Colombier static long
1000906943f9SDavid du Colombier rhubwrite(Ep *ep, void *a, long n)
1001906943f9SDavid du Colombier {
1002906943f9SDavid du Colombier 	uchar *s;
1003906943f9SDavid du Colombier 	int cmd;
1004906943f9SDavid du Colombier 	int feature;
1005906943f9SDavid du Colombier 	int port;
1006906943f9SDavid du Colombier 	Hci *hp;
1007906943f9SDavid du Colombier 
1008906943f9SDavid du Colombier 	if(ep->dev == nil || ep->dev->isroot == 0 || ep->nb != 0)
1009906943f9SDavid du Colombier 		return -1;
1010906943f9SDavid du Colombier 	if(n != Rsetuplen)
1011906943f9SDavid du Colombier 		error("root hub is a toy hub");
1012906943f9SDavid du Colombier 	ep->rhrepl = -1;
1013906943f9SDavid du Colombier 	s = a;
1014906943f9SDavid du Colombier 	if(s[Rtype] != (Rh2d|Rclass|Rother) && s[Rtype] != (Rd2h|Rclass|Rother))
1015906943f9SDavid du Colombier 		error("root hub is a toy hub");
1016906943f9SDavid du Colombier 	hp = ep->hp;
1017906943f9SDavid du Colombier 	cmd = s[Rreq];
1018906943f9SDavid du Colombier 	feature = GET2(s+Rvalue);
1019906943f9SDavid du Colombier 	port = GET2(s+Rindex);
1020906943f9SDavid du Colombier 	if(port < 1 || port > hp->nports)
1021906943f9SDavid du Colombier 		error("bad hub port number");
1022906943f9SDavid du Colombier 	switch(feature){
1023906943f9SDavid du Colombier 	case Rportenable:
1024906943f9SDavid du Colombier 		ep->rhrepl = hp->portenable(hp, port, cmd == Rsetfeature);
1025906943f9SDavid du Colombier 		break;
1026906943f9SDavid du Colombier 	case Rportreset:
1027906943f9SDavid du Colombier 		ep->rhrepl = hp->portreset(hp, port, cmd == Rsetfeature);
1028906943f9SDavid du Colombier 		break;
1029906943f9SDavid du Colombier 	case Rgetstatus:
1030906943f9SDavid du Colombier 		ep->rhrepl = hp->portstatus(hp, port);
1031906943f9SDavid du Colombier 		break;
1032906943f9SDavid du Colombier 	default:
1033906943f9SDavid du Colombier 		ep->rhrepl = 0;
1034906943f9SDavid du Colombier 	}
1035906943f9SDavid du Colombier 	return n;
1036906943f9SDavid du Colombier }
1037906943f9SDavid du Colombier 
1038906943f9SDavid du Colombier static long
10399a747e4fSDavid du Colombier usbread(Chan *c, void *a, long n, vlong offset)
10409a747e4fSDavid du Colombier {
1041906943f9SDavid du Colombier 	int q;
1042906943f9SDavid du Colombier 	Ep *ep;
1043906943f9SDavid du Colombier 	int nr;
1044906943f9SDavid du Colombier 
1045906943f9SDavid du Colombier 	q = QID(c->qid);
10469a747e4fSDavid du Colombier 
10479a747e4fSDavid du Colombier 	if(c->qid.type == QTDIR)
10489a747e4fSDavid du Colombier 		return devdirread(c, a, n, nil, 0, usbgen);
10499a747e4fSDavid du Colombier 
1050906943f9SDavid du Colombier 	if(q == Qctl || isqtype(q, Qepctl))
1051906943f9SDavid du Colombier 		return ctlread(c, a, n, offset);
10529a747e4fSDavid du Colombier 
1053906943f9SDavid du Colombier 	ep = getep(qid2epidx(q));
1054906943f9SDavid du Colombier 	if(ep == nil)
10559a747e4fSDavid du Colombier 		error(Eio);
10569a747e4fSDavid du Colombier 	if(waserror()){
1057906943f9SDavid du Colombier 		putep(ep);
10589a747e4fSDavid du Colombier 		nexterror();
10599a747e4fSDavid du Colombier 	}
1060906943f9SDavid du Colombier 	if(ep->dev->state == Ddetach)
1061906943f9SDavid du Colombier 		error(Edetach);
1062906943f9SDavid du Colombier 	if(ep->mode == OWRITE || ep->inuse == 0)
1063906943f9SDavid du Colombier 		error(Ebadusefd);
1064906943f9SDavid du Colombier 	switch(ep->ttype){
1065906943f9SDavid du Colombier 	case Tnone:
1066906943f9SDavid du Colombier 		error("endpoint not configured");
1067906943f9SDavid du Colombier 	case Tctl:
1068906943f9SDavid du Colombier 		nr = rhubread(ep, a, n);
1069906943f9SDavid du Colombier 		if(nr >= 0){
1070906943f9SDavid du Colombier 			n = nr;
107102c76561SDavid du Colombier 			break;
10729a747e4fSDavid du Colombier 		}
1073906943f9SDavid du Colombier 		/* else fall */
1074906943f9SDavid du Colombier 	default:
1075906943f9SDavid du Colombier 		ddeprint("\nusbread q %#x fid %d cnt %ld off %lld\n",q,c->fid,n,offset);
1076906943f9SDavid du Colombier 		n = ep->hp->epread(ep, a, n);
1077906943f9SDavid du Colombier 		break;
10789a747e4fSDavid du Colombier 	}
10799a747e4fSDavid du Colombier 	poperror();
1080906943f9SDavid du Colombier 	putep(ep);
10819a747e4fSDavid du Colombier 	return n;
10829a747e4fSDavid du Colombier }
10839a747e4fSDavid du Colombier 
1084906943f9SDavid du Colombier static long
1085906943f9SDavid du Colombier pow2(int n)
10869a747e4fSDavid du Colombier {
108784860c5dSDavid du Colombier 	return 1 << n;
1088906943f9SDavid du Colombier }
1089906943f9SDavid du Colombier 
1090906943f9SDavid du Colombier static void
1091906943f9SDavid du Colombier setmaxpkt(Ep *ep, char* s)
1092906943f9SDavid du Colombier {
1093906943f9SDavid du Colombier 	long spp;	/* samples per packet */
1094906943f9SDavid du Colombier 
1095906943f9SDavid du Colombier 	if(ep->dev->speed == Highspeed)
1096906943f9SDavid du Colombier 		spp = (ep->hz * ep->pollival * ep->ntds + 7999) / 8000;
1097906943f9SDavid du Colombier 	else
1098906943f9SDavid du Colombier 		spp = (ep->hz * ep->pollival + 999) / 1000;
1099906943f9SDavid du Colombier 	ep->maxpkt = spp * ep->samplesz;
1100906943f9SDavid du Colombier 	deprint("usb: %s: setmaxpkt: hz %ld poll %ld"
1101906943f9SDavid du Colombier 		" ntds %d %s speed -> spp %ld maxpkt %ld\n", s,
1102906943f9SDavid du Colombier 		ep->hz, ep->pollival, ep->ntds, spname[ep->dev->speed],
1103906943f9SDavid du Colombier 		spp, ep->maxpkt);
1104906943f9SDavid du Colombier 	if(ep->maxpkt > 1024){
1105906943f9SDavid du Colombier 		print("usb: %s: maxpkt %ld > 1024. truncating\n", s, ep->maxpkt);
1106906943f9SDavid du Colombier 		ep->maxpkt = 1024;
1107906943f9SDavid du Colombier 	}
1108906943f9SDavid du Colombier }
1109906943f9SDavid du Colombier 
1110906943f9SDavid du Colombier /*
1111906943f9SDavid du Colombier  * Many endpoint ctls. simply update the portable representation
1112906943f9SDavid du Colombier  * of the endpoint. The actual controller driver will look
1113906943f9SDavid du Colombier  * at them to setup the endpoints as dictated.
1114906943f9SDavid du Colombier  */
1115906943f9SDavid du Colombier static long
1116906943f9SDavid du Colombier epctl(Ep *ep, Chan *c, void *a, long n)
1117906943f9SDavid du Colombier {
111884860c5dSDavid du Colombier 	int i, l, mode, nb, tt;
111984860c5dSDavid du Colombier 	char *b, *s;
112084860c5dSDavid du Colombier 	Cmdbuf *cb;
112184860c5dSDavid du Colombier 	Cmdtab *ct;
1122906943f9SDavid du Colombier 	Ep *nep;
11239a747e4fSDavid du Colombier 	Udev *d;
112484860c5dSDavid du Colombier 	static char *Info = "info ";
1125906943f9SDavid du Colombier 
1126906943f9SDavid du Colombier 	d = ep->dev;
1127906943f9SDavid du Colombier 
1128906943f9SDavid du Colombier 	cb = parsecmd(a, n);
1129906943f9SDavid du Colombier 	if(waserror()){
1130906943f9SDavid du Colombier 		free(cb);
1131906943f9SDavid du Colombier 		nexterror();
1132906943f9SDavid du Colombier 	}
1133906943f9SDavid du Colombier 	ct = lookupcmd(cb, epctls, nelem(epctls));
1134906943f9SDavid du Colombier 	if(ct == nil)
1135906943f9SDavid du Colombier 		error(Ebadctl);
1136906943f9SDavid du Colombier 	i = ct->index;
1137a23bc242SDavid du Colombier 	if(i == CMnew || i == CMspeed || i == CMhub || i == CMpreset)
1138906943f9SDavid du Colombier 		if(ep != ep->ep0)
1139906943f9SDavid du Colombier 			error("allowed only on a setup endpoint");
1140906943f9SDavid du Colombier 	if(i != CMclrhalt && i != CMdetach && i != CMdebugep && i != CMname)
1141906943f9SDavid du Colombier 		if(ep != ep->ep0 && ep->inuse != 0)
1142906943f9SDavid du Colombier 			error("must configure before using");
1143906943f9SDavid du Colombier 	switch(i){
1144906943f9SDavid du Colombier 	case CMnew:
1145906943f9SDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1146906943f9SDavid du Colombier 		nb = strtol(cb->f[1], nil, 0);
1147906943f9SDavid du Colombier 		if(nb < 0 || nb >= Ndeveps)
1148906943f9SDavid du Colombier 			error("bad endpoint number");
1149906943f9SDavid du Colombier 		tt = name2ttype(cb->f[2]);
1150906943f9SDavid du Colombier 		if(tt == Tnone)
1151906943f9SDavid du Colombier 			error("unknown endpoint type");
1152906943f9SDavid du Colombier 		mode = name2mode(cb->f[3]);
1153906943f9SDavid du Colombier 		if(mode < 0)
1154906943f9SDavid du Colombier 			error("unknown i/o mode");
1155906943f9SDavid du Colombier 		newdevep(ep, nb, tt, mode);
1156906943f9SDavid du Colombier 		break;
1157906943f9SDavid du Colombier 	case CMnewdev:
1158906943f9SDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1159906943f9SDavid du Colombier 		if(ep != ep->ep0 || d->ishub == 0)
1160906943f9SDavid du Colombier 			error("not a hub setup endpoint");
1161906943f9SDavid du Colombier 		l = name2speed(cb->f[1]);
1162906943f9SDavid du Colombier 		if(l == Nospeed)
1163906943f9SDavid du Colombier 			error("speed must be full|low|high");
1164906943f9SDavid du Colombier 		nep = newdev(ep->hp, 0, 0);
1165906943f9SDavid du Colombier 		nep->dev->speed = l;
1166906943f9SDavid du Colombier 		if(nep->dev->speed  != Lowspeed)
1167906943f9SDavid du Colombier 			nep->maxpkt = 64;	/* assume full speed */
1168906943f9SDavid du Colombier 		nep->dev->hub = d->nb;
1169906943f9SDavid du Colombier 		nep->dev->port = atoi(cb->f[2]);
1170906943f9SDavid du Colombier 		/* next read request will read
1171906943f9SDavid du Colombier 		 * the name for the new endpoint
1172906943f9SDavid du Colombier 		 */
1173906943f9SDavid du Colombier 		l = sizeof(up->genbuf);
1174906943f9SDavid du Colombier 		snprint(up->genbuf, l, "ep%d.%d", nep->dev->nb, nep->nb);
1175906943f9SDavid du Colombier 		kstrdup(&c->aux, up->genbuf);
1176906943f9SDavid du Colombier 		break;
1177906943f9SDavid du Colombier 	case CMhub:
1178906943f9SDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1179906943f9SDavid du Colombier 		d->ishub = 1;
1180906943f9SDavid du Colombier 		break;
1181906943f9SDavid du Colombier 	case CMspeed:
1182906943f9SDavid du Colombier 		l = name2speed(cb->f[1]);
1183906943f9SDavid du Colombier 		deprint("usb epctl %s %d\n", cb->f[0], l);
1184906943f9SDavid du Colombier 		if(l == Nospeed)
1185906943f9SDavid du Colombier 			error("speed must be full|low|high");
1186906943f9SDavid du Colombier 		qlock(ep->ep0);
1187906943f9SDavid du Colombier 		d->speed = l;
1188906943f9SDavid du Colombier 		qunlock(ep->ep0);
1189906943f9SDavid du Colombier 		break;
1190906943f9SDavid du Colombier 	case CMmaxpkt:
1191906943f9SDavid du Colombier 		l = strtoul(cb->f[1], nil, 0);
1192906943f9SDavid du Colombier 		deprint("usb epctl %s %d\n", cb->f[0], l);
1193906943f9SDavid du Colombier 		if(l < 1 || l > 1024)
1194906943f9SDavid du Colombier 			error("maxpkt not in [1:1024]");
1195906943f9SDavid du Colombier 		qlock(ep);
1196906943f9SDavid du Colombier 		ep->maxpkt = l;
1197906943f9SDavid du Colombier 		qunlock(ep);
1198906943f9SDavid du Colombier 		break;
1199906943f9SDavid du Colombier 	case CMntds:
1200906943f9SDavid du Colombier 		l = strtoul(cb->f[1], nil, 0);
1201906943f9SDavid du Colombier 		deprint("usb epctl %s %d\n", cb->f[0], l);
1202906943f9SDavid du Colombier 		if(l < 1 || l > 3)
1203906943f9SDavid du Colombier 			error("ntds not in [1:3]");
1204906943f9SDavid du Colombier 		qlock(ep);
1205906943f9SDavid du Colombier 		ep->ntds = l;
1206906943f9SDavid du Colombier 		qunlock(ep);
1207906943f9SDavid du Colombier 		break;
1208906943f9SDavid du Colombier 	case CMpollival:
1209906943f9SDavid du Colombier 		if(ep->ttype != Tintr && ep->ttype != Tiso)
1210906943f9SDavid du Colombier 			error("not an intr or iso endpoint");
1211906943f9SDavid du Colombier 		l = strtoul(cb->f[1], nil, 0);
1212906943f9SDavid du Colombier 		deprint("usb epctl %s %d\n", cb->f[0], l);
1213906943f9SDavid du Colombier 		if(ep->ttype == Tiso ||
1214906943f9SDavid du Colombier 		   (ep->ttype == Tintr && ep->dev->speed == Highspeed)){
1215906943f9SDavid du Colombier 			if(l < 1 || l > 16)
1216906943f9SDavid du Colombier 				error("pollival power not in [1:16]");
1217906943f9SDavid du Colombier 			l = pow2(l-1);
1218906943f9SDavid du Colombier 		}else
1219906943f9SDavid du Colombier 			if(l < 1 || l > 255)
1220906943f9SDavid du Colombier 				error("pollival not in [1:255]");
1221906943f9SDavid du Colombier 		qlock(ep);
1222906943f9SDavid du Colombier 		ep->pollival = l;
1223906943f9SDavid du Colombier 		if(ep->ttype == Tiso)
1224906943f9SDavid du Colombier 			setmaxpkt(ep, "pollival");
1225906943f9SDavid du Colombier 		qunlock(ep);
1226906943f9SDavid du Colombier 		break;
1227906943f9SDavid du Colombier 	case CMsamplesz:
1228906943f9SDavid du Colombier 		if(ep->ttype != Tiso)
1229906943f9SDavid du Colombier 			error("not an iso endpoint");
1230906943f9SDavid du Colombier 		l = strtoul(cb->f[1], nil, 0);
1231906943f9SDavid du Colombier 		deprint("usb epctl %s %d\n", cb->f[0], l);
1232906943f9SDavid du Colombier 		if(l <= 0 || l > 8)
1233906943f9SDavid du Colombier 			error("samplesz not in [1:8]");
1234906943f9SDavid du Colombier 		qlock(ep);
1235906943f9SDavid du Colombier 		ep->samplesz = l;
1236906943f9SDavid du Colombier 		setmaxpkt(ep, "samplesz");
1237906943f9SDavid du Colombier 		qunlock(ep);
1238906943f9SDavid du Colombier 		break;
1239906943f9SDavid du Colombier 	case CMhz:
1240906943f9SDavid du Colombier 		if(ep->ttype != Tiso)
1241906943f9SDavid du Colombier 			error("not an iso endpoint");
1242906943f9SDavid du Colombier 		l = strtoul(cb->f[1], nil, 0);
1243906943f9SDavid du Colombier 		deprint("usb epctl %s %d\n", cb->f[0], l);
1244906943f9SDavid du Colombier 		if(l <= 0 || l > 100000)
1245906943f9SDavid du Colombier 			error("hz not in [1:100000]");
1246906943f9SDavid du Colombier 		qlock(ep);
1247906943f9SDavid du Colombier 		ep->hz = l;
1248906943f9SDavid du Colombier 		setmaxpkt(ep, "hz");
1249906943f9SDavid du Colombier 		qunlock(ep);
1250906943f9SDavid du Colombier 		break;
1251906943f9SDavid du Colombier 	case CMclrhalt:
1252906943f9SDavid du Colombier 		qlock(ep);
1253906943f9SDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1254906943f9SDavid du Colombier 		ep->clrhalt = 1;
1255906943f9SDavid du Colombier 		qunlock(ep);
1256906943f9SDavid du Colombier 		break;
1257906943f9SDavid du Colombier 	case CMinfo:
1258906943f9SDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1259906943f9SDavid du Colombier 		l = strlen(Info);
1260906943f9SDavid du Colombier 		s = a;
1261906943f9SDavid du Colombier 		if(n < l+2 || strncmp(Info, s, l) != 0)
1262906943f9SDavid du Colombier 			error(Ebadctl);
1263906943f9SDavid du Colombier 		if(n > 1024)
1264906943f9SDavid du Colombier 			n = 1024;
1265906943f9SDavid du Colombier 		b = smalloc(n);
1266906943f9SDavid du Colombier 		memmove(b, s+l, n-l);
1267906943f9SDavid du Colombier 		b[n-l] = 0;
1268906943f9SDavid du Colombier 		if(b[n-l-1] == '\n')
1269906943f9SDavid du Colombier 			b[n-l-1] = 0;
1270906943f9SDavid du Colombier 		qlock(ep);
1271906943f9SDavid du Colombier 		free(ep->info);
1272906943f9SDavid du Colombier 		ep->info = b;
1273906943f9SDavid du Colombier 		qunlock(ep);
1274906943f9SDavid du Colombier 		break;
1275906943f9SDavid du Colombier 	case CMaddress:
1276906943f9SDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1277906943f9SDavid du Colombier 		ep->dev->state = Denabled;
1278906943f9SDavid du Colombier 		break;
1279906943f9SDavid du Colombier 	case CMdetach:
1280906943f9SDavid du Colombier 		if(ep->dev->isroot != 0)
1281906943f9SDavid du Colombier 			error("can't detach a root hub");
1282906943f9SDavid du Colombier 		deprint("usb epctl %s ep%d.%d\n",
1283906943f9SDavid du Colombier 			cb->f[0], ep->dev->nb, ep->nb);
1284906943f9SDavid du Colombier 		ep->dev->state = Ddetach;
1285906943f9SDavid du Colombier 		/* Release file system ref. for its endpoints */
1286906943f9SDavid du Colombier 		for(i = 0; i < nelem(ep->dev->eps); i++)
1287906943f9SDavid du Colombier 			putep(ep->dev->eps[i]);
1288906943f9SDavid du Colombier 		break;
1289906943f9SDavid du Colombier 	case CMdebugep:
1290906943f9SDavid du Colombier 		if(strcmp(cb->f[1], "on") == 0)
1291906943f9SDavid du Colombier 			ep->debug = 1;
1292906943f9SDavid du Colombier 		else if(strcmp(cb->f[1], "off") == 0)
1293906943f9SDavid du Colombier 			ep->debug = 0;
1294906943f9SDavid du Colombier 		else
1295906943f9SDavid du Colombier 			ep->debug = strtoul(cb->f[1], nil, 0);
1296906943f9SDavid du Colombier 		print("usb: ep%d.%d debug %d\n",
1297906943f9SDavid du Colombier 			ep->dev->nb, ep->nb, ep->debug);
1298906943f9SDavid du Colombier 		break;
1299906943f9SDavid du Colombier 	case CMname:
1300906943f9SDavid du Colombier 		deprint("usb epctl %s %s\n", cb->f[0], cb->f[1]);
1301906943f9SDavid du Colombier 		validname(cb->f[1], 0);
1302906943f9SDavid du Colombier 		kstrdup(&ep->name, cb->f[1]);
1303906943f9SDavid du Colombier 		break;
1304d37e33ffSDavid du Colombier 	case CMtmout:
1305d37e33ffSDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1306d37e33ffSDavid du Colombier 		if(ep->ttype == Tiso || ep->ttype == Tctl)
1307d37e33ffSDavid du Colombier 			error("ctl ignored for this endpoint type");
1308d37e33ffSDavid du Colombier 		ep->tmout = strtoul(cb->f[1], nil, 0);
1309d37e33ffSDavid du Colombier 		if(ep->tmout != 0 && ep->tmout < Xfertmout)
1310d37e33ffSDavid du Colombier 			ep->tmout = Xfertmout;
1311d37e33ffSDavid du Colombier 		break;
1312a23bc242SDavid du Colombier 	case CMpreset:
1313a23bc242SDavid du Colombier 		deprint("usb epctl %s\n", cb->f[0]);
1314a23bc242SDavid du Colombier 		if(ep->ttype != Tctl)
1315a23bc242SDavid du Colombier 			error("not a control endpoint");
1316a23bc242SDavid du Colombier 		if(ep->dev->state != Denabled)
1317a23bc242SDavid du Colombier 			error("forbidden on devices not enabled");
1318a23bc242SDavid du Colombier 		ep->dev->state = Dreset;
1319a23bc242SDavid du Colombier 		break;
1320906943f9SDavid du Colombier 	default:
1321c9b6d007SDavid du Colombier 		panic("usb: unknown epctl %d", ct->index);
1322906943f9SDavid du Colombier 	}
1323906943f9SDavid du Colombier 	free(cb);
1324906943f9SDavid du Colombier 	poperror();
1325906943f9SDavid du Colombier 	return n;
1326906943f9SDavid du Colombier }
1327906943f9SDavid du Colombier 
1328906943f9SDavid du Colombier static long
1329906943f9SDavid du Colombier usbctl(void *a, long n)
1330906943f9SDavid du Colombier {
1331906943f9SDavid du Colombier 	Cmdtab *ct;
1332906943f9SDavid du Colombier 	Cmdbuf *cb;
1333906943f9SDavid du Colombier 	Ep *ep;
1334906943f9SDavid du Colombier 	int i;
1335906943f9SDavid du Colombier 
1336906943f9SDavid du Colombier 	cb = parsecmd(a, n);
1337906943f9SDavid du Colombier 	if(waserror()){
1338906943f9SDavid du Colombier 		free(cb);
1339906943f9SDavid du Colombier 		nexterror();
1340906943f9SDavid du Colombier 	}
1341906943f9SDavid du Colombier 	ct = lookupcmd(cb, usbctls, nelem(usbctls));
1342906943f9SDavid du Colombier 	dprint("usb ctl %s\n", cb->f[0]);
1343906943f9SDavid du Colombier 	switch(ct->index){
1344906943f9SDavid du Colombier 	case CMdebug:
1345906943f9SDavid du Colombier 		if(strcmp(cb->f[1], "on") == 0)
1346906943f9SDavid du Colombier 			debug = 1;
1347906943f9SDavid du Colombier 		else if(strcmp(cb->f[1], "off") == 0)
1348906943f9SDavid du Colombier 			debug = 0;
1349906943f9SDavid du Colombier 		else
1350906943f9SDavid du Colombier 			debug = strtol(cb->f[1], nil, 0);
1351906943f9SDavid du Colombier 		print("usb: debug %d\n", debug);
1352906943f9SDavid du Colombier 		for(i = 0; i < epmax; i++)
1353906943f9SDavid du Colombier 			if((ep = getep(i)) != nil){
1354906943f9SDavid du Colombier 				ep->hp->debug(ep->hp, debug);
1355906943f9SDavid du Colombier 				putep(ep);
1356906943f9SDavid du Colombier 			}
1357906943f9SDavid du Colombier 		break;
1358906943f9SDavid du Colombier 	case CMdump:
1359906943f9SDavid du Colombier 		dumpeps();
1360906943f9SDavid du Colombier 		break;
1361906943f9SDavid du Colombier 	}
1362906943f9SDavid du Colombier 	free(cb);
1363906943f9SDavid du Colombier 	poperror();
1364906943f9SDavid du Colombier 	return n;
1365906943f9SDavid du Colombier }
1366906943f9SDavid du Colombier 
1367906943f9SDavid du Colombier static long
1368906943f9SDavid du Colombier ctlwrite(Chan *c, void *a, long n)
1369906943f9SDavid du Colombier {
1370906943f9SDavid du Colombier 	int q;
1371906943f9SDavid du Colombier 	Ep *ep;
1372906943f9SDavid du Colombier 
1373906943f9SDavid du Colombier 	q = QID(c->qid);
1374906943f9SDavid du Colombier 	if(q == Qctl)
1375906943f9SDavid du Colombier 		return usbctl(a, n);
1376906943f9SDavid du Colombier 
1377906943f9SDavid du Colombier 	ep = getep(qid2epidx(q));
1378906943f9SDavid du Colombier 	if(ep == nil)
1379906943f9SDavid du Colombier 		error(Eio);
1380906943f9SDavid du Colombier 	if(waserror()){
1381906943f9SDavid du Colombier 		putep(ep);
1382906943f9SDavid du Colombier 		nexterror();
1383906943f9SDavid du Colombier 	}
1384906943f9SDavid du Colombier 	if(ep->dev->state == Ddetach)
1385906943f9SDavid du Colombier 		error(Edetach);
1386906943f9SDavid du Colombier 	if(isqtype(q, Qepctl) && c->aux != nil){
1387906943f9SDavid du Colombier 		/* Be sure we don't keep a cloned ep name */
1388906943f9SDavid du Colombier 		free(c->aux);
1389906943f9SDavid du Colombier 		c->aux = nil;
1390906943f9SDavid du Colombier 		error("read, not write, expected");
1391906943f9SDavid du Colombier 	}
1392906943f9SDavid du Colombier 	n = epctl(ep, c, a, n);
1393906943f9SDavid du Colombier 	putep(ep);
1394906943f9SDavid du Colombier 	poperror();
1395906943f9SDavid du Colombier 	return n;
1396906943f9SDavid du Colombier }
1397906943f9SDavid du Colombier 
1398906943f9SDavid du Colombier static long
1399906943f9SDavid du Colombier usbwrite(Chan *c, void *a, long n, vlong off)
1400906943f9SDavid du Colombier {
140184860c5dSDavid du Colombier 	int nr, q;
1402906943f9SDavid du Colombier 	Ep *ep;
14039a747e4fSDavid du Colombier 
14049a747e4fSDavid du Colombier 	if(c->qid.type == QTDIR)
140545d5944cSDavid du Colombier 		error(Eisdir);
14069a747e4fSDavid du Colombier 
1407906943f9SDavid du Colombier 	q = QID(c->qid);
14089a747e4fSDavid du Colombier 
1409906943f9SDavid du Colombier 	if(q == Qctl || isqtype(q, Qepctl))
1410906943f9SDavid du Colombier 		return ctlwrite(c, a, n);
14119a747e4fSDavid du Colombier 
1412906943f9SDavid du Colombier 	ep = getep(qid2epidx(q));
1413906943f9SDavid du Colombier 	if(ep == nil)
14149a747e4fSDavid du Colombier 		error(Eio);
1415906943f9SDavid du Colombier 	if(waserror()){
1416906943f9SDavid du Colombier 		putep(ep);
1417906943f9SDavid du Colombier 		nexterror();
1418906943f9SDavid du Colombier 	}
1419906943f9SDavid du Colombier 	if(ep->dev->state == Ddetach)
1420906943f9SDavid du Colombier 		error(Edetach);
1421906943f9SDavid du Colombier 	if(ep->mode == OREAD || ep->inuse == 0)
1422906943f9SDavid du Colombier 		error(Ebadusefd);
14239a747e4fSDavid du Colombier 
1424906943f9SDavid du Colombier 	switch(ep->ttype){
1425906943f9SDavid du Colombier 	case Tnone:
1426906943f9SDavid du Colombier 		error("endpoint not configured");
1427906943f9SDavid du Colombier 	case Tctl:
1428906943f9SDavid du Colombier 		nr = rhubwrite(ep, a, n);
1429906943f9SDavid du Colombier 		if(nr >= 0){
1430906943f9SDavid du Colombier 			n = nr;
14319a747e4fSDavid du Colombier 			break;
14329a747e4fSDavid du Colombier 		}
1433906943f9SDavid du Colombier 		/* else fall */
1434906943f9SDavid du Colombier 	default:
1435906943f9SDavid du Colombier 		ddeprint("\nusbwrite q %#x fid %d cnt %ld off %lld\n",q, c->fid, n, off);
1436906943f9SDavid du Colombier 		ep->hp->epwrite(ep, a, n);
1437906943f9SDavid du Colombier 	}
1438906943f9SDavid du Colombier 	putep(ep);
1439906943f9SDavid du Colombier 	poperror();
14409a747e4fSDavid du Colombier 	return n;
14419a747e4fSDavid du Colombier }
14429a747e4fSDavid du Colombier 
1443c8cbc0e9SDavid du Colombier void
1444c8cbc0e9SDavid du Colombier usbshutdown(void)
1445c8cbc0e9SDavid du Colombier {
1446c8cbc0e9SDavid du Colombier 	Hci *hp;
1447c8cbc0e9SDavid du Colombier 	int i;
1448c8cbc0e9SDavid du Colombier 
1449c8cbc0e9SDavid du Colombier 	for(i = 0; i < Nhcis; i++){
1450c8cbc0e9SDavid du Colombier 		hp = hcis[i];
1451c8cbc0e9SDavid du Colombier 		if(hp == nil)
1452c8cbc0e9SDavid du Colombier 			continue;
1453c8cbc0e9SDavid du Colombier 		if(hp->shutdown == nil)
1454c8cbc0e9SDavid du Colombier 			print("#u: no shutdown function for %s\n", hp->type);
1455c8cbc0e9SDavid du Colombier 		else
1456c8cbc0e9SDavid du Colombier 			hp->shutdown(hp);
1457c8cbc0e9SDavid du Colombier 	}
1458c8cbc0e9SDavid du Colombier }
1459c8cbc0e9SDavid du Colombier 
14609a747e4fSDavid du Colombier Dev usbdevtab = {
1461906943f9SDavid du Colombier 	L'u',
14629a747e4fSDavid du Colombier 	"usb",
14639a747e4fSDavid du Colombier 
14649a747e4fSDavid du Colombier 	usbreset,
14659a747e4fSDavid du Colombier 	usbinit,
1466c8cbc0e9SDavid du Colombier 	usbshutdown,
14679a747e4fSDavid du Colombier 	usbattach,
14689a747e4fSDavid du Colombier 	usbwalk,
14699a747e4fSDavid du Colombier 	usbstat,
14709a747e4fSDavid du Colombier 	usbopen,
14719a747e4fSDavid du Colombier 	devcreate,
14729a747e4fSDavid du Colombier 	usbclose,
14739a747e4fSDavid du Colombier 	usbread,
14749a747e4fSDavid du Colombier 	devbread,
14759a747e4fSDavid du Colombier 	usbwrite,
14769a747e4fSDavid du Colombier 	devbwrite,
14779a747e4fSDavid du Colombier 	devremove,
14789a747e4fSDavid du Colombier 	devwstat,
14799a747e4fSDavid du Colombier };
1480