914 lines · 19.3 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ #include <u.h>#include <libc.h>#include <thread.h>#include <fcall.h>#include "usb.h"#include "usbfs.h"#include "usbd.h" static Channel *portc;static int win;static int verbose; int mainstacksize = Stack;static Hub *hubs;static int nhubs;static int mustdump;static int pollms = Pollms; static char *dsname[] = { "disabled", "attached", "configed" }; static inthubfeature(Hub *h, int port, int f, int on){	int cmd; 	if(on)		cmd = Rsetfeature;	else		cmd = Rclearfeature;	return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0);} /* * This may be used to detect overcurrent on the hub */static voidcheckhubstatus(Hub *h){	uchar buf[4];	int sts; 	if(h->isroot)	/* not for root hubs */		return;	if(usbcmd(h->dev, Rd2h|Rclass|Rdev, Rgetstatus, 0, 0, buf, 4) < 0){		dprint(2, "%s: get hub status: %r\n", h->dev->dir);		return;	}	sts = GET2(buf);	dprint(2, "hub %s: status %#ux\n", h->dev->dir, sts);} static intconfighub(Hub *h){	int type;	uchar buf[128];	/* room for extra descriptors */	int i;	Usbdev *d;	DHub *dd;	Port *pp;	int nr;	int nmap;	uchar *PortPwrCtrlMask;	int offset;	int mask; 	d = h->dev->usb;	for(i = 0; i < nelem(d->ddesc); i++)		if(d->ddesc[i] == nil)			break;		else if(d->ddesc[i]->data.bDescriptorType == Dhub){			dd = (DHub*)&d->ddesc[i]->data;			nr = Dhublen;			goto Config;		}	type = Rd2h|Rclass|Rdev;	nr = usbcmd(h->dev, type, Rgetdesc, Dhub<<8|0, 0, buf, sizeof buf);	if(nr < Dhublen){		dprint(2, "%s: %s: getdesc hub: %r\n", argv0, h->dev->dir);		return -1;	}	dd = (DHub*)buf;Config:	h->nport = dd->bNbrPorts;	nmap = 1 + h->nport/8;	if(nr < 7 + 2*nmap){		fprint(2, "%s: %s: descr. too small\n", argv0, h->dev->dir);		return -1;	}	h->port = emallocz((h->nport+1)*sizeof(Port), 1);	h->pwrms = dd->bPwrOn2PwrGood*2;	if(h->pwrms < Powerdelay)		h->pwrms = Powerdelay;	h->maxcurrent = dd->bHubContrCurrent;	h->pwrmode = dd->wHubCharacteristics[0] & 3;	h->compound = (dd->wHubCharacteristics[0] & (1<<2))!=0;	h->leds = (dd->wHubCharacteristics[0] & (1<<7)) != 0;	PortPwrCtrlMask = dd->DeviceRemovable + nmap;	for(i = 1; i <= h->nport; i++){		pp = &h->port[i];		offset = i/8;		mask = 1<<(i%8);		pp->removable = (dd->DeviceRemovable[offset] & mask) != 0;		pp->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0;	}	return 0;} static voidconfigroothub(Hub *h){	Dev *d;	char buf[128];	char *p;	int nr; 	d = h->dev;	h->nport = 2;	h->maxpkt = 8;	seek(d->cfd, 0, 0);	nr = read(d->cfd, buf, sizeof(buf)-1);	if(nr < 0)		goto Done;	buf[nr] = 0; 	p = strstr(buf, "ports ");	if(p == nil)		fprint(2, "%s: %s: no port information\n", argv0, d->dir);	else		h->nport = atoi(p+6);	p = strstr(buf, "maxpkt ");	if(p == nil)		fprint(2, "%s: %s: no maxpkt information\n", argv0, d->dir);	else		h->maxpkt = atoi(p+7);Done:	h->port = emallocz((h->nport+1)*sizeof(Port), 1);	dprint(2, "%s: %s: ports %d maxpkt %d\n", argv0, d->dir, h->nport, h->maxpkt);} Hub*newhub(char *fn, Dev *d){	Hub *h;	int i;	Usbdev *ud; 	h = emallocz(sizeof(Hub), 1);	h->isroot = (d == nil);	if(h->isroot){		h->dev = opendev(fn);		if(h->dev == nil){			fprint(2, "%s: opendev: %s: %r", argv0, fn);			goto Fail;		}		if(opendevdata(h->dev, ORDWR) < 0){			fprint(2, "%s: opendevdata: %s: %r\n", argv0, fn);			goto Fail;		}		configroothub(h);	/* never fails */	}else{		h->dev = d;		if(confighub(h) < 0){			fprint(2, "%s: %s: config: %r\n", argv0, fn);			goto Fail;		}	}	if(h->dev == nil){		fprint(2, "%s: opendev: %s: %r\n", argv0, fn);		goto Fail;	}	devctl(h->dev, "hub");	ud = h->dev->usb;	if(h->isroot)		devctl(h->dev, "info roothub csp %#08ux ports %d",			0x000009, h->nport);	else{		devctl(h->dev, "info hub csp %#08ulx ports %d %q %q",			ud->csp, h->nport, ud->vendor, ud->product);		for(i = 1; i <= h->nport; i++)			if(hubfeature(h, i, Fportpower, 1) < 0)				fprint(2, "%s: %s: power: %r\n", argv0, fn);		sleep(h->pwrms);		for(i = 1; i <= h->nport; i++)			if(h->leds != 0)				hubfeature(h, i, Fportindicator, 1);	}	h->next = hubs;	hubs = h;	nhubs++;	dprint(2, "%s: hub %#p allocated:", argv0, h);	dprint(2, " ports %d pwrms %d max curr %d pwrm %d cmp %d leds %d\n",		h->nport, h->pwrms, h->maxcurrent,		h->pwrmode, h->compound, h->leds);	incref(h->dev);	return h;Fail:	if(d != nil)		devctl(d, "detach");	free(h->port);	free(h);	dprint(2, "%s: hub %#p failed to start:", argv0, h);	return nil;} static void portdetach(Hub *h, int p); /* * If during enumeration we get an I/O error the hub is gone or * in pretty bad shape. Because of retries of failed usb commands * (and the sleeps they include) it can take a while to detach all * ports for the hub. This detaches all ports and makes the hub void. * The parent hub will detect a detach (probably right now) and * close it later. */static voidhubfail(Hub *h){	int i; 	for(i = 1; i <= h->nport; i++)		portdetach(h, i);	h->failed = 1;} static voidclosehub(Hub *h){	Hub **hl; 	dprint(2, "%s: closing hub %#p\n", argv0, h);	for(hl = &hubs; *hl != nil; hl = &(*hl)->next)		if(*hl == h)			break;	if(*hl == nil)		sysfatal("closehub: no hub");	*hl = h->next;	nhubs--;	hubfail(h);		/* detach all ports */	free(h->port);	assert(h->dev != nil);	devctl(h->dev, "detach");	closedev(h->dev);	free(h);} static intportstatus(Hub *h, int p){	Dev *d;	uchar buf[4];	int t;	int sts;	int dbg; 	dbg = usbdebug;	if(dbg != 0 && dbg < 4)		usbdebug = 1;	/* do not be too chatty */	d = h->dev;	t = Rd2h|Rclass|Rother;	if(usbcmd(d, t, Rgetstatus, 0, p, buf, sizeof(buf)) < 0)		sts = -1;	else		sts = GET2(buf);	usbdebug = dbg;	return sts;} static char*stsstr(int sts){	static char s[80];	char *e; 	e = s;	if(sts&PSsuspend)		*e++ = 'z';	if(sts&PSreset)		*e++ = 'r';	if(sts&PSslow)		*e++ = 'l';	if(sts&PShigh)		*e++ = 'h';	if(sts&PSchange)		*e++ = 'c';	if(sts&PSenable)		*e++ = 'e';	if(sts&PSstatuschg)		*e++ = 's';	if(sts&PSpresent)		*e++ = 'p';	if(e == s)		*e++ = '-';	*e = 0;	return s;} static intgetmaxpkt(Dev *d, int islow){	uchar buf[64];	/* More room to try to get device-specific descriptors */	DDev *dd; 	dd = (DDev*)buf;	if(islow)		dd->bMaxPacketSize0 = 8;	else		dd->bMaxPacketSize0 = 64;	if(usbcmd(d, Rd2h|Rstd|Rdev, Rgetdesc, Ddev<<8|0, 0, buf, sizeof(buf)) < 0)		return -1;	return dd->bMaxPacketSize0;} /* * BUG: does not consider max. power avail. */static Dev*portattach(Hub *h, int p, int sts){	Dev *d;	Port *pp;	Dev *nd;	char fname[80];	char buf[40];	char *sp;	int mp;	int nr; 	d = h->dev;	pp = &h->port[p];	nd = nil;	pp->state = Pattached;	dprint(2, "%s: %s: port %d attach sts %#ux\n", argv0, d->dir, p, sts);	sleep(Connectdelay);	if(hubfeature(h, p, Fportenable, 1) < 0)		dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);	sleep(Enabledelay);	if(hubfeature(h, p, Fportreset, 1) < 0){		dprint(2, "%s: %s: port %d: reset: %r\n", argv0, d->dir, p);		goto Fail;	}	sleep(Resetdelay);	sts = portstatus(h, p);	if(sts < 0)		goto Fail;	if((sts & PSenable) == 0){		dprint(2, "%s: %s: port %d: not enabled?\n", argv0, d->dir, p);		hubfeature(h, p, Fportenable, 1);		sts = portstatus(h, p);		if((sts & PSenable) == 0)			goto Fail;	}	sp = "full";	if(sts & PSslow)		sp = "low";	if(sts & PShigh)		sp = "high";	dprint(2, "%s: %s: port %d: attached status %#ux\n", argv0, d->dir, p, sts); 	if(devctl(d, "newdev %s %d", sp, p) < 0){		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);		goto Fail;	}	seek(d->cfd, 0, 0);	nr = read(d->cfd, buf, sizeof(buf)-1);	if(nr == 0){		fprint(2, "%s: %s: port %d: newdev: eof\n", argv0, d->dir, p);		goto Fail;	}	if(nr < 0){		fprint(2, "%s: %s: port %d: newdev: %r\n", argv0, d->dir, p);		goto Fail;	}	buf[nr] = 0;	snprint(fname, sizeof(fname), "/dev/usb/%s", buf);	nd = opendev(fname);	if(nd == nil){		fprint(2, "%s: %s: port %d: opendev: %r\n", argv0, d->dir, p);		goto Fail;	}	if(usbdebug > 2)		devctl(nd, "debug 1");	if(opendevdata(nd, ORDWR) < 0){		fprint(2, "%s: %s: opendevdata: %r\n", argv0, nd->dir);		goto Fail;	}	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetaddress, nd->id, 0, nil, 0) < 0){		dprint(2, "%s: %s: port %d: setaddress: %r\n", argv0, d->dir, p);		goto Fail;	}	if(devctl(nd, "address") < 0){		dprint(2, "%s: %s: port %d: set address: %r\n", argv0, d->dir, p);		goto Fail;	} 	mp=getmaxpkt(nd, strcmp(sp, "low") == 0);	if(mp < 0){		dprint(2, "%s: %s: port %d: getmaxpkt: %r\n", argv0, d->dir, p);		goto Fail;	}else{		dprint(2, "%s; %s: port %d: maxpkt %d\n", argv0, d->dir, p, mp);		devctl(nd, "maxpkt %d", mp);	}	if((sts & PSslow) != 0 && strcmp(sp, "full") == 0)		dprint(2, "%s: %s: port %d: %s is full speed when port is low\n",			argv0, d->dir, p, nd->dir);	if(configdev(nd) < 0){		dprint(2, "%s: %s: port %d: configdev: %r\n", argv0, d->dir, p);		goto Fail;	}	/*	 * We always set conf #1. BUG.	 */	if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0){		dprint(2, "%s: %s: port %d: setconf: %r\n", argv0, d->dir, p);		unstall(nd, nd, Eout);		if(usbcmd(nd, Rh2d|Rstd|Rdev, Rsetconf, 1, 0, nil, 0) < 0)			goto Fail;	}	dprint(2, "%s: %U", argv0, nd);	pp->state = Pconfiged;	dprint(2, "%s: %s: port %d: configed: %s\n",			argv0, d->dir, p, nd->dir);	return pp->dev = nd;Fail:	pp->state = Pdisabled;	pp->sts = 0;	if(pp->hub != nil)		pp->hub = nil;	/* hub closed by enumhub */	hubfeature(h, p, Fportenable, 0);	if(nd != nil)		devctl(nd, "detach");	closedev(nd);	return nil;} static voidportdetach(Hub *h, int p){	Dev *d;	Port *pp;	extern void usbfsgone(char*);	d = h->dev;	pp = &h->port[p]; 	/*	 * Clear present, so that we detect an attach on reconnects.	 */	pp->sts &= ~(PSpresent|PSenable); 	if(pp->state == Pdisabled)		return;	pp->state = Pdisabled;	dprint(2, "%s: %s: port %d: detached\n", argv0, d->dir, p); 	if(pp->hub != nil){		closehub(pp->hub);		pp->hub = nil;	}	if(pp->devmaskp != nil)		putdevnb(pp->devmaskp, pp->devnb);	pp->devmaskp = nil;	if(pp->dev != nil){		devctl(pp->dev, "detach");		usbfsgone(pp->dev->dir);		closedev(pp->dev);		pp->dev = nil;	}} /* * The next two functions are included to * perform a port reset asked for by someone (usually a driver). * This must be done while no other device is in using the * configuration address and with care to keep the old address. * To keep drivers decoupled from usbd they write the reset request * to the #u/usb/epN.0/ctl file and then exit. * This is unfortunate because usbd must now poll twice as much. * * An alternative to this reset process would be for the driver to detach * the device. The next function could see that, issue a port reset, and * then restart the driver once to see if it's a temporary error. * * The real fix would be to use interrupt endpoints for non-root hubs * (would probably make some hubs fail) and add an events file to * the kernel to report events to usbd. This is a severe change not * yet implemented. */static intportresetwanted(Hub *h, int p){

Lines 1–500 of 914.