Browse Source

Plan 9 from Bell Labs 2009-05-27

David du Colombier 15 years ago
parent
commit
3a65aae13f
88 changed files with 18212 additions and 7069 deletions
  1. 0 12
      386/bin/usb/print
  2. 18 9
      386/bin/usb/probe
  3. 59 0
      386/bin/usb/usbfat:
  4. 0 13
      386/bin/usb/usbprint
  5. 0 9
      386/bin/usb/usbprobe
  6. 1 1
      rc/bin/termrc
  7. 42 25
      rc/bin/usbfat:
  8. 11 2
      rc/bin/usbstart
  9. 458 0
      sys/man/2/usb
  10. 341 0
      sys/man/2/usbfs
  11. 471 213
      sys/man/3/usb
  12. 204 150
      sys/man/4/usb
  13. 229 39
      sys/man/4/usbd
  14. 2 11
      sys/src/9/boot/boot.c
  15. 580 324
      sys/src/9/pc/devusb.c
  16. 4 2
      sys/src/9/pc/pc
  17. 10 2
      sys/src/9/pc/pcauth
  18. 4 4
      sys/src/9/pc/pccd
  19. 6 2
      sys/src/9/pc/pccpu
  20. 4 0
      sys/src/9/pc/pccpuf
  21. 5 2
      sys/src/9/pc/pcdisk
  22. 5 1
      sys/src/9/pc/pcf
  23. 8 3
      sys/src/9/pc/pcfl
  24. 11 2
      sys/src/9/pc/pcflop
  25. 155 183
      sys/src/9/pc/usb.h
  26. 3456 0
      sys/src/9/pc/usbehci.c
  27. 521 855
      sys/src/9/pc/usbohci.c
  28. 1834 1152
      sys/src/9/pc/usbuhci.c
  29. 2 0
      sys/src/boot/pc/dat.h
  30. 2 1
      sys/src/boot/pc/devbios.c
  31. 149 173
      sys/src/cmd/usb/audio/audio.c
  32. 14 4
      sys/src/cmd/usb/audio/audio.h
  33. 205 231
      sys/src/cmd/usb/audio/audioctl.c
  34. 0 0
      sys/src/cmd/usb/audio/audioctl.h
  35. 106 148
      sys/src/cmd/usb/audio/audiofs.c
  36. 134 107
      sys/src/cmd/usb/audio/audiosub.c
  37. 5 5
      sys/src/cmd/usb/audio/mkfile
  38. 481 788
      sys/src/cmd/usb/disk/disk.c
  39. 70 0
      sys/src/cmd/usb/disk/main.c
  40. 30 10
      sys/src/cmd/usb/disk/mkfile
  41. 32 0
      sys/src/cmd/usb/disk/mkscsierrs
  42. 949 0
      sys/src/cmd/usb/disk/scsireq.c
  43. 223 0
      sys/src/cmd/usb/disk/scsireq.h
  44. 96 0
      sys/src/cmd/usb/disk/ums.h
  45. 484 0
      sys/src/cmd/usb/ether/asix.c
  46. 60 0
      sys/src/cmd/usb/ether/cdc.c
  47. 993 0
      sys/src/cmd/usb/ether/clether.c
  48. 1139 0
      sys/src/cmd/usb/ether/ether.c
  49. 115 0
      sys/src/cmd/usb/ether/ether.h
  50. 89 0
      sys/src/cmd/usb/ether/main.c
  51. 25 0
      sys/src/cmd/usb/ether/mkfile
  52. 30 27
      sys/src/cmd/usb/kb/hid.h
  53. 301 370
      sys/src/cmd/usb/kb/kb.c
  54. 64 0
      sys/src/cmd/usb/kb/main.c
  55. 13 9
      sys/src/cmd/usb/kb/mkfile
  56. 471 0
      sys/src/cmd/usb/lib/dev.c
  57. 0 185
      sys/src/cmd/usb/lib/device.c
  58. 167 0
      sys/src/cmd/usb/lib/devs.c
  59. 136 564
      sys/src/cmd/usb/lib/dump.c
  60. 0 21
      sys/src/cmd/usb/lib/fmt.c
  61. 672 0
      sys/src/cmd/usb/lib/fs.c
  62. 418 0
      sys/src/cmd/usb/lib/fsdir.c
  63. 11 4
      sys/src/cmd/usb/lib/mkfile
  64. 269 0
      sys/src/cmd/usb/lib/parse.c
  65. 0 101
      sys/src/cmd/usb/lib/setup.c
  66. 269 305
      sys/src/cmd/usb/lib/usb.h
  67. 61 0
      sys/src/cmd/usb/lib/usbfs.h
  68. 0 41
      sys/src/cmd/usb/lib/util.c
  69. 0 32
      sys/src/cmd/usb/misc/mkfile
  70. 0 12
      sys/src/cmd/usb/misc/print
  71. 0 12
      sys/src/cmd/usb/misc/probe
  72. 0 273
      sys/src/cmd/usb/misc/usbmouse.c
  73. 15 3
      sys/src/cmd/usb/mkfile
  74. 47 0
      sys/src/cmd/usb/print/main.c
  75. 22 0
      sys/src/cmd/usb/print/mkfile
  76. 86 0
      sys/src/cmd/usb/print/print.c
  77. 21 0
      sys/src/cmd/usb/probe
  78. 0 30
      sys/src/cmd/usb/usbd/dat.h
  79. 252 0
      sys/src/cmd/usb/usbd/dev.c
  80. 0 21
      sys/src/cmd/usb/usbd/fns.h
  81. 0 229
      sys/src/cmd/usb/usbd/hub.c
  82. 113 0
      sys/src/cmd/usb/usbd/mkdev
  83. 15 6
      sys/src/cmd/usb/usbd/mkfile
  84. 0 63
      sys/src/cmd/usb/usbd/setup.c
  85. 725 278
      sys/src/cmd/usb/usbd/usbd.c
  86. 127 0
      sys/src/cmd/usb/usbd/usbd.h
  87. 6 0
      sys/src/cmd/usb/usbd/usbdb
  88. 59 0
      sys/src/cmd/usb/usbfat:

+ 0 - 12
386/bin/usb/print

@@ -1,12 +0,0 @@
-#!/bin/rc
-# usbprint - bind usb printer endpoint to /dev/lp
-rfork e
-for (id in /dev/usb[0-9]*/[0-9]*)
-	if (grep -s 'Enabled 0x020107' $id/status >[2]/dev/null){
-		echo -n 'ep 2 bulk w 64 32' >$id/ctl
-		aux/stub /dev/lp
-		bind $id/ep2data /dev/lp
-		exit ''
-	}
-echo $0: no usb printer found >[1=2]
-exit 'no printer'

+ 18 - 9
386/bin/usb/probe

@@ -1,12 +1,21 @@
 #!/bin/rc
 #!/bin/rc
-# list all usb devices
 rfork e
 rfork e
-if(! test -r '#U'/usb0)
-	exit no-usb
-if(! test -r /dev/usb0)
-	bind -a '#U' /dev
-for (id in /dev/usb[0-9]*/[0-9]*/status)
-	if (test -e $id) {
-		echo $id | sed 's;/status$;:	;' | tr -d '\12'
-		grep '^[A-Z]' $id
+test -e /dev/usb || bind -a '#u' /dev || {
+	echo no '#u/usb' >[1=2]
+	exit nousb
+}
+
+awk 'BEGIN{ep="";}
+	$1 ~ /ep[0-9]+\.0/ && $2 == "enabled" && $NF ~ /busy|idle/ {
+		ep=$1;
+		next;
 	}
 	}
+	{
+		if(ep != ""){
+			printf("%s %s\n", ep, $0);
+			ep="";
+		}
+	}
+' /dev/usb/ctl
+
+exit ''

+ 59 - 0
386/bin/usb/usbfat:

@@ -0,0 +1,59 @@
+#!/bin/rc
+# usbfat: [disk [mtpt]] - mount a USB disk's MS FAT file system
+rfork e
+disk = ()
+mtpt = /n/usb
+
+test -e /dev/usb || bind -a '#u' /dev || {
+	echo no '#u/usb' >[1=2]
+	exit nousb
+}
+test -e /dev/usbdctl || mount -a /srv/usb /dev || {
+	echo cannot mount /srv/usb >[1=2]
+	exit nousbd
+}
+
+disks=()
+mtpt=()
+switch ($#*) {
+case 0
+	;
+case 1
+	disks = $1
+case 2
+	disks = $1
+	mtpt = $2
+case *
+	echo usage: $0 ' [disk [mtpt]]' >[1=2]
+	exit usage
+}
+
+if (~ $#disks 0){
+	if(! test -e /dev/sdU*/data){
+		echo no usb disks >[1=2]
+		exit nodisk
+	}
+	disks = `{echo /dev/sdU*/data}
+}
+for(d in $disks){
+	if(~ $d sdU*.[0-9]*)
+		d=/dev/$d/data
+	if(test -e $d){
+		name=`{echo $d | sed 's/.*(sdU[0-9]+\.[0-9]+).*/\1/'}
+		if(~ $#mtpt 0)
+			mnt=/n/$name
+		if not
+			mnt=$mtpt
+		# don't mount it if it seems to be already mounted.
+		if(! test -e $mnt/*)
+		if(grep -s geometry /dev/$name/ctl){
+			blk = `{disk/fdisk -p $d | awk '/^part dos / {print $3}'}
+			if (! ~ $#blk 0 &&  ~ $blk [0-9]*)
+				d=$d:$blk
+			mount -c <{dossrv -sf $d >[2]/dev/null} $mnt && echo $mnt
+		}
+	}
+	if not
+		echo $d does not exist
+}
+exit ''

+ 0 - 13
386/bin/usb/usbprint

@@ -1,13 +0,0 @@
-#!/bin/rc
-# usbprint - bind usb printer endpoint to /dev/lp
-rfork e
-echo warning: use usb/print instead of usb/usbprint >[1=2]
-for (id in /dev/usb[0-9]*/[0-9]*)
-	if (grep -s 'Enabled 0x020107' $id/status >[2]/dev/null){
-		echo -n 'ep 2 bulk w 64 32' >$id/ctl
-		aux/stub /dev/lp
-		bind $id/ep2data /dev/lp
-		exit ''
-	}
-echo $0: no usb printer found >[1=2]
-exit 'no printer'

+ 0 - 9
386/bin/usb/usbprobe

@@ -1,9 +0,0 @@
-#!/bin/rc
-# list all usb devices
-rfork e
-echo warning: use usb/probe instead of usb/usbprobe >[1=2]
-for (id in /dev/usb[0-9]*/[0-9]*/status)
-	if (test -e $id) {
-		echo $id | sed 's;/status$;:	;' | tr -d '\12'
-		grep '^[A-Z]' $id
-	}

+ 1 - 1
rc/bin/termrc

@@ -6,7 +6,7 @@ NDBFILE=/lib/ndb/local
 mntgen -s slashn && chmod 666 /srv/slashn
 mntgen -s slashn && chmod 666 /srv/slashn
 
 
 # bind all likely devices (#S was bound in boot)
 # bind all likely devices (#S was bound in boot)
-for(i in f t m v L P U '$' Σ κ)
+for(i in f t m v L P u U '$' Σ κ)
 	/bin/bind -a '#'^$i /dev >/dev/null >[2=1]
 	/bin/bind -a '#'^$i /dev >/dev/null >[2=1]
 
 
 # set up any partitions
 # set up any partitions

+ 42 - 25
rc/bin/usbfat:

@@ -1,42 +1,59 @@
 #!/bin/rc
 #!/bin/rc
-# usbfat: [-fl] [disk [mtpt]] - mount a USB disk's MS FAT file system
+# usbfat: [disk [mtpt]] - mount a USB disk's MS FAT file system
 rfork e
 rfork e
-opts=()
-while (! ~ $#* 0 && ~ $1 -*) {
-	switch ($1) {
-	case -f -l -lf -fl
-		opts=($opts $1)
-	case -*
-		echo usage: $0 '[-fl] [disk [mtpt]]' >[1=2]
-		exit usage
-	}
-	shift
-}
-disk = /n/disk/0/data
+disk = ()
 mtpt = /n/usb
 mtpt = /n/usb
 
 
+test -e /dev/usb || bind -a '#u' /dev || {
+	echo no '#u/usb' >[1=2]
+	exit nousb
+}
+test -e /dev/usbdctl || mount -a /srv/usb /dev || {
+	echo cannot mount /srv/usb >[1=2]
+	exit nousbd
+}
+
+disks=()
+mtpt=()
 switch ($#*) {
 switch ($#*) {
 case 0
 case 0
 	;
 	;
 case 1
 case 1
-	disk = $1
+	disks = $1
 case 2
 case 2
-	disk = $1
+	disks = $1
 	mtpt = $2
 	mtpt = $2
 case *
 case *
-	echo usage: $0 '[-fl] [disk [mtpt]]' >[1=2]
+	echo usage: $0 ' [disk [mtpt]]' >[1=2]
 	exit usage
 	exit usage
 }
 }
 
 
-if (! test -f /srv/usbfat.$user) {
-	if (! test -e $disk)
-		usb/disk $opts || exit 'no disk'
-	blk = `{disk/fdisk -p $disk | awk '/^part dos / {print $3}'}
-	if (~ $#blk 0 || ! ~ $blk [0-9]*) {
-		echo $0: warning: no fdisk dos partition found... >[1=2]
-		dossrv -f $disk usbfat.$user || exit dossrv
+if (~ $#disks 0){
+	if(! test -e /dev/sdU*/data){
+		echo no usb disks >[1=2]
+		exit nodisk
+	}
+	disks = `{echo /dev/sdU*/data}
+}
+for(d in $disks){
+	if(~ $d sdU*.[0-9]*)
+		d=/dev/$d/data
+	if(test -e $d){
+		name=`{echo $d | sed 's/.*(sdU[0-9]+\.[0-9]+).*/\1/'}
+		if(~ $#mtpt 0)
+			mnt=/n/$name
+		if not
+			mnt=$mtpt
+		# don't mount it if it seems to be already mounted.
+		if(! test -e $mnt/*)
+		if(grep -s geometry /dev/$name/ctl){
+			blk = `{disk/fdisk -p $d | awk '/^part dos / {print $3}'}
+			if (! ~ $#blk 0 &&  ~ $blk [0-9]*)
+				d=$d:$blk
+			mount -c <{dossrv -sf $d >[2]/dev/null} $mnt && echo $mnt
+		}
 	}
 	}
 	if not
 	if not
-		dossrv -f $disk:$blk usbfat.$user || exit dossrv
+		echo $d does not exist
 }
 }
-mount -c /srv/usbfat.$user $mtpt
+exit ''

+ 11 - 2
rc/bin/usbstart

@@ -1,15 +1,24 @@
 #!/bin/rc
 #!/bin/rc
+# usbstart - start appropriate usb flavour
+if(test -r '#u'/usb) {
+	if(! test -r /dev/usb)
+		bind -a '#u' /dev
 
 
-if(test -r '#U'/usb0) {
+	# /boot/boot may have started usbd, which starts all usb drivers
+	if (! ps | grep -s ' usbd$')
+		usb/usbd
+}
+if not if(test -r '#U'/usb0) {
 	if(! test -r /dev/usb0)
 	if(! test -r /dev/usb0)
 		bind -a '#U' /dev
 		bind -a '#U' /dev
+
 	# /boot/boot may have started usbd, usb/kb or usb/disk
 	# /boot/boot may have started usbd, usb/kb or usb/disk
 	if (! ps | grep -s ' usbd$')
 	if (! ps | grep -s ' usbd$')
 		usb/usbd
 		usb/usbd
 	usb/usbmouse -a 2
 	usb/usbmouse -a 2
 	if (! ps | grep -s ' kb$')
 	if (! ps | grep -s ' kb$')
 		usb/kb -k
 		usb/kb -k
-	usb/usbaudio -s usbaudio.$sysname # -V
+	usb/usbaudio -s usbaudio.$sysname -V
 	# usb/print
 	# usb/print
 }
 }
 exit ''
 exit ''

+ 458 - 0
sys/man/2/usb

@@ -0,0 +1,458 @@
+.TH USB 2
+.SH NAME
+usbcmd,
+classname,
+closedev,
+configdev,
+devctl,
+finddevs,
+loaddevstr,
+matchdevcsp,
+opendev,
+opendevdata,
+openep,
+startdevs,
+unstall,
+class,
+subclass,
+proto,
+CSP \- USB device driver library
+.SH SYNOPSIS
+.EX
+.ta 8n +8n +8n +8n +8n +8n +8n
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../lib/usb.h"
+.sp 0.3v
+struct Dev {
+	Ref;
+	char*	dir;		/* path for the endpoint dir */
+	int	id;		/* usb id for device or ep. number */
+	int	dfd;		/* descriptor for the data file */
+	int	cfd;		/* descriptor for the control file */
+	int	maxpkt;		/* cached from usb description */
+	Usbdev*	usb;		/* USB description */
+	void*	aux;		/* for the device driver */
+	void	(*free)(void*);	/* idem. to release aux */
+};
+.sp 0.3v
+struct Usbdev {
+	ulong	csp;		/* USB class/subclass/proto */
+	int	vid;		/* vendor id */
+	int	did;		/* product (device) id */
+	char*	vendor;
+	char*	product;
+	char*	serial;
+	int	ls;		/* low speed */
+	int	class;		/* from descriptor */
+	int	nconf;		/* from descriptor */
+	Conf*	conf[Nconf];	/* configurations */
+	Ep*	ep[Nep];	/* all endpoints in device */
+	Desc*	ddesc[Nddesc];	/* (raw) device specific descriptors */
+};
+.sp 0.3v
+struct Ep {
+	uchar	addr;		/* endpt address */
+	uchar	dir;		/* direction, Ein/Eout */
+	uchar	type;		/* Econtrol, Eiso, Ebulk, Eintr */
+	uchar	isotype;	/* Eunknown, Easync, Eadapt, Esync */
+	int	id;
+	int	maxpkt;		/* max. packet size */
+	Conf*	conf;		/* the endpoint belongs to */
+	Iface*	iface;		/* the endpoint belongs to */
+};
+.sp 0.3v
+struct Altc {
+	int	attrib;
+	int	interval;
+	void*	aux;		/* for the driver program */
+};
+.sp 0.3v
+struct Iface {
+	int 	id;		/* interface number */
+	ulong	csp;		/* USB class/subclass/proto */
+	Altc*	altc[Naltc];
+	Ep*	ep[Nep];
+	void*	aux;		/* for the driver program */
+};
+.sp 0.3v
+struct Conf {
+	int	cval;		/* value for set configuration */
+	int	attrib;
+	int	milliamps;	/* maximum power in this config. */
+	Iface*	iface[Niface];	/* up to 16 interfaces */
+};
+.sp 0.3v
+struct Desc {
+	Conf*	conf;		/* where this descriptor was read */
+	Iface*	iface;		/* last iface before desc in conf. */
+	Ep*	ep;		/* last endpt before desc in conf. */
+	Altc*	altc;		/* last alt.c. before desc in conf. */
+	DDesc	data;		/* unparsed standard USB descriptor */
+};
+.sp 0.3v
+struct DDesc {
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bbytes[1];
+	/* extra bytes allocated here to keep the rest of it */
+};
+.sp 0.3v
+#define Class(csp)	((csp)&0xff)
+#define Subclass(csp)	(((csp)>>8)&0xff)
+#define Proto(csp)	(((csp)>>16)&0xff)
+#define CSP(c, s, p)	((c) | ((s)<<8) | ((p)<<16))
+#define	GET2(p)		...
+#define	PUT2(p,v)	...
+#define	GET4(p)		...
+#define	PUT4(p,v)	...
+#define dprint	 if(usbdebug)fprint
+#define ddprint if(usbdebug > 1)fprint
+.sp 0.3v
+int	Ufmt(Fmt *f);
+char*	classname(int c);
+void	closedev(Dev *d);
+int	configdev(Dev *d);
+int	devctl(Dev *dev, char *fmt, ...);
+void*	emallocz(ulong size, int zero);
+char*	estrdup(char *s);
+int	finddevs(int (*matchf)(char*,void*), void *farg, char** dirs, int ndirs);
+char*	hexstr(void *a, int n);
+char*	loaddevstr(Dev *d, int sid);
+int	matchdevcsp(char *info, void *a);
+Dev*	opendev(char *fn);
+int	opendevdata(Dev *d, int mode);
+Dev*	openep(Dev *d, int id);
+void	startdevs(char *args, char *argv[], int argc,
+		int (*mf)(char*,void*), void*ma, int (*df)(Dev*,int,char**));
+int	unstall(Dev *dev, Dev *ep, int dir);
+int	usbcmd(Dev *d, int type, int req,
+		int value, int index, uchar *data, int count);
+.sp 0.3v
+extern int usbdebug;	/* more messages for bigger values */
+.EE
+.SH DESCRIPTION
+This library provides convenience structures and functions to write
+USB device drivers.
+It is not intended for user programs using USB devices.
+See
+.IR usb (3)
+for a description of the interfaces provided for that purpose.
+For drivers that provide a file system and may be embedded into
+.IR usbd ,
+the library includes a file system implementation toolkit described in
+.IR usbfs (2).
+.PP
+Usb drivers rely on
+.IR usb (3)
+to perform I/O through USB as well as on
+.IR usbd (4)
+to perform the initial configuration for the device's setup endpoint.
+The rest of the work is up to the driver and is where this library may help.
+.PP
+In most cases, a driver locates the devices of interest and configures them
+by calling
+.I startdevs
+and
+then sets up additional endpoints as needed (by calling
+.IR openep )
+to finally perform I/O by reading and writing the
+data files for the endpoints.
+.PP
+An endpoint as provided by
+.IR usb (3)
+is represented by a
+.B Dev
+data structure.
+The setup endpoint for a
+device represents the USB device, because it is the means to
+configure and operate the device.
+This structure is reference counted.
+Functions creating
+.B Devs
+adjust the number of references to one, initially.
+The driver is free to call
+.IR incref
+(in
+.IR lock (2))
+to add references and
+.I closedev
+to drop references (and release resources when the last one vanishes).
+As an aid to the driver, the field
+.B aux
+may keep driver-specific data and the function
+.B free
+will be called (if not null) to release the
+.B aux
+structure when the reference count goes down to zero.
+.PP
+.I Dev.dir
+holds the path for the endpoint's directory.
+.PP
+The field
+.B id
+keeps the device number for setup endpoints and the endpoint number
+for all other endpoints.
+For example, it would be
+.B 3
+for
+.B /dev/usb/ep3.0
+and
+.B 1
+for
+.BR /dev/usb/ep3.1 .
+It is easy to remember this because the former is created to operate
+on the device, while the later has been created as a particular endpoint
+to perform I/O.
+.PP
+Fields
+.B dfd
+and
+.B cfd
+keep the data and
+control file descriptors, respectively.
+When a
+.B Dev
+is created the control file is open, initially.
+Opening the data
+file requires calling
+.I opendevdata
+with the appropriate mode.
+.PP
+When the device configuration information has been loaded (see below),
+.B maxpkt
+holds the maximum packet size (in bytes) for the endpoint and
+.B usb
+keeps the rest of the USB information.
+.PP
+Most of the information in
+.B usb
+comes from parsing
+various device and configuration descriptors provided by the device,
+by calling one of the functions described later.
+Only descriptors unknown
+to the library are kept unparsed at
+.B usb.ddesc
+as an aid for the driver
+(which should know how to parse them and what to do with the information).
+.SS Configuration
+.I Startdevs
+is a wrapper that locates devices of interest, loads their configuration
+information, and starts a
+.IR thread (2)'s
+.I proc
+for each device located so that it executes
+.I f
+as its main entry point. The entry point is called with a pointer to
+the
+.B Dev
+for the device it has to process,
+.BR argc ,
+and
+.BR argv .
+Devices are located either from the arguments (after options) in
+.IR argv ,
+if any,
+or by calling the helper function
+.I mf
+with the argument
+.I ma
+to determine (for each device available) if the device belongs to
+the driver or not. If the function returns -1 then the device is not for us.
+.PP
+In many cases,
+.I matchdevcsp
+may be supplied as
+.I mf
+along with a (null terminated) vector of CSP values supplied as
+.IR ma .
+This function returns 0 for any device with a CSP matching one in the
+vector supplied as an argument and -1 otherwise.
+In other cases (eg., when a particular vendor and device ids are the
+ones identifying the device) the driver must include its own function
+and supply it as an argument to
+.IR startdevs .
+The first argument of the function corresponds to the information
+known about the device (the second line in its
+.B ctl
+file).
+.I Openep
+creates the endpoint number
+.I id
+for the device
+.I d
+and returns a
+.B Dev
+structure to operate on it (with just the control file open).
+.PP
+.I Opendev
+creates a
+.B Dev
+for the endpoint with directory
+.IR fn .
+Usually, the endpoint is a setup endpoint representing a device. The endpoint
+control file is open, but the data file is not. The USB description is void.
+In most cases drivers call
+.I startdevs
+and
+.I openep
+and do not call this function directly.
+.PP
+.I Configdev
+opens the data file for the device supplied and
+loads and parses its configuration information.
+After calling it, the device is ready for I/O and the USB description in
+.B Dev.usb
+is valid.
+When using
+.IR startdevs
+it is not desirable to call this function (because
+.IR startdevs
+already calls it).
+.PP
+Control requests for an endpoint may be written by calling
+.I devctl
+in the style of
+.IR print (2).
+It is better not to call
+.I print
+directly because the control request should be issued as a single
+.IR write (2).
+See
+.IR usb (3)
+for a list of available control requests (not to be confused with
+USB control transfers performed on a control endpoint).
+.SS Input/Output
+.I Opendevdata
+opens the data file for the device according to the given
+.IR mode .
+The mode must match that of the endpoint, doing otherwise is considered
+an error.
+Actual I/O is performed by reading/writing the descriptor kept in the
+.B dfd
+field of
+.BR Dev .
+.PP
+For control endpoints,
+it is not necessary to call
+.I read
+and
+.I write
+directly.
+Instead,
+.I usbcmd
+issues a USB control request to the device
+.I d
+(not to be confused with a
+.IR usb (3)
+control request sent to its control file).
+.I Usbcmd
+retries the control request several times upon failure because some devices
+require it.
+The format of requests is fixed per the USB standard:
+.I type
+is the type of request and
+.I req
+identifies the request. Arguments
+.I value
+and
+.I index
+are parameters to the request and the last two arguments,
+.I data
+and
+.IR count ,
+are similar to
+.I read
+and
+.I write
+arguments.
+However,
+.I data
+may be
+.B nil
+if no transfer (other than the control request) has to take place.
+The library header file includes numerous symbols defined to help writing
+the type and arguments for a request.
+.PP
+The return value from
+.I usbcmd
+is the number of bytes transferred, zero to indicate a stall and -1
+to indicate an error.
+.PP
+A common request is to unstall an endpoint that has been stalled
+due to some reason by the device (eg., when read or write indicate
+a count of zero bytes read or written on the endpoint). The function
+.I unstall
+does this.
+It is given the device that stalled the endpoint,
+.IR dev ,
+the
+stalled endpoint,
+.IR ep ,
+and the direction of the stall (one of
+.B Ein
+or
+.BR Eout ).
+The function takes care of notifying the device of the unstall as well
+as notifying the kernel.
+.SS Tools
+.I Class
+returns the class part of the number given, representing a CSP.
+.I Subclass
+does the same for the device subclass and
+.I Proto
+for the protocol.
+The counterpart is
+.IR CSP ,
+which builds a CSP from the device class, subclass, and protocol.
+For some classes,
+.I classname
+knows the name (for those with constants in the library header file).
+.PP
+The macros
+.I GET2
+and
+.I PUT2
+get and put a (little-endian) two-byte value and are useful to
+parse descriptors and replies for control requests.
+.PP
+Functions
+.I emallocz
+and
+.I estrdup
+are similar to
+.I mallocz
+and
+.I strdup
+but abort program operation upon failure.
+.PP
+The function
+.I Ufmt
+is a format routine suitable for
+.IR fmtinstall (2)
+to print a
+.B Dev
+data structure.
+The auxiliary
+.I hexstr
+returns a string representing a dump (in hexadecimal) of
+.I n
+bytes starting at
+.IR a .
+The string is allocated using
+.IR malloc (2)
+and memory must be released by the caller.
+.PP
+.I Loaddevstr
+returns the string obtained by reading the device string descriptor number
+.IR sid .
+.SH SOURCE
+.B /sys/src/cmd/usb/lib
+.SH "SEE ALSO"
+.IR usbfs (2),
+.IR usb (3),
+.IR usb (4),
+.IR usbd (4).
+.SH BUGS
+Not heavily exercised yet.

+ 341 - 0
sys/man/2/usbfs

@@ -0,0 +1,341 @@
+.TH USBFS 2
+.SH NAME
+usbreadbuf,
+usbfsadd,
+usbfsdel,
+usbdirread,
+usbfsinit,
+usbdirfs,
+usbfs \- USB device driver file system library
+.SH SYNOPSIS
+.EX
+.ta 8n +8n +8n +8n +8n +8n +8n
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../lib/usb.h"
+#include "../lib/usbfs.h"
+.sp 0.3v
+enum {
+	Hdrsize	= 128,		/* plenty of room for headers */
+	Msgsize	= 8 * 1024,
+	Bufsize	= Hdrsize + Msgsize,
+	Namesz = 40,
+	Errmax = 128,
+	ONONE = ~0,		/* omode in Fid when not open */
+};
+.sp 0.3v
+struct Fid {
+	int	fid;
+	Qid	qid;
+	int	omode;
+	Fid*	next;
+	void*	aux;
+};
+.sp 0.3v
+struct Usbfs {
+	char	name[Namesz];
+	uvlong	qid;
+	Dev*	dev;
+	void*	aux;
+.sp 0.3v
+	int	(*walk)(Usbfs *fs, Fid *f, char *name);
+	void	(*clone)(Usbfs *fs, Fid *of, Fid *nf);
+	void	(*clunk)(Usbfs *fs, Fid *f);
+	int	(*open)(Usbfs *fs, Fid *f, int mode);
+	long	(*read)(Usbfs *fs, Fid *f,
+			void *data, long count, vlong offset);
+	long	(*write)(Usbfs *fs, Fid*f,
+			void *data, long count, vlong offset);
+	int	(*stat)(Usbfs *fs, Qid q, Dir *d);
+	void	(*end)(Usbfs *fs);
+};
+.sp 0.3v
+typedef int (*Dirgen)(Usbfs*, Qid, int, Dir*, void*);
+.sp 0.3v
+long	usbreadbuf(void *data, long count,
+		vlong offset, void *buf, long n);
+void	usbfsadd(Usbfs *dfs);
+void	usbfsdel(Usbfs *dfs);
+int	usbdirread(Usbfs*f, Qid q, char *data, long cnt,
+		vlong off, Dirgen gen, void *arg);
+void	usbfsinit(char* srv, char *mnt, Usbfs *f, int flag);
+void	usbfsdirdump(void);
+.sp 0.3v
+extern char Enotfound[], Etoosmall[], Eio[], Eperm[], Ebadcall[],
+	Ebadfid[], Einuse[], Eisopen[], Ebadctl[];
+.sp 0.3v
+extern Usbfs usbdirfs;
+extern int usbfsdebug;
+.EE
+.SH DESCRIPTION
+This library provides an alternative to
+.IR 9p (2)
+for implementing a file server within a USB driver.
+Drivers using this
+library may be embedded into
+.IR usbd (4).
+It may be also desirable to use this library when drivers are
+not embedded because it is tailored to work well with the
+library for handling USB devices.
+.PP
+A USB file system is described by a
+.I Usbfs
+structure.
+In most cases, the driver is not responsible for the root of the
+file tree.
+It is customary that a driver creates a file server
+for each device handled and links all of them to a root directory
+implemented by the
+.I usbdirfs
+file system implemented by the library.
+This root directory is bound to
+.B /dev
+in most cases.
+.PP
+.I Usbdirfs
+implements a root directory populated by named file trees,
+each one described by a
+.B Usbfs
+structure.
+.PP
+The field
+.B Usbfs.name
+contains the name for the root directory of the file system, usually
+a directory seen at
+.BI /dev/ name
+when the driver is embedded.
+.PP
+.B Usbfs.qid
+maintains a value used to decorate qids for the file tree.
+This may be ignored when
+.I usbdirfs
+is not used.
+Otherwise,
+.I usbdirfs
+assigns a unique value kept at the high 32 bits of
+.B Qid.path
+for all files on each file tree bound to it.
+Each
+.I Usbfs
+server must bitwise OR
+.B Usbfs.qid
+to all
+.B Qid.path
+values returned by its functions.
+In the same way,
+functions usually clear bits in
+.B Usbfs.qid
+before processing
+.B Qid.path
+values supplied as input.
+.PP
+The USB device handled by a file tree is referenced from
+.B Usbfs.dev
+(and a reference must be counted for it).
+This permits the following functions to quickly locate the device of
+interest, and also permits releasing the device when
+no request is outstanding.
+.PP
+The field
+.B Usbfs.aux
+is for the device to use.
+The rest of the fields implement the 9P protocol for the device.
+Not all the operations need be implemented.
+Only
+.IR walk ,
+.IR open ,
+.IR read ,
+.IR write ,
+and
+.IR stat ,
+must be implemented (and their corresponding fields in
+.B Usbfs
+may never be
+.BR nil ).
+These functions must return -1 upon failure
+and set the error string to reflect the cause of a failure.
+.PP
+In all the functions, a 9P fid is represented by a
+.B Fid
+structure.
+It contains the 9P
+.IR fid ,
+the corresponding
+.IR qid ,
+and an auxiliary pointer for the driver to use.
+Open
+.IR fid s
+have a valid open mode in
+.I omode
+while others have
+.B ONONE
+to indicate that the
+.I fid
+is not open.
+The library takes care of which
+fids
+exist and which ones do not.
+.PP
+.I Walk
+must walk
+.I f
+to
+.I name
+(a single name, not a file path)
+in the supplied
+.IR fs .
+Its implementation should update the qid in
+.I f
+to reflect the walk.
+This function must bitwise OR any returned Qid with
+.B Usbfs.qid ,
+if
+.I usbdirfs
+is used.
+.PP
+.I Clone
+must clone fid
+.I of
+onto
+.I nf
+so that,
+upon successful completion,
+.I nf
+also refers to the file that
+.I f
+refers to.
+An implementation must update the Qid of the cloned
+fid.
+If this function is not supplied, the library copies the
+.I aux
+field to the cloned fid.
+.PP
+.I Clunk
+clunks
+.IR f .
+It usually releases data kept in the
+.I aux
+field, but may be
+set to
+.B nil
+otherwise.
+.PP
+.I Open
+prepares the fid
+.I f
+for I/O according to
+.IR mode .
+The open mode in the fid is updated by the library upon return.
+The library checks trivial cases like opening already-open fids.
+The implementation performs most permission checking.
+.PP
+.I Read
+reads up to
+.I count
+bytes into
+.I data
+starting at
+.I offset
+in the file referenced by
+.IR f .
+.I Write
+is the counterpart.
+To read from directories,
+the function
+.I usbdirread
+may be called.
+It returns the return value of
+.I read
+or -1.
+.I usbdirread
+calls
+.I gen
+to iterate through files as needed.
+The
+.B Dirgen
+function will be called with index values of 0
+and up to ask for the first file and following files.
+To read from data already in buffers, the function
+.I usbreadbuf
+may help.
+It must be given the arguments supplied
+by the user, plus the buffer and buffer size.
+.PP
+.I Stat
+must fill
+.I d
+with the directory entry for the file identified by
+.IR q.
+As an aid,
+.I d
+is initialized to fake access and modification times,
+and user and group ids.
+Also, the field
+.B name
+in
+.I d
+is initialized to point to a 40-byte buffer.
+If the file name fits,
+it may be copied directly into
+.B d->name
+without allocating memory for that purpose.
+Otherwise
+.B d->name
+must be initialized to point to static memory.
+.PP
+The function
+.I end
+is called upon termination of the file tree to
+release resources.
+.PP
+Calling
+.I usbfsinit
+starts a file server for
+.I f
+that mounts itself at
+.I mnt
+and posts
+.I srv
+at
+.IR srv (3).
+In most cases, the file system supplied is
+.IR usbdirfs .
+The
+.I flag
+is used for
+.IR mount (2).
+Once
+.I usbdirfs
+is started, calls to
+.IR usbfsadd
+add a file tree implemented by
+.I dfs
+to the root directory of
+.I usbdirfs
+and
+calls to
+.I usbfsdel
+remove that binding (and release resources including
+the reference to the USB device).
+.PP
+Various error strings are declared as an aid.
+The global
+.B usbfsdebug
+may be set to trigger diagnostics and protocol tracing.
+.SH EXAMPLE
+See
+.B /sys/src/cmd/usb/disk
+for an example driver that uses this library.
+Looking at an example is strongly suggested
+to see how reference counts for the USB device
+and the file system are handled.
+.SH SOURCE
+.B /sys/src/cmd/usb/lib
+.SH "SEE ALSO"
+.IR usb (2),
+.IR usb (3),
+.IR usb (4),
+.IR usbd (4).
+.SH BUGS
+Not heavily exercised yet.

+ 471 - 213
sys/man/3/usb

@@ -1,264 +1,522 @@
-.TH USB 3 
+.EQ
+delim $$
+.EN
+.TH USB 3
 .SH NAME
 .SH NAME
 usb \- USB Host Controller Interface
 usb \- USB Host Controller Interface
 .SH SYNOPSIS
 .SH SYNOPSIS
 .nf
 .nf
-.B bind -a #U /dev
+.B bind -a #u /dev
 .PP
 .PP
 .nf
 .nf
-.BI /dev/usb m
-.BI /dev/usb m /new
-.BI /dev/usb m /port
-.BI /dev/usb m / n 
-.BI /dev/usb m / n /ctl
-.BI /dev/usb m / n /status
-.BI /dev/usb m / n /setup
-.BI /dev/usb m / n /ep k\fLdata
+.B /dev/usb
+.B /dev/usb/ctl
+.BI /dev/usb/ep N . M
+.BI /dev/usb/ep N . M /data
+.BI /dev/usb/ep N . M /ctl
 \&...
 \&...
 .fi
 .fi
 .SH DESCRIPTION
 .SH DESCRIPTION
 The Universal Serial Bus is a complex yet popular bus
 The Universal Serial Bus is a complex yet popular bus
-for connecting devices, such as mice, keyboards, printers, scanners,
-and (eventually with USB 2) disks to a PC.  It is
-a four-wire tree-shaped bus that provides both communication and (limited)
-power to devices.  Branching points in the tree are provided by devices
-called
+for connecting all kind of devices to a computer.
+It is a four-wire tree-shaped bus that provides both communication and (limited)
+power to devices.
+Branching points in the tree are provided by devices called
 .IR hubs .
 .IR hubs .
+Hubs provide ports where USB devices (also hubs) can be attached.
 .PP
 .PP
-Most PCs have a two-slot hub built in, accommodating two USB devices.  To
-attach more devices, one or more hubs have to be plugged in to the USB
-slots of the PC.  The topology of the network is a tree with at most
+Most PCs have one or more USB controllers called
+.I host
+controllers.
+Each one has a built-in hub called a
+.I "root hub"
+providing several ports.
+In some cases, more hubs are built-in
+and attached to a root hub port.
+The topology of the network is a tree with at most
 127 nodes, counting both internal and leaf nodes.
 127 nodes, counting both internal and leaf nodes.
 .PP
 .PP
+Host controllers come in four flavours:
+UHCI and OHCI for USB 1 (up to 12 Mb/s),
+EHCI for USB 2 (up to 480 Mb/s)
+and
+XHCI for USB 3 (up to 5 Gb/s).
+We currently support all but XHCI, which is still quite new.
+.PP
 The USB bus is fully controlled by the host; all devices are polled.
 The USB bus is fully controlled by the host; all devices are polled.
 Hubs are passive in the sense that they do not poll the devices attached
 Hubs are passive in the sense that they do not poll the devices attached
-to them.  The host polls those devices and the hubs merely route the
-messages.
+to them.
+The host polls those devices and the hubs merely route the messages.
 .PP
 .PP
 Devices may be added to or removed from the bus at any time.
 Devices may be added to or removed from the bus at any time.
-When a device is attached, the host queries it to determine its type
-and its speed.  The querying process is standardized.  The first level
-of querying is the same for all devices, the next is somewhat specialized
+When a device is attached, the host queries it to determine its type and speed.
+The querying process is standardized.
+The first level of querying is the same for all devices,
+the next is somewhat specialized
 for particular classes of devices (such as mice, keyboards, or audio devices).
 for particular classes of devices (such as mice, keyboards, or audio devices).
 Specialization continues as subclasses and subsubclasses are explored.
 Specialization continues as subclasses and subsubclasses are explored.
-.SS Discovery
-For each connected device there is a directory in
-.BI #U/usb n\fR.
-Reading
-.BI #U/usb n /*/status
-yields the state, class/subclass/proto, vendor-id and product-id of each device in the
-first line.  The remaining lines give the state of each of the
-interfaces.
-.PP
-To find a mouse, for example, scan the status files for the line beginning with
-.IP
-.EX
-.B "Enabled 0x020103"
-.EE
 .PP
 .PP
-A mouse belongs to class 3 (in the least significant byte),
-.IR "human interface device" ,
-subclass 1,
-.IR boot ,
-proto 2,
-.I mouse
-(proto 1 would be the keyboard).
-USB class, subclass and proto codes can be found on
-.BR www.usb.org .
-.SS Device Control
-The control interface for each device is
-.I "``endpoint 0''"
-and is named
-.BI #U/usb n /*/setup \fR.
-The control interface of the device is accessed by reading and writing
-this file.
+Enumeration of the bus and initial configuration of devices is done
+by a user level program,
+.IR usbd (4).
+Device drivers are implemented by separate user programs, although
+some of them may be statically linked into
+.IR usbd .
 .PP
 .PP
-There is a separate
-.I "control interface
-named
-.BI #U/usb n /*/ctl
-which is used to configure the USB device
-.IR driver .
-By writing to this
-file, driver endpoints can be created and configured for communication with a
-device's data streams.  A mouse, for example, has one control interface
-and one data interface.  By communicating with the control interface,
-one can find out the device type (i.e., `mouse'), power consumption, number
-on interfaces, etc.
-.IR Usbd (4)
-will extract all this information and, in verbose mode, print it.
+The kernel device described in this page is responsible for providing
+I/O for using the devices through so called
+.IR endpoints .
+Access to the host controller is hidden from user programs, which see
+just a set of endpoints.
+After system initialization, some endpoints
+are created by the device to permit I/O to root hubs.
+All other devices must be configured by
+.IR usbd .
+.SS Devices and Endpoints
+A device includes one or more functions (e.g., audio output,
+volume control buttons, mouse input, etc.)
+Communication with device functions is performed
+by some combination of
+issuing control requests to, sending data to, and receiving data from
+device
+.IR endpoints .
+Endpoints can be understood as addresses in the bus.
+There are several types:
+.TF "\fIIsochronous
+.TP
+.I Control
+Their main use is to configure devices.
+Writing a message with a specific format
+(specified in the USB specification)
+issues a request to the device.
+If the request implies a reply,
+a read can be made next to retrieve the requested data (if the write succeeded).
+.TP
+.I Interrupt
+Used to send and receive messages to or from a specific device function
+(e.g., to read events from a mouse).
+.TP
+.I Bulk
+Used to send and receive larger amounts of data through streams
+(e.g., to write blocks to a disk).
+.TP
+.I Isochronous
+Used to send and receive data in a timely manner
+(e.g., to write audio samples to a speaker).
+.PD
 .PP
 .PP
-By sending an `endpoint message' to the
-.I ctl
-file, new driver endpoints can be created.  The syntax of these messages
-is
-.IP
-.B ep
-.I n
-.B ctl
-.I "mode maxpkt nbuf
+All USB devices include at least
+a control endpoint to perform device configuration.
+This is called the
+.I setup
+endpoint or
+.IR "endpoint zero" .
+After configuring a device, other endpoints may be created
+as dictated by the device to perform actual I/O.
+.SS Operation
+Bus enumeration and device configuration is performed by
+.IR usbd (8)
+and not by this driver.
+The driver provides an interface
+to access existing endpoints (initially those for the built-in root hubs),
+to create and configure other ones, and to perform I/O through them.
 .PP
 .PP
-or
-.IP
-.B ep
-.I n
-.B bulk
-.I "mode maxpkt nbuf
+Each directory
+.BI /dev/usb/ep N . M
+represents an endpoint, where
+.I N
+is a number identifying a device and
+.I M
+is a number identifying one of its endpoints.
 .PP
 .PP
-or
-.IP
-.B ep
-.I "n period mode maxpkt
+For each device attached to the bus, and configured by
+.IR usbd (8),
+an endpoint zero (a
+.I setup
+endpoint)
+is provided at
+.BI /dev/usb/ep N .0
+for configuring the device.
+This is always a control endpoint and represents the device itself.
 .PP
 .PP
-or
-.IP
-.B ep
-.I "n period mode samplesize hz
+The device driver may use the setup endpoint
+to issue control requests and perhaps to create more endpoints for the device.
+Each new endpoint created has its own directory as said above.
+For example, if the driver for the device
+.BI /dev/usb/ep N . 0
+creates the endpoint number 3 for that device, a directory
+.BI /dev/usb/ep N .3
+will be available to access that endpoint.
 .PP
 .PP
-There are four forms for, respectively, Control, Bulk, Interrupt and
-Isochronous traffic (see USB specs for what that means).
-In all forms,
-.I n
-is the endpoint to be configured, and
-.I mode
-is
-.B r
-for read only,
-.B w
-for write only, or
-.B rw
-for reading and writing.
-.I Maxpkt
-is the maximum packet size to be used (between 8 and 1023),
+All endpoint directories contain two files:
+.B data
 and
 and
-.I nbuf
-is the number of buffers to be allocated by the driver.
+.BR ctl .
+The former has mode bit
+.B DMEXCL
+set and can be open by only one process at a time.
+.SS data
 .PP
 .PP
-.I Period
-is the number of milliseconds between packets (iso) or polls (interrupt).
-This number is usually dictated by the device.  It must be between 1 and 1000.
 The
 The
-.I samplesize
-is the size in bytes of the data units making up packets, and
-.I hz
-is the number of data units transmitted or received per second.
+.B data
+file is used to perform actual I/O.
+In general, reading from it retrieves
+data from the endpoint and writing into it sends data to the endpoint.
+For control endpoints, writing to this file issues a control request
+(which may include data); if the request retrieves data from the device,
+a following read on the file will provide such data.
 .PP
 .PP
-The data rate for an isochronous channel is
-.IR hz × samplesize
-bytes per second, and the number of samples in a packet
-will be 
-.RI ( period × hz )/1000,
-rounded up or down.
-Packets do not contain fractional samples.
-A 16-bit stereo 44.1 KHz audio stream will thus have 44100 4-byte samples
-per second, typically in a 1ms period.  Ove a 10 ms period, this yields 9
-packets of 176 bytes followed by a 180-byte packet (the driver figures it
-out for you).
+USB errors reported by the endpoint upon I/O failures
+are passed to the user process through the error string.
+I/O stalls not resulting from an error, usually
+an indication from the device, are reported by indicating that the
+number of bytes transferred has been zero.
+In most cases, the correct course of action after noticing the stall
+is for the device driver to issue a `clear halt' request (see
+.I unstall
+in
+.IR usb (2))
+to resume I/O.
+The most common error is
+.L crc/timeout
+indicating problems in communication with the device (eg., a physical
+detach of the device or a wiring problem).
 .PP
 .PP
-The mouse, which produces 3-byte samples, is configured with
-.BR "ep 1 ctl r 3 32" :
-endpoint 1 is configured for non-real-time read-only 3-byte messages
-and allows 32 of them to be outstanding.
-.PP
-A usb audio output device at 44.1 KHz, 2 channels, 16-bit samples, on endpoint
-4 will be configured with
-.BR "ep 4 1 w 4 44100" .
-.PP
-If the configuration is successful, a file named
-.BI ep n data
-is created which can be read and/or written depending on
-configuration.  Configuration is not allowed when the data endpoint
-is open.
-.SS Isochronous Streams
-Forward
-.I seek
-operations on isochronous endpoints
-can be used to start the I/O at a specific time.
-The usb status file provides information that can be used to map file
-offsets to points in time:  For each endpoint, the status file produces a line
-of the form:
-.IP
-.B "4 0x000201 \f2nnn\fP bytes \f2nnn\fP blocks
+For control, bulk, and isochronous transfers, there is an implicit
+timeout performed by the kernel and it is not necessary for applications
+to place their own timers.
+For interrupt transfers, the kernel will not time out any operation.
+.SS "ctl and status"
 .PP
 .PP
-The fields are, from left to right,
-endpoint number, class/subclass/proto (as a six-digit hex number with class in the
-least significant byte), number of bytes read/written, number of blocks read/written.
+The
+.B ctl
+file can be read to learn about the endpoint.
+It contains information that can be used
+to locate a particular device (or endpoint).
+It also accepts writes with textual control requests described later.
 .PP
 .PP
-For isochronous devices only, an additional line is produced of the
-form:
+This may result from the read of an endpoint control file:
 .IP
 .IP
-.B "bufsize \f2s\fP buffered \f2b\fP offset \f2o\fP time \f2t\fP
-.PP
-.I S
-is the size of the DMA operations on the device (i.e., the minimum useful size
-for reads and writes),
-.I b
-is the number of bytes currently buffered for input or output, and
-.I o
+.EX
+.I "(the first line is wrapped to make it fit here)"
+.ft L
+enabled control rw speed full maxpkt 64 pollival 0
+	samplesz 0 hz 0 hub 1 port 3 busy
+storage csp 0x500608 vid 0x951 did 0x1613 Kingston 'DT 101 II'
+.ft
+.EE
+.LP
+The first line contains status information.
+The rest is information supplied by
+.IR usbd(8)
+as an aid to locate devices.
+The status information includes:
+.TF "\fREndpoint mode
+.PD
+.TP
+Device state
+One of
+.BR config ,
+.BR enabled ,
 and
 and
-.I t
-should be interpreted to mean that byte offset
-.I o
-was/will be reached at time
-.I t
-(nanoseconds since the epoch).
-.PP
-To play or record samples exactly at some predetermined time, use
-.I o
+.BR detached .
+An endpoint starts in the
+.B config
+state, and accepts control commands written to its
+.B ctl
+file to configure the endpoint.
+When configured, the
+state is
+.B enabled
+and the
+.B data
+file is used as described above (several control requests can still
+be issued to its
+.B ctl
+file, but most will not be accepted from now on).
+Upon severe errors, perhaps a physical
+detachment from the bus, the endpoint enters the
+.B detached
+state and no further I/O is accepted on it.
+Files for an endpoint (including its directory)
+vanish when the device is detached and its files are no longer open.
+Root hubs may not be detached.
+.TP
+Endpoint type
+.BR control ,
+.BR iso ,
+.BR interrupt ,
+or
+.BR bulk ,
+indicating the type of transfer supported by the endpoint.
+.TP
+Endpoint mode
+One of
+.BR r ,
+.BR w ,
+or
+.BR rw ,
+depending on the direction of the endpoint (in, out, or inout).
+.TP
+Speed
+.BR low
+(1.5 Mb/s),
+.BR full
+(12 Mb/s),
+or
+.BR high
+(480 Mb/s).
+.TP
+Maximum packet size
+Used when performing I/O on the data file.
+.TP
+Polling interval
+The polling period expressed as a number of µframes
+(for high-speed endpoints) or frames (for low- and full-speed endpoints).
+Note that a µframe takes 125 µs while a frame takes 1 ms.
+This is only of relevance for interrupt and isochronous endpoints.
+This value determines how often I/O happens.
+Note that the control request adjusting the polling interval does
+.I not
+use these units, to make things easier for USB device drivers.
+.TP
+Sample size
+Number of bytes per I/O sample (isochronous endpoints only).
+.TP
+Frequency
+Number of samples per second (Hertz).
+.TP
+Hub address
+Device address of the hub where the device is attached.
+.TP
+Port number
+Port number (in the hub) where the device is attached.
+.TP
+Usage
+.L busy
+while the data file is open and
+.L idle
+otherwise.
+This is useful to avoid disturbing endpoints already run
+by a device driver.
+.LP
+The second line contains information describing the device:
+.TF "\fRDevice strings
+.PD
+.TP
+Class name
+As provided by the device itself.
+.TP
+CSP
+Class, Subclass, and Protocol for the device.
+If the device contains different functions and has more CSPs,
+all of them will be listed.
+The first one is that of the device itself.
+For example,
+a mouse and keyboard combo may identify itself as a keyboard but
+then include two CSPs, one for the keyboard and another one for the mouse.
+.TP
+Vid and Did
+Vendor and device identifiers.
+.TP
+Device strings
+Provided by the device and identifying the manufacturer and type of device.
+.LP
+For example, to find a mouse not yet in use by a driver, scan the
+.B ctl
+files for
+.BR enabled ,
+.BR idle ,
 and
 and
-.I t
-with the sampling rate to calculate the offset to seek to.
+.BR "csp 0x020103" .
+A mouse belongs to class 3 (in the least significant byte),
+.IR "human interface device" ,
+subclass 1,
+.IR boot ,
+protocol 2,
+.I mouse
+(protocol 1 would be the keyboard).
+USB class, subclass and proto codes can be found at
+.BR http://www.usb.org .
+.SS Control requests
+Endpoint control files accept the following requests.
+In most cases
+the driver does not issue them, leaving the task to either
+.IR usbd (8)
+or the usb driver library documented in
+.IR usb (2).
+.TF "\fLsamplehz\fI n
+.TP
+.B detach
+Prevent further I/O on the device (delete the endpoint)
+and remove its file interface as soon as no process is using their files.
+.TP
+.BI maxpkt " n"
+Set the maximum packet size to
+.I n
+bytes.
+.TP
+.BI pollival " n"
+Only for interrupt and isochronous endpoints.
+Set the polling interval as a function of the value
+.I n
+given by the endpoint descriptor.
+The interval value used is the period
+.I n
+in bus time units for low- and full-speed interrupt endpoints.
+Otherwise, the actual interval is
+$2 sup n$
+and not
+.IR n .
+Bus time units are 1 ms for low- and full-speed endpoints and 125 µs for
+high-speed endpoints.
+In most cases, the device driver may ignore
+all this and issue the control request supplying the
+polling interval value as found
+in the endpoint descriptor.
+The kernel adjusts the value according
+to the endpoint configuration and converts it into the number of
+frames or µframes between two consecutive polls.
+.TP
+.BI samplesz " n"
+Use
+.I n
+as the number of bytes per sample.
+.TP
+.BI hz " n"
+Use
+.I n
+as the number of samples per second.
+.TP
+.BI ntds " n"
+Use
+.I n
+as the number of transactions per frame (or µframe), as reported
+by the descriptor.
+.TP
+.B clrhalt
+Clear the halt condition for an endpoint.
+Used to recover from a stall caused by a device to signal its driver
+(usually due to an unknown request or a failure to complete one).
+.TP
+.BI info " string"
+Replaces description information in
+.B ctl
+with
+.IR string .
+.IR Usbd (8)
+uses this to add device descriptions.
+.TP
+.B address
+Tell this driver that the device has been given an address,
+which causes the device to enter the
+.I enabled
+state.
+.TP
+.BI name " str"
+Generates an additional file name,
+.I str ,
+for the
+.B data
+file of the endpoint.
+This file name appears in the root directory of the
+.L #u
+tree.
+For example, this is used by the audio device
+driver to make the
+.B data
+file also available as
+.BR /dev/audio .
+.TP
+.BI debug " n"
+Enable debugging of the endpoint.
+.I N
+is an integer;
+larger values make diagnostics more verbose.
+.L 0
+stops debugging diagnostics.
+.L 1
+causes just problem reports.
+Bigger values report almost everything.
+.PD
+.LP
+Setup endpoints
+(those represented by
+.BI ep N .0
+directories)
+also accept the following requests:
+.TP
+.BI new " n type mode"
+Creates a new endpoint with number
+.I n
+of the given
+.IR type
+(\c
+.BR ctl ,
+.BR bulk ,
+.BR intr ,
+or
+.BR iso ).
+.I Mode
+may be
+.BR r ,
+.BR w ,
+or
+.BR rw ,
+which creates, respectively, an input, output, or input/output endpoint.
+.TP
+.B "speed {low|full|high}
+Set the endpoint speed to full, low, or high, respectively.
+.TP
+.B hub
+Tell this driver that the endpoint corresponds to a hub device.
+.PD
+.PP
+Setup endpoints for hub devices also accept his request:
+.TP
+.B "newdev {low|full|high} \fIport\fP
+Create a new setup endpoint to represent a new device.
+The first argument is the device speed.
+.I Port
+is the port number where the device is attached
+(the hub is implied by the endpoint where the control request is issued).
+.PD
 .PP
 .PP
-The number of bytes buffered can also be obtained using
-.IR stat (2)
-on the endpoint file.  See also
-.IR audio (3).
+The file
+.B /dev/usb/ctl
+provides all the information provided by the various
+.B ctl
+files when read.
+It accepts several requests that refer to
+the entire driver and not to particular endpoints:
+.TF "\fLdebug \fIn"
+.TP
+.B "debug \fIn\fP
+Sets the global debug flag to
+.IR n .
+.TP
+.B dump
+Dumps data structures for inspection.
 .SH FILES
 .SH FILES
-.TF "#U/usb n /*/status"
-.TP
-.BI #U/usb n /port
-USB port status file; for each port, space separated: port number, hexadecimal port status, port status string
-.TP
-.BI #U/usb n /*/status
-USB device status file; class/subclass/proto, vendor-id and product-id are found in line one
-.TP
-.BI #U/usb n /*/ctl
-USB
-.I driver
-control file, used to create driver endpoints, control debugging, etc.
-.TP
-.BI #U/usb n /*/setup
-USB
-.I device
-control file, used to exchange messages with a device's control channel.
-.B setup
-may be viewed as a preconfigured
-.B ep0data
-file.
-.TP
-.BI #U/usb n /*/ep k data
-USB device data channel for the
-.IR k 'th
-configuration.
+.TF #u/usb
+.TP
+.B #u/usb
+root of the USB interface
 .SH SOURCE
 .SH SOURCE
 .B /sys/src/9/pc/usb.h
 .B /sys/src/9/pc/usb.h
 .br
 .br
 .B /sys/src/9/pc/devusb.c
 .B /sys/src/9/pc/devusb.c
 .br
 .br
-.B /sys/src/9/pc/usb[ou]hci.c
+.B /sys/src/9/pc/usb?hci.c
 .SH "SEE ALSO"
 .SH "SEE ALSO"
+.IR usb (2),
 .IR usb (4),
 .IR usb (4),
 .IR usbd (4),
 .IR usbd (4),
 .IR plan9.ini (8)
 .IR plan9.ini (8)
 .SH BUGS
 .SH BUGS
-EHCI USB 2 controllers are not yet supported.
-.PP
-The interface for configuring endpoints is at variance with the standard.
+Isochronous input streams are not implemented for OHCI.
 .PP
 .PP
-The notion that the endpoints themselves have a class and subclass
-is a distortion of the standard.
-It would be better to leave all handling of the notions of class to the
-user-level support programs, and remove it from the driver.
+Some EHCI controllers drop completion interrupts and so must
+be polled, which hurts throughput.
 .PP
 .PP
-There may be a timing bug that makes disk accesses via UHCI much
-slower than necessary.
+Not heavily exercised yet.

+ 204 - 150
sys/man/4/usb

@@ -1,99 +1,152 @@
 .TH USB 4
 .TH USB 4
 .SH NAME
 .SH NAME
-usbmouse,
+audio,
+disk,
+ether,
 kb,
 kb,
-usbaudio,
-print
-\- Universal Serial Bus user-level device drivers
+print,
+probe,
+usbfat:
+\- Universal Serial Bus device drivers
 .SH SYNOPSIS
 .SH SYNOPSIS
-.B usb/usbmouse
+.B usb/kb
 [
 [
-.B -fsv
+.B -dkm
 ] [
 ] [
 .B -a
 .B -a
 .I accel
 .I accel
 ] [
 ] [
-.I ctrlno
-.I n
+.I dev ...
 ]
 ]
 .PP
 .PP
-.B usb/kb
+.B usb/disk
 [
 [
-.B -dkmn
-] [
-.B -a
-.I n
-] [
-.I ctlrno
-.I n
+.B -Dd
+]
+[
+.B -m
+.I mnt
+]
+[
+.B -s
+.I srv
+]
+[
+.I dev ...
+]
+.PP
+.B usbfat:
+[
+.I disk ...
 ]
 ]
 .PP
 .PP
-.B usb/usbaudio
+.B usb/audio
 [
 [
-.B -pV
+.B -dpV
 ] [
 ] [
 .B -m
 .B -m
-.I mountpoint
+.I mnt
 ] [
 ] [
 .B -s
 .B -s
-.I srvname
+.I srv
 ] [
 ] [
 .B -v
 .B -v
-.I volume
+.I vol
 ] [
 ] [
-.I ctrlno
-.I n
+.I dev
+]
+.PP
+.B usb/ether
+[
+.B -Dd
+]
+[
+.B -m
+.I mnt
+]
+[
+.B -s
+.I srv
+]
+[
+.I dev ...
 ]
 ]
 .PP
 .PP
 .B usb/print
 .B usb/print
+[
+.B -d
+]
+[
+.I dev ...
+]
+.PP
+.B usb/probe
 .SH DESCRIPTION
 .SH DESCRIPTION
-These programs implement support for specific USB device classes.
-They should be run after
+These programs drive USB devices of specific classes via
+.IR usb (3).
+Usually they are started by
 .IR usbd (4)
 .IR usbd (4)
-has had a chance to locate the devices in question and provide
-them with device addresses and minimal configuration.
-Dynamic handling of device insertion and removal is currently not supported.
-.SS Mice
-.I Usbmouse
-sends mouse events from a USB mouse to
-.B /dev/mousein
-where the Plan 9 kernel processes them like other mice, but see
+upon attachment of the device to the bus.
+Less often, users start them manually, depending on
+.IR usbd (4)'s
+configuration.
+Usually,
 .I kb
 .I kb
-below.
+and
+.I disk
+are started by
+.I usbd
+and other programs are started by hand.
 .PP
 .PP
-Without arguments, it scans the USB status files to find a mouse
-and uses the first one it finds.  A pair of numeric arguments overrides this search
-with a specific USB controller and device.  The options are
-.TF "-a ac"
-.TP
-.BI -a " accel"
-Accelerate mouse movements.
-.TP
-.BI -f
-Run usbmouse in foreground.
-.TP
-.BI -s
-Use the scrollwheel.
-.TP
-.BI -v
-Verbose mode.
+Without arguments, the drivers handle all the devices (of
+the appropriate USB class) found on the bus.
+To make a driver handle only certain devices, supply as arguments
+the paths for the directories of the devices
+(actually of their zero endpoints).
+.PP
+Drivers that provide file systems accept options
+.B -s
+and
+.B -m
+to instruct them to post a 9P connection at
+.IR srv (3)
+with the given name and/or to mount themselves at
+.IR mnt .
+When embedded into
+.IR usbd
+these options may not be used.
+In this case,
+the file tree supplied by the device driver is
+available through the file system provided by
+.IR usbd ,
+usually mounted at
+.B /dev
+and reachable through the 9P connection posted at
+.BR /srv/usb .
+.PP
+Options
+.B -d
+and
+.B -D
+present on most drivers trigger debug diagnostics and
+file system debugging diagnostics.
+Repeating any one of these may increase verbosity.
+.PP
+To help locate devices of interest,
+.I probe
+lists all the USB devices available,
+including those with no driver started.
 .SS Keyboards and mice
 .SS Keyboards and mice
 .I Kb
 .I Kb
 supports USB keyboards and mice either as separate USB devices
 supports USB keyboards and mice either as separate USB devices
-or as a single combined USB device. Scan codes from the keyboard
-are sent to
+or as a single combined USB device.
+Scan codes from the keyboard are sent to
 .B /dev/kbin
 .B /dev/kbin
 to let the kernel process them.
 to let the kernel process them.
 Mouse events are sent to
 Mouse events are sent to
 .B /dev/mousein
 .B /dev/mousein
 in the same way.
 in the same way.
 .PP
 .PP
-Without arguments it handles the keyboard and mouse devices found
-on the bus.
-Otherwise it uses the one attached to controller
-.I ctrlno
-with device number
-.IR n .
 The following options are understood:
 The following options are understood:
 .TF -k
 .TF -k
 .TP
 .TP
@@ -102,25 +155,69 @@ Accelerate the mouse to level
 .I n
 .I n
 (similar to the kernel mouse driver acceleration).
 (similar to the kernel mouse driver acceleration).
 .TP
 .TP
-.B \-d
-Activate debug diagnostics. Repeating the flag one or more times
-increases the verbosity.
-.TP
 .B \-k
 .B \-k
 Serve just the keyboard (and not the mouse).
 Serve just the keyboard (and not the mouse).
 .TP
 .TP
 .B \-m
 .B \-m
 Serve just the mouse (and not the keyboard).
 Serve just the mouse (and not the keyboard).
-.TP
-.B \-n
-Dry run. Do not send any events to the kernel for processing.
+.SS Disks
+.I Disk
+configures and manages USB mass storage devices. It
+provides a file system (usually seen at
+.BR /dev )
+that includes one directory per storage device, named
+.BI sdU N . M
+in correspondence with the usb device number and the storage
+unit number (or LUN).
+For example, LUN number 2 on
+.B /dev/usb/ep3.0
+can be accessed through
+.BR /dev/sdU3.2 .
+.PP
+The storage device directory contains the usual files
+served by
+.IR sd (3):
+.BR data ,
+.BR raw ,
+and
+.BR ctl .
+.PP
+The
+.B ctl
+file supplies the device
+geometry when read.
+.PP
+The convenience script
+.B usbfat:
+mounts the FAT file system in the DOS partition of the named
+.IR disk s;
+if none, it mounts those file systems found at
+.BR /dev/sdU*.*/data .
 .SS Printers
 .SS Printers
 .I Print
 .I Print
-is a script that mounts a USB printer on
-.BR /dev/lp .
+provides a single file can be written to print on a USB printer.
+Options are similar to those of
+.IR disk .
+The file is also bound at
+.B /dev/lp
+as is customary.
+.SS Ethernet adapters
+.I Ether
+provides a file interface similar to that of
+.IR ether (3)
+for each USB Ethernet adapter found.
+The name of an Ethernet device is
+.BI etherU N
+where
+.I N
+is the device name.
+When started manually, the file interface is mounted at
+.B /net
+as is customary.
 .SS Audio devices
 .SS Audio devices
 .I Usbaudio
 .I Usbaudio
-configures and manages a USB audio device.  It implements a file system,
+configures and manages a USB audio device.
+It implements a file system,
 normally mounted on
 normally mounted on
 .BI /dev ,
 .BI /dev ,
 but this can be changed with the
 but this can be changed with the
@@ -135,11 +232,12 @@ The names
 .B volume
 .B volume
 and
 and
 .B audio
 .B audio
-maintain backward compatibility with the soundblaster driver.
+maintain backward compatibility with the Soundblaster driver.
 .PP
 .PP
 The
 The
 .B \-V
 .B \-V
-option (verbose) causes usbaudio to print information about the device on startup.
+option (verbose)
+causes usbaudio to print information about the device on startup.
 The
 The
 .B \-s
 .B \-s
 option specifies a name for a file descriptor to be posted in
 option specifies a name for a file descriptor to be posted in
@@ -153,10 +251,11 @@ Reading
 .B volume
 .B volume
 or
 or
 .B audioctl
 .B audioctl
-yields the device's settings.  The data format of
+yields the device's settings.
+The data format of
 .B volume
 .B volume
-is compatible with the soundblaster and
-produces something like
+is compatible with the Soundblaster and produces output in this
+format:
 .IP
 .IP
 .EX
 .EX
 audio out 65
 audio out 65
@@ -165,9 +264,11 @@ bass out 0
 speed out 44100
 speed out 44100
 .EE
 .EE
 .PP
 .PP
-This file can be written using the same syntax.  The keyword
-.I out
-may be omitted.  Settings are given as percentages of the range,
+This file can be written using the same syntax.
+The keyword
+.L out
+may be omitted.
+Settings are given as percentages of the range,
 except for speed which is in Hz.
 except for speed which is in Hz.
 .PP
 .PP
 The file
 The file
@@ -187,13 +288,17 @@ There are 3, 5, or 6 columns present.
 Maxima and resolution are omitted when they are not available or not applicable.
 Maxima and resolution are omitted when they are not available or not applicable.
 The resolution for
 The resolution for
 .I speed
 .I speed
-is reported as 1 (one) if the sampling frequency is continuously variable.  It is absent
-if it is settable at a fixed number of discrete values only.
+is reported as 1 (one) if the sampling frequency is continuously variable.
+It is absent if it is settable at a fixed number of discrete values only.
 .PP
 .PP
 When all values from
 When all values from
 .B audioctl
 .B audioctl
-have been read, a zero-sized buffer is returned (the usual end-of-file indication).
-A new read will then block until one of the settings changes and then report its new value.
+have been read, a zero-length buffer is returned
+(the usual end-of-file indication).
+A new
+.I read
+will then block until one of the settings changes,
+then report its new value.
 .PP
 .PP
 The file
 The file
 .B audioctl
 .B audioctl
@@ -204,78 +309,27 @@ Audio data is written to
 .B audio
 .B audio
 and read from
 and read from
 .BR audioin .
 .BR audioin .
-The data format is little endian, samples ordered primarily by time and
-secondarily by channel.  Samples occupy the minimum integral number
-of bytes.  Read and write operations of arbitrary size are allowed.
-.SH EXAMPLE
-To use a USB mouse and audio device, put the following in your profile
-(replace
-.I x
-with your favorite initial volume setting):
-.IP
-.EX
-.ta 6n
-if (test -r '#U'/usb0) {
-	usb/usbd
-	usb/usbmouse -a 2
-	usb/usbaudio -v \fIx\fP
-	usb/print
-}
-.EE
-.PP
-Alternatively, just put
-.B usbstart
-in your profile.
+The data format is little-endian,
+samples ordered primarily by time and
+secondarily by channel.
+Samples occupy the minimum integral number of bytes.
+Read and write operations of arbitrary size are allowed.
 .SH SOURCE
 .SH SOURCE
 .B /sys/src/cmd/usb
 .B /sys/src/cmd/usb
 .SH "SEE ALSO"
 .SH "SEE ALSO"
+.IR kbin (3),
+.IR mouse (3),
+.IR sd (3),
 .IR usb (3),
 .IR usb (3),
-.IR usbd (4),
-.IR usbdisk (4)
+.IR usbd (4)
 .SH BUGS
 .SH BUGS
-.I Usbaudio
-only works for certain audio devices.
-This is the list of devices known to work with
-.IR usbaudio :
-.IP "" 3
-.RS
-.TF "Edirol U"
-.TP
-Xitel AN1
-Output only.
-Marginally enough to drive headphones.
-Has mute, volume, bass, treble controls.
-.TP
-Philips USB speakers, model DSS 370/17
-.I Usbaudio
-acts on the volume
-.L +
-and
-.L -
-buttons.
-.TP
-Edirol UA-3
-Playback and record.
-Playback only at 44.1 KHz, record at 32, 44.1 or 48 KHz.
-Playback volume control and mute control.
-The device only has analog (slider controlled)
-input volume control.
-.TP
-Edirol UA-1X
-Playback and record.
-Playback only at 32, 44.1 or 48 KHz, record at 8, 16, 22.05, 32, 44.1 or 48 KHz.
-Playback volume control and mute control
-(haven't tested recording, but I believe it'll work).
-.TP
-Xitel Pro HiFi-Link
-Playback only.
-48 KHz only.
-There is a volume control but it isn't connected to the output, so does nothing.
-.TP
-Onkyo WAVIO series MA-500U
-Includes three optical digital interfaces, two analog, and an
-amplifier (15W + 15W).
-.TP
-Turtle Beach Audio Advantage micro
-Headset and S/Pdif out, volume and mute controls.
-.RE
+The various device drivers are generic USB drivers and
+may work only for certain devices on each class.
+The Ethernet device works only for certain ASIX-based cards and for CDC devices.
+ATA storage devices are not supported.
+Both the Ethernet and printer drivers have not
+been tested and it is likely they will fail.
+.PP
+Not heavily exercised yet.
+The entire set of drivers is new and therefore potentially unreliable.
+A list of working devices must be compiled.

+ 229 - 39
sys/man/4/usbd

@@ -4,52 +4,242 @@ usbd \- Universal Serial Bus daemon
 .SH SYNOPSIS
 .SH SYNOPSIS
 .B usbd
 .B usbd
 [
 [
-.B -DfV
-] [
-.B -d
-.I bitmask
-] [
-.B -u
-.I root-hub-num
+.B -Dd
+]
+[
+.B -s
+.I srv
+]
+[
+.B -m
+.I mnt
+]
+[
+.I hub...
 ]
 ]
 .SH DESCRIPTION
 .SH DESCRIPTION
 .I Usbd
 .I Usbd
-manages the USB infrastructure, polls all ports, configures hubs and
-provides access to USB devices through a file system in
-.BR #U .
-It monitors all ports, active or inactive and acts on state changes
-by configuring devices when they are plugged in or turned on and
-unconfiguring them when they are pulled out or switched off.
-.PP
-.B Usbd
+complements
+.IR usb (3)
+to provide USB I/O for device drivers.
+It enumerates the bus, polling
+hub ports to detect device attachments and detachments, performs
+initial configuration of setup endpoints, and writes extra information into
+.IR usb (3)
+endpoint control files, to ease device location.
+.PP
+By default,
+.I usbd
+opens all setup endpoints found at
+.B #u/usb
+(which correspond to built-in hubs initialized by the kernel during boot).
+Paths to directories representing setup endpoints for hubs can be given
+as arguments to restrict
+.I usbd
+operation to such hubs.
+.PP
+When a device is attached,
+depending upon a configuration file compiled into
+.I usbd ,
+the appropriate device driver may be started without
+user intervention.
+This mechanism can be used to statically link some USB device drivers into
+.I usbd
+itself.
+Initial configuration for setup endpoints is performed independently
+of this configuration.
+.PP
+.I Usbd
+provides a file interface used to change debugging flags, and also used by
+USB device drivers statically linked into
+.IR usbd .
+By default, the file system is mounted (after) at
+.B /dev
+and a 9P connection is posted at
+.BR /srv/usb .
+.PP
+Besides files provided by device drivers, the file
+.B usbdctl
+is always present in the file interface.
+It accepts these control requests:
+.TF "fsdebug\fI n
+.TP
+.BI debug " n"
+Sets the debugging level to
+.IR n .
+.TP
+.BI fsdebug " n"
+Sets the file system debugging level to
+.IR n .
+.TP
+.B dump
+Prints the list of devices and file systems known by
+.IR usbd .
+.PD
+.PP
+.I Usbd
 recognizes the following options:
 recognizes the following options:
+.TF "-m\fI mnt
+.TP
+.B -d
+Print debugging diagnostics.
+Repeating the option increases verbosity.
+.TP
+.B -D
+Print debugging diagnostics for the file system interface.
 .TP
 .TP
-.B d
-Set USB library debugging option
-.IR bitmask .
-A value of 1 sets
-.BI Dbginfo ,
-2 sets
-.BI Dbgfs ,
-4 sets
-.BI Dbgproc ,
-and 8 sets
-.BI Dbgcontrol ;
-they may be added to set multiple options.
-.TP
-.B D
-Debug; print the bytes in each message sent or received.
-.TP
-.B f
-Don't fork.
-.TP
-.B u
-Specifies the controller number of the root hub.
-.TP
-.B V
-Verbose; print configuration information and device status as they change.
+.BI -m " mnt"
+Mount the served file system at
+.IR mnt .
+.TP
+.BI -s " srv"
+Post a 9P connection at
+.BI #s/ srv.
+.PD
+.SS Configuration
+.PP
+.I Usbd
+can be configured to start drivers for devices matching one or more CSPs
+(hex representation of USB class, subclass and protocol), class,
+subclass, protocol, vendor id, or device id.
+When a new device is attached,
+.I usbd
+scans the configuration and, if an entry matches the device descriptor, starts
+the driver.
+If no driver is configured, the setup endpoint for the device is left
+configured to let the user start the driver by hand.
+.PP
+Configuration is via compilation
+because one of the options is to embed (link) the driver into the
+.I usbd
+binary.
+If the driver is embedded,
+.I usbd
+creates a process for it and calls its main entry point.
+Otherwise,
+.I usbd
+tries to locate the driver binary in
+.B /bin/usb
+and creates a process to execute it.
+.PP
+The configuration file,
+.BR usbdb ,
+has two sections:
+.B embed
+and
+.BR auto .
+Each section includes lines to configure particular drivers.
+A driver may have more than one line if necessary.
+Each line includes the name of the
+driver (the base name of the binary) and one or more attributes of the form
+.IP
+.IR name = value
+.PP
+The following attributes exist:
+.TF subclass
+.TP
+.B class
+.I Value
+may be the name of the class
+or a number identifying the device class (using C syntax).
+The following class names are known:
+.BR audio ,
+.BR comms ,
+.BR hid ,
+.BR printer ,
+.BR storage ,
+.BR hub ,
+and
+.BR data .
+.TP
+.B subclass
+.I Value
+is the number of the device subclass.
+.TP
+.B proto
+.I Value
+is the number of the device protocol.
+.TP
+.B csp
+.I Value
+is the hexadecimal number describing the CSP for the device.
+.TP
+.B vid
+.I Value
+is the vendor id.
+.TP
+.B did
+.I Value
+is the device id.
+.TP
+.B args
+This must be the last field.
+The value is the rest of the line,
+and is supplied as arguments to the driver process.
+.PD
+.LP
+Several environment variables can be used to alter the behaviour of
+.IR usbd ,
+for example, for use in
+.IR plan9.ini (8).
+.B usbdebug
+sets a debug level (zero for no diagnostics and positive
+values for increasing verbosity).
+.B kbargs
+overrides the keyboard arguments as specified by the configuration file.
+.B diskargs
+overrides the disk arguments in the same way.
+.SH EXAMPLE
+This configuration file links
+.B usb/kb
+into
+.I usbd
+when it is compiled.
+It arranges for the driver's entry point,
+.B kbmain
+in this case,
+to be called for any device with CSPs matching either
+.B 0x010103
+or
+.BR 0x020103 .
+Option
+.B -d
+will be supplied as command line arguments for
+.BR kbmain .
+This configuration also arranges for
+.B /bin/usb/disk
+to start (with no arguments) whenever a device of class
+.B storage
+is attached.
+.IP
+.EX
+embed
+	kb	csp=0x010103 csp=0x020103	args=-d
+auto
+	disk	class=storage	args=
+.EE
+.SH FILES
+.TF /srv/usb
+.TP
+.B /srv/usb
+9P connection to the driver file system.
+.TP
+.B /dev
+mount point for the driver file system.
+.TP
+.B /sys/src/cmd/usb/usbd/usbdb
+Configuration file deciding which devices are included into
+.I usbd
+and which ones are started automatically.
 .SH SOURCE
 .SH SOURCE
 .B /sys/src/cmd/usb/usbd
 .B /sys/src/cmd/usb/usbd
 .SH "SEE ALSO"
 .SH "SEE ALSO"
+.IR usb (2),
 .IR usb (3),
 .IR usb (3),
 .IR usb (4)
 .IR usb (4)
+.SH BUGS
+.I Usbd
+is not supposed to be restarted.
+This is arguable.
+.PP
+Not heavily exercised yet.

+ 2 - 11
sys/src/9/boot/boot.c

@@ -325,18 +325,9 @@ static void
 usbinit(void)
 usbinit(void)
 {
 {
 	static char *darg[] = { "/boot/usbd", nil };
 	static char *darg[] = { "/boot/usbd", nil };
-	static char *kbarg[] = { "/boot/kb", "-a2", nil };
-	static char *dskarg[] = {
-		"/boot/disk", "-l", "-s", "usbdisk", "-m", "/mnt", nil
-	};
 
 
-	if(bind("#U", "/dev", MAFTER) < 0 || access("/dev/usb0", 0) < 0)
-		return;
-	run("usbd", darg);
-	if(access("#m/mouse", 0) < 0)	/* no mouse driver? */
-		kbarg[1] = "-k";
-	run("kb", kbarg);
-	run("disk", dskarg);		/* mounts on /mnt/<lun> */
+	if(bind("#u", "/dev", MAFTER) >= 0 && access("/dev/usb", 0) >= 0)
+		run("usbd", darg);
 }
 }
 
 
 static void
 static void

File diff suppressed because it is too large
+ 580 - 324
sys/src/9/pc/devusb.c


+ 4 - 2
sys/src/9/pc/pc

@@ -53,12 +53,14 @@ link
 	ether82563	pci
 	ether82563	pci
 	ether82557	pci
 	ether82557	pci
 	ether83815	pci
 	ether83815	pci
+	etherdp83820	pci
 	etherec2t	ether8390
 	etherec2t	ether8390
 	etherelnk3	pci
 	etherelnk3	pci
 	etherga620	pci
 	etherga620	pci
 	etherigbe	pci ethermii
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethersink
 	ethersink
 	ethersmc	devi82365 cis
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	etherwavelan	wavelan devi82365 cis pci
@@ -70,6 +72,7 @@ link
 	loopbackmedium
 	loopbackmedium
 	usbuhci
 	usbuhci
 	usbohci
 	usbohci
+	usbehci
 
 
 misc
 misc
 	archmp		mp apic
 	archmp		mp apic
@@ -115,16 +118,15 @@ ip
 	gre
 	gre
 	ipmux
 	ipmux
 	esp
 	esp
-#	il
 
 
 port
 port
 	int cpuserver = 0;
 	int cpuserver = 0;
 
 
 boot
 boot
 	tcp
 	tcp
-#	il
 
 
 bootdir
 bootdir
 	bootpc.out boot
 	bootpc.out boot
 	/386/bin/ip/ipconfig
 	/386/bin/ip/ipconfig
 	/386/bin/auth/factotum
 	/386/bin/auth/factotum
+	/386/bin/usb/usbd

+ 10 - 2
sys/src/9/pc/pcauth

@@ -1,4 +1,4 @@
-# pcauth - pc kernel for our auth servers
+# pcauth - pccpuf specialised for our auth servers
 dev
 dev
 	root
 	root
 	cons
 	cons
@@ -28,14 +28,21 @@ dev
 	floppy		dma
 	floppy		dma
 
 
 	uart
 	uart
+	usb
+	kbin
 
 
 link
 link
 	apm		apmjump
 	apm		apmjump
+	etherdp83820	pci
 	ether82557	pci
 	ether82557	pci
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethermedium
 	ethermedium
 	netdevmedium
 	netdevmedium
 	loopbackmedium
 	loopbackmedium
+	usbuhci
+	usbohci
+	usbehci
 
 
 misc
 misc
 	realmode
 	realmode
@@ -50,6 +57,7 @@ misc
 ip
 ip
 	tcp
 	tcp
 	udp
 	udp
+	rudp
 	ipifc
 	ipifc
 	icmp
 	icmp
 	icmp6
 	icmp6
@@ -70,4 +78,4 @@ bootdir
 	/386/bin/auth/factotum
 	/386/bin/auth/factotum
 	/386/bin/fossil/fossil
 	/386/bin/fossil/fossil
 	/386/bin/venti/venti
 	/386/bin/venti/venti
-#	/386/bin/disk/kfs
+	/386/bin/usb/usbd

+ 4 - 4
sys/src/9/pc/pccd

@@ -1,4 +1,4 @@
-# small kernel used to install from cd
+# pccd - small kernel used to install from cd
 dev
 dev
 	root
 	root
 	cons
 	cons
@@ -60,6 +60,7 @@ link
 	etherigbe	pci ethermii
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethersink
 	ethersink
 	ethersmc	devi82365 cis
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	etherwavelan	wavelan devi82365 cis pci
@@ -70,6 +71,7 @@ link
 	netdevmedium
 	netdevmedium
 	usbuhci
 	usbuhci
 	usbohci
 	usbohci
+	usbehci
 
 
 misc
 misc
 	archmp		mp apic
 	archmp		mp apic
@@ -112,18 +114,16 @@ ip
 	ipifc
 	ipifc
 	icmp
 	icmp
 	icmp6
 	icmp6
-#	il
 
 
 port
 port
 	int cpuserver = 0;
 	int cpuserver = 0;
 
 
 boot boot #S/sdD0/data
 boot boot #S/sdD0/data
 	tcp
 	tcp
-#	il
 	local
 	local
 
 
 bootdir
 bootdir
 	bootpccd.out boot
 	bootpccd.out boot
 	/386/bin/ip/ipconfig ipconfig
 	/386/bin/ip/ipconfig ipconfig
 	/386/bin/9660srv kfs
 	/386/bin/9660srv kfs
-
+	/386/bin/usb/usbd

+ 6 - 2
sys/src/9/pc/pccpu

@@ -1,3 +1,4 @@
+# pccpu - cpu server kernel
 dev
 dev
 	root
 	root
 	cons
 	cons
@@ -28,6 +29,7 @@ dev
 
 
 	uart
 	uart
 	usb
 	usb
+	kbin
 	audio
 	audio
 
 
 link
 link
@@ -42,11 +44,13 @@ link
 	ether82563	pci
 	ether82563	pci
 	ether82557	pci
 	ether82557	pci
 	ether83815	pci
 	ether83815	pci
+	etherdp83820	pci
 	etherelnk3	pci
 	etherelnk3	pci
 	etherga620	pci
 	etherga620	pci
 	etherigbe	pci ethermii
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 #	etherm10g	pci ethermii
 #	etherm10g	pci ethermii
 	ether82598	pci
 	ether82598	pci
 	ethersink
 	ethersink
@@ -54,6 +58,7 @@ link
 	loopbackmedium
 	loopbackmedium
 	usbuhci
 	usbuhci
 	usbohci
 	usbohci
+	usbehci
 
 
 misc
 misc
 	archmp		mp apic
 	archmp		mp apic
@@ -79,16 +84,15 @@ ip
 	ipmux
 	ipmux
 	esp
 	esp
 	rudp
 	rudp
-#	il
 
 
 port
 port
 	int cpuserver = 1;
 	int cpuserver = 1;
 
 
 boot cpu
 boot cpu
 	tcp
 	tcp
-#	il
 
 
 bootdir
 bootdir
 	bootpccpu.out boot
 	bootpccpu.out boot
 	/386/bin/ip/ipconfig ipconfig
 	/386/bin/ip/ipconfig ipconfig
 	/386/bin/auth/factotum
 	/386/bin/auth/factotum
+	/386/bin/usb/usbd

+ 4 - 0
sys/src/9/pc/pccpuf

@@ -32,6 +32,7 @@ dev
 
 
 	uart
 	uart
 	usb
 	usb
+	kbin
 
 
 link
 link
 	realmode
 	realmode
@@ -55,6 +56,7 @@ link
 	etherigbe	pci ethermii
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 #	etherm10g	pci
 #	etherm10g	pci
 	ethersink
 	ethersink
 	ethersmc	devi82365 cis
 	ethersmc	devi82365 cis
@@ -64,6 +66,7 @@ link
 	loopbackmedium
 	loopbackmedium
 	usbuhci
 	usbuhci
 	usbohci
 	usbohci
+	usbehci
 
 
 misc
 misc
 	archmp		mp apic
 	archmp		mp apic
@@ -125,3 +128,4 @@ bootdir
 #	/386/bin/disk/kfs
 #	/386/bin/disk/kfs
 	/386/bin/fossil/fossil
 	/386/bin/fossil/fossil
 	/386/bin/venti/venti
 	/386/bin/venti/venti
+	/386/bin/usb/usbd

+ 5 - 2
sys/src/9/pc/pcdisk

@@ -59,6 +59,7 @@ link
 	etherigbe	pci ethermii
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethersink
 	ethersink
 	ethersmc	devi82365 cis
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	etherwavelan	wavelan devi82365 cis pci
@@ -67,6 +68,8 @@ link
 	netdevmedium
 	netdevmedium
 	loopbackmedium
 	loopbackmedium
 	usbuhci
 	usbuhci
+	usbohci
+	usbehci
 
 
 misc
 misc
 	archmp		mp apic
 	archmp		mp apic
@@ -106,20 +109,19 @@ misc
 ip
 ip
 	tcp
 	tcp
 	udp
 	udp
+	rudp
 	ipifc
 	ipifc
 	icmp
 	icmp
 	icmp6
 	icmp6
 	gre
 	gre
 	ipmux
 	ipmux
 	esp
 	esp
-#	il
 
 
 port
 port
 	int cpuserver = 0;
 	int cpuserver = 0;
 
 
 boot boot #S/sdC0/
 boot boot #S/sdC0/
 	tcp
 	tcp
-#	il
 	local
 	local
 
 
 bootdir
 bootdir
@@ -128,3 +130,4 @@ bootdir
 	/386/bin/auth/factotum
 	/386/bin/auth/factotum
 	/386/bin/disk/kfs
 	/386/bin/disk/kfs
 	/386/bin/cfs
 	/386/bin/cfs
+	/386/bin/usb/usbd

+ 5 - 1
sys/src/9/pc/pcf

@@ -59,6 +59,7 @@ link
 	etherigbe	pci ethermii
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethersink
 	ethersink
 	ethersmc	devi82365 cis
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	etherwavelan	wavelan devi82365 cis pci
@@ -67,6 +68,8 @@ link
 	netdevmedium
 	netdevmedium
 	loopbackmedium
 	loopbackmedium
 	usbuhci
 	usbuhci
+	usbohci
+	usbehci
 
 
 misc
 misc
 	archmp		mp apic
 	archmp		mp apic
@@ -105,6 +108,7 @@ misc
 ip
 ip
 	tcp
 	tcp
 	udp
 	udp
+	rudp
 	ipifc
 	ipifc
 	icmp
 	icmp
 	icmp6
 	icmp6
@@ -123,6 +127,6 @@ bootdir
 	bootpcf.out boot
 	bootpcf.out boot
 	/386/bin/ip/ipconfig
 	/386/bin/ip/ipconfig
 	/386/bin/auth/factotum
 	/386/bin/auth/factotum
-#	/386/bin/disk/kfs
 	/386/bin/fossil/fossil
 	/386/bin/fossil/fossil
 	/386/bin/venti/venti
 	/386/bin/venti/venti
+	/386/bin/usb/usbd

+ 8 - 3
sys/src/9/pc/pcfl

@@ -1,3 +1,4 @@
+# pcfl - no longer builds because /sys/lib/sysconfig/fl is gone
 dev
 dev
 	root
 	root
 	cons
 	cons
@@ -30,7 +31,8 @@ dev
 	pccard
 	pccard
 	i82365		cis
 	i82365		cis
 	uart
 	uart
-	usb
+#	usb
+#	kbin
 
 
 link
 link
 	devpccard
 	devpccard
@@ -56,7 +58,9 @@ link
 	pcmciamodem
 	pcmciamodem
 	netdevmedium
 	netdevmedium
 	loopbackmedium
 	loopbackmedium
-	usbuhci
+#	usbohci
+#	usbuhci
+#	usbehci
 
 
 misc
 misc
 	archmp		mp apic
 	archmp		mp apic
@@ -100,6 +104,7 @@ port
 	int cpuserver = 0;
 	int cpuserver = 0;
 
 
 boot boot #S/sdC0/
 boot boot #S/sdC0/
+	tcp
 	local
 	local
 
 
 bootdir
 bootdir
@@ -114,8 +119,8 @@ bootdir
 	/386/bin/auth/factotum
 	/386/bin/auth/factotum
 	/386/bin/fossil/fossil
 	/386/bin/fossil/fossil
 	/386/bin/ip/ipconfig
 	/386/bin/ip/ipconfig
+	/386/bin/usb/usbd
 	/386/bin/venti/venti
 	/386/bin/venti/venti
 	/sys/lib/sysconfig/fl/boot
 	/sys/lib/sysconfig/fl/boot
 	/sys/lib/sysconfig/fl/flproto
 	/sys/lib/sysconfig/fl/flproto
 	/sys/lib/sysconfig/fl/venti.conf
 	/sys/lib/sysconfig/fl/venti.conf
-

+ 11 - 2
sys/src/9/pc/pcflop

@@ -1,6 +1,8 @@
+# tiny kernel used to install from floppy
 dev
 dev
 	root
 	root
 	cons
 	cons
+
 	arch
 	arch
 	pnp		pci
 	pnp		pci
 	env
 	env
@@ -32,6 +34,7 @@ dev
 	i82365		cis
 	i82365		cis
 	uart
 	uart
 #	usb
 #	usb
+#	kbin
 
 
 link
 link
 	realmode
 	realmode
@@ -53,17 +56,22 @@ link
 	etherec2t	ether8390
 	etherec2t	ether8390
 	etherelnk3	pci
 	etherelnk3	pci
 	etherga620	pci
 	etherga620	pci
-	ethervgbe	pci ethermii
 	etherigbe	pci ethermii
 	etherigbe	pci ethermii
+	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
 	ethervt6102	pci ethermii
+#	ethervt6105m	pci ethermii
 #	ethersink
 #	ethersink
 	ethersmc	devi82365 cis
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	etherwavelan	wavelan devi82365 cis pci
 	ethermedium
 	ethermedium
+#	etherm10g
+#	ether82598	pci
 	pcmciamodem
 	pcmciamodem
 	netdevmedium
 	netdevmedium
 	loopbackmedium
 	loopbackmedium
 #	usbuhci
 #	usbuhci
+#	usbohci
+#	usbehci
 
 
 misc
 misc
 #	archmp		mp apic
 #	archmp		mp apic
@@ -71,6 +79,7 @@ misc
 	sdata		pci sdscsi
 	sdata		pci sdscsi
 	sd53c8xx	pci sdscsi
 	sd53c8xx	pci sdscsi
 	sdmylex		pci sdscsi
 	sdmylex		pci sdscsi
+#	sdiahci		pci sdscsi
 
 
 	uarti8250
 	uarti8250
 #	uartpci		pci
 #	uartpci		pci
@@ -104,7 +113,6 @@ ip
 	ipifc
 	ipifc
 	icmp
 	icmp
 	icmp6
 	icmp6
-#	il
 
 
 port
 port
 	int cpuserver = 0;
 	int cpuserver = 0;
@@ -116,3 +124,4 @@ bootdir
 	bootpcflop.out boot
 	bootpcflop.out boot
 	/sys/lib/dist/bin/386/bzfs kfs
 	/sys/lib/dist/bin/386/bzfs kfs
 	/sys/lib/dist/pc/root.bz2 bzroot
 	/sys/lib/dist/pc/root.bz2 bzroot
+#	/386/bin/usb/usbd

+ 155 - 183
sys/src/9/pc/usb.h

@@ -1,213 +1,185 @@
 /*
 /*
- * common USB definitions
+ * common USB definitions.
  */
  */
-typedef struct Ctlr Ctlr;
-typedef struct Endpt Endpt;
-typedef struct Udev Udev;
-typedef struct Usbhost Usbhost;
+typedef struct Udev Udev;	/* USB device */
+typedef struct Ep Ep;		/* Endpoint */
+typedef struct Hci Hci;		/* Host Controller Interface */
+typedef struct Hciimpl Hciimpl;	/* Link to the controller impl. */
 
 
 enum
 enum
 {
 {
-	MaxUsb = 10,	/* max # of USB Host Controller Interfaces (Usbhost*) */
-	MaxUsbDev = 32,	/* max # of attached USB devs, including root hub (Udev*) */
+	Nhcis	= 16,		/* max nb. of HCIs */
+	Neps	= 64,		/* max nb. of endpoints */
+	Ndeveps	= 16,		/* max nb. of endpoints per device */
+	Maxctllen = 8*1024,	/* max allowed sized for ctl. xfers */
+
+	/* transfer types. keep this order */
+	Tnone = 0,		/* no tranfer type configured */
+	Tctl,			/* wr req + rd/wr data + wr/rd sts */
+	Tiso,			/* stream rd or wr (real time) */
+	Tbulk,			/* stream rd or wr */
+	Tintr,			/* msg rd or wr */
+	Nttypes,		/* number of transfer types */
+
+	Epmax	= 0xF,		/* max ep. addr */
+	Devmax	= 0x7F,		/* max dev. addr */
+
+	/* Speeds */
+	Fullspeed = 0,
+	Lowspeed,
+	Highspeed,
+	Nospeed,
 
 
 	/* request type */
 	/* request type */
-	RH2D = 0<<7,		/* output */
-	RD2H = 1<<7,		/* input */
-	Rstandard = 0<<5,
-	Rclass	= 1<<5,
-	Rvendor = 2<<5,
-
-	Rdevice = 0,
-	Rinterface = 1,
-	Rendpt = 2,
+	Rh2d = 0<<7,
+	Rd2h = 1<<7,
+	Rstd = 0<<5,
+	Rclass =  1<<5,
+	Rep = 2,
 	Rother = 3,
 	Rother = 3,
-};
-
-#define Class(csp)	((csp)&0xff)
-#define Subclass(csp)	(((csp)>>8)&0xff)
-#define Proto(csp)	(((csp)>>16)&0xff)
-#define CSP(c, s, p)	((c) | ((s)<<8) | ((p)<<16))
-
-/* for OHCI */
-typedef struct ED ED;
-struct ED {
-	ulong	ctrl;
-	ulong	tail;		/* transfer descriptor */
-	ulong	head;
-	ulong	next;
-};
 
 
-enum{
-	Dirout,
-	Dirin,
+	/* req offsets */
+	Rtype	= 0,
+	Rreq	= 1,
+	Rvalue	= 2,
+	Rindex	= 4,
+	Rcount	= 6,
+	Rsetuplen = 8,
+
+	/* standard requests */
+	Rgetstatus	= 0,
+	Rclearfeature	= 1,
+	Rsetfeature	= 3,
+
+	/* device states */
+	Dconfig	 = 0,		/* configuration in progress */
+	Denabled,		/* address assigned */
+	Ddetach,		/* device is detached */
+
+	/* (root) Hub reply to port status (reported to usbd) */
+	HPpresent	= 0x1,
+	HPenable	= 0x2,
+	HPsuspend	= 0x4,
+	HPovercurrent	= 0x8,
+	HPreset		= 0x10,
+	HPpower		= 0x100,
+	HPslow		= 0x200,
+	HPhigh		= 0x400,
+	HPstatuschg	= 0x10000,
+	HPchange	= 0x20000,
 };
 };
 
 
 /*
 /*
- * device endpoint
+ * Services provided by the driver.
+ * epopen allocates hardware structures to prepare the endpoint
+ * for I/O. This happens when the user opens the data file.
+ * epclose releases them. This happens when the data file is closed.
+ * epwrite tries to write the given bytes, waiting until all of them
+ * have been written (or failed) before returning; but not for Iso.
+ * epread does the same for reading.
+ * It can be assumed that endpoints are DMEXCL but concurrent
+ * read/writes may be issued and the controller must take care.
+ * For control endpoints, device-to-host requests must be followed by
+ * a read of the expected length if needed.
+ * The port requests are called when usbd issues commands for root
+ * hubs. Port status must return bits as a hub request would do.
+ * Toggle handling and other details are left for the controller driver
+ * to avoid mixing too much the controller and the comon device.
+ * While an endpoint is closed, its toggles are saved in the Ep struct.
  */
  */
-struct Endpt
+struct Hciimpl
 {
 {
-	Ref;
-	Lock;
-	int	x;		/* index in Udev.ep */
-	struct{		/* OHCI */
-		char*	err;	/* needs to be global for unstall; fix? */
-		int	xdone;
-		int	xstarted;
-		int	queued;	/* # of TDs queued on ED */
-		Rendez	rend;
-	}	dir[2];
-	int	epmode;
-	int	epnewmode;
-	int	id;		/* hardware endpoint address */
-	int	maxpkt;		/* maximum packet size (from endpoint descriptor) */
- 	uchar	wdata01;	/* 0=DATA0, 1=DATA1 for output direction */
- 	uchar	rdata01;	/* 0=DATA0, 1=DATA1 for input direction */
- 	int	override;	/* a data command sets this and prevents
- 				 * auto setting of rdata01 or wdata01
- 				 */
-	uchar	eof;
-	ulong	csp;
-	uchar	mode;		/* OREAD, OWRITE, ORDWR */
-	uchar	nbuf;		/* number of buffers allowed */
-	uchar	debug;
-	uchar	active;		/* listed for examination by interrupts */
-	int	setin;
-	/* ISO is all half duplex, so need only one copy of these: */
-	ulong	bw;		/* bandwidth requirement (OHCI) */
-	int	hz;
-	int	remain;		/* for packet size calculations */
-	int	partial;	/* last iso packet may have been half full */
-	Block	*bpartial;
-	int	samplesz;
-	int	sched;		/* schedule index; -1 if undefined or aperiodic */
-	int	pollms;		/* polling interval in msec */
-	int	psize;		/* (remaining) size of this packet */
-	int	off;		/* offset into packet */
-	/* Real-time iso stuff */
-	vlong	foffset;	/* file offset (to detect seeks) */
-	ulong	poffset;	/* offset of next packet to be queued */
-	short	frnum;		/* frame number associated with poffset */
-	int	rem;		/* remainder after rounding Hz to samples/ms */
-	vlong	toffset;	/* offset associated with time */
-	vlong	time;		/* time associated with offset */
-	int	buffered;	/* bytes captured but unread, or written but unsent */
-	/* end ISO stuff */
-
-	Udev*	dev;		/* owning device */
-
-	ulong	nbytes;
-	ulong	nblocks;
-
-	void	*private;
-
-	/*
-	 * all the rest could (should?) move to the driver private structure;
-	 * except perhaps err.
-	 */
-	QLock	rlock;
-	Queue*	rq;
-
-	QLock	wlock;
-	Queue*	wq;
-
-	int	ntd;
-
-	Endpt*	activef;	/* active endpoint list */
+	void	*aux;				/* for controller info */
+	void	(*init)(Hci*);			/* init. controller */
+	void	(*dump)(Hci*);			/* debug */
+	void	(*interrupt)(Ureg*, void*);	/* service interrupt */
+	void	(*epopen)(Ep*);			/* prepare ep. for I/O */
+	void	(*epclose)(Ep*);		/* terminate I/O on ep. */
+	long	(*epread)(Ep*,void*,long);	/* transmit data for ep */
+	long	(*epwrite)(Ep*,void*,long);	/* receive data for ep */
+	char*	(*seprintep)(char*,char*,Ep*);	/* debug */
+	int	(*portenable)(Hci*, int, int);	/* enable/disable port */
+	int	(*portreset)(Hci*, int, int);	/* set/clear port reset */
+	int	(*portstatus)(Hci*, int);	/* get port status */
+	void	(*debug)(Hci*, int);		/* set/clear debug flag */
 };
 };
 
 
-/* OHCI endpoint modes */
-enum {
-	Nomode,
-	Ctlmode,
-	Bulkmode,
-	Intrmode,
-	Isomode,
-	Nmodes,
-};
-
-/* device parameters */
-enum
+struct Hci
 {
 {
-	/* Udev.state */
-	Disabled = 0,
-	Attached,
-	Enabled,
-	Assigned,
-	Configured,
-
-	/* Udev.class */
-	Noclass = 0,
-	Hubclass = 9,
-};
+	ISAConf;				/* hardware info */
+	int	tbdf;				/* type+busno+devno+funcno */
+	int	ctlrno;				/* controller number */
+	int	nports;				/* number of ports in hub */
+	int	highspeed;
+	Hciimpl;					/* HCI driver  */
 
 
-typedef enum {
-	Fullspeed,	/* Don't change order, used in ehci h/w interface */
-	Lowspeed,
-	Highspeed,
-	Nospeed,
-} Speed;	/* Device speed */
+};
 
 
 /*
 /*
- * active USB device
+ * USB endpoint.
+ * All endpoints are kept in a global array. The first
+ * block of fields is constant after endpoint creation.
+ * The rest is configuration information given to all controllers.
+ * The first endpoint for a device (known as ep0) represents the
+ * device and is used to configure it and create other endpoints.
+ * Its QLock also protects per-device data in dev.
+ * See Hciimpl for clues regarding how this is used by controllers.
  */
  */
-struct Udev
+struct Ep
 {
 {
-	Ref;
-	Lock;
-	Usbhost	*uh;
-	int	x;		/* index in usbdev[] */
-	int	busy;
-	int	state;
-	int	id;
-	uchar	port;		/* port number on connecting hub */
-	ulong	csp;
-	ushort	vid;		/* vendor id */
-	ushort	did;		/* product id */
-	Speed	speed;	
-	int	npt;
-	Endpt*	ep[16];		/* active end points */
-
-	Udev*	ports;		/* active ports, if hub */
-	Udev*	next;		/* next device on this hub */
+	Ref;			/* one per fid (and per dev ep for ep0s) */
+
+	/* const once inited. */
+	int	idx;		/* index in global eps array */
+	int	nb;		/* endpoint number in device */
+	Hci*	hp;		/* HCI it belongs to */
+	Udev*	dev;		/* device for the endpoint */
+	Ep*	ep0;		/* control endpoint for its device */
+
+	QLock;			/* protect fields below */
+	char*	name;		/* for ep file names at #u/ */
+	int	inuse;		/* endpoint is open */
+	int	mode;		/* OREAD, OWRITE, or ORDWR */
+	int	clrhalt;	/* true if halt was cleared on ep. */
+	int	debug;		/* per endpoint debug flag */
+	char*	info;		/* for humans to read */
+	long	maxpkt;		/* maximum packet size */
+	int	ttype;		/* tranfer type */
+	ulong	load;		/* in µs, for a fransfer of maxpkt bytes */
+	void*	aux;		/* for controller specific info */
+	int	rhrepl;		/* fake root hub replies */
+	int	toggle[2];	/* saved toggles (while ep is not in use) */
+	long	pollival;		/* poll interval ([µ]frames; intr/iso) */
+	long	hz;		/* poll frequency (iso) */
+	long	samplesz;	/* sample size (iso) */
+	int	ntds;		/* nb. of Tds per µframe */
 };
 };
 
 
 /*
 /*
- * One of these per active Host Controller Interface (HCI)
+ * Per-device configuration and cached list of endpoints.
+ * eps[0]->QLock protects it.
  */
  */
-struct Usbhost
+struct Udev
 {
 {
-	ISAConf;		/* hardware info */
-	int	tbdf;		/* type+busno+devno+funcno */
-
-	QLock;			/* protects namespace state */
-	int	idgen;		/* version # to distinguish new connections */
-	Udev*	dev[MaxUsbDev];	/* device endpoints managed by this HCI */
-
-	void	(*init)(Usbhost*);
-	void	(*interrupt)(Ureg*, void*);
-
-	void	(*debug)(Usbhost*, char*, char*);
-	void	(*portinfo)(Usbhost*, char*, char*);
-	void	(*portreset)(Usbhost*, int);
-	void	(*portenable)(Usbhost*, int, int);
-
-	void	(*epalloc)(Usbhost*, Endpt*);
-	void	(*epfree)(Usbhost*, Endpt*);
-	void	(*epopen)(Usbhost*, Endpt*);
-	void	(*epclose)(Usbhost*, Endpt*);
-	void	(*epmode)(Usbhost*, Endpt*);
-	void	(*epmaxpkt)(Usbhost*, Endpt*);
-
-	long	(*read)(Usbhost*, Endpt*, void*, long, vlong);
-	long	(*write)(Usbhost*, Endpt*, void*, long, vlong, int);
+	int	nb;		/* USB device number */
+	int	state;		/* state for the device */
+	int	ishub;		/* hubs can allocate devices */
+	int	isroot;		/* is a root hub */
+	int	speed;		/* Full/Low/High/No -speed */
+	int	hub;		/* dev number for the parent hub */
+	int	port;		/* port number in the parent hub */
+	Ep*	eps[Ndeveps];	/* end points for this device (cached) */
+};
 
 
-	void	*ctlr;
+void	addhcitype(char *type, int (*reset)(Hci*));
+#define dprint		if(debug)print
+#define ddprint		if(debug>1)print
+#define deprint		if(debug || ep->debug)print
+#define ddeprint	if(debug>1 || ep->debug>1)print
+#define	GET2(p)		((((p)[1]&0xFF)<<8)|((p)[0]&0xFF))
+#define	PUT2(p,v)	{((p)[0] = (v)); ((p)[1] = (v)>>8);}
 
 
-	int	tokin;
-	int	tokout;
-	int	toksetup;
-};
+extern char *usbmodename[];
+extern char Estalled[];
 
 
-extern void addusbtype(char*, int(*)(Usbhost*));
+extern char *seprintdata(char*,char*,uchar*,int);

+ 3456 - 0
sys/src/9/pc/usbehci.c

@@ -0,0 +1,3456 @@
+/*
+ * USB Enhanced Host Controller Interface (EHCI) driver
+ * High speed USB 2.0.
+ *
+ * BUGS:
+ * - Too many delays and ilocks.
+ * - bandwidth admission control must be done per-frame.
+ * - requires polling (some controllers miss interrupts).
+ * - must warn of power overruns.
+ */
+
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"io.h"
+#include	"../port/error.h"
+#include	"usb.h"
+
+typedef struct Ctlio Ctlio;
+typedef struct Ctlr Ctlr;
+typedef struct Ecapio Ecapio;
+typedef struct Eopio Eopio;
+typedef struct Itd Itd;
+typedef struct Sitd Sitd;
+typedef struct Qtd Qtd;
+typedef struct Td Td;
+typedef struct Qh Qh;
+typedef struct Fstn Fstn;
+typedef union Ed Ed;
+typedef struct Edpool Edpool;
+typedef struct Qio Qio;
+typedef struct Qtree Qtree;
+typedef struct Isoio Isoio;
+typedef struct Poll Poll;
+
+/*
+ * EHCI interface registers and bits
+ */
+enum
+{
+	/* Queue states (software) */
+	Qidle		= 0,
+	Qinstall,
+	Qrun,
+	Qdone,
+	Qclose,
+	Qfree,
+
+	Enabledelay	= 100,		/* waiting for a port to enable */
+	Abortdelay	= 5,		/* delay after cancelling Tds (ms) */
+	Ctltmout	= 2000,		/* timeout for a ctl. request (ms) */
+	Bulktmout	= 2000,		/* timeout for a bulk xfer. (ms) */
+	Isotmout	= 2000,		/* timeout for an iso. request (ms) */
+
+	Incr		= 64,		/* for pools of Tds, Qhs, etc. */
+	Align		= 128,		/* in bytes for all those descriptors */
+
+	/* Keep them as a power of 2, lower than ctlr->nframes */
+	/* Also, keep Nisoframes >= Nintrleafs */
+	Nintrleafs	= 32,		/* nb. of leaf frames in intr. tree */
+	Nisoframes	= 64,		/* nb. of iso frames (in window) */
+
+	/*
+	 * HW constants
+	 */
+
+	Cnports		= 0xF,		/* nport bits in Ecapio parms. */
+	C64		= 1,		/* 64-bits, in Ecapio capparms. */
+
+	/* typed links  */
+	Lterm		= 1,
+	Litd		= 0<<1,
+	Lqh		= 1<<1,
+	Lsitd		= 2<<1,
+	Lfstn		= 3<<1,		/* we don't use these */
+
+	/* Cmd reg. */
+	Cstop		= 0x00000,	/* stop running */
+	Crun		= 0x00001,	/* start operation */
+	Chcreset	= 0x00002,	/* host controller reset */
+	Cflsmask	= 0x0000C,	/* frame list size bits */
+	Cfls1024	= 0x00000,	/* frame list size 1024 */
+	Cfls512		= 0x00004,	/* frame list size 512 frames */
+	Cfls256		= 0x00008,	/* frame list size 256 frames */
+	Cpse		= 0x00010,	/* periodic sched. enable */
+	Case		= 0x00020,	/* async sched. enable */
+	Ciasync		= 0x00040,	/* interrupt on async advance doorbell */
+	Citc1		= 0x10000,	/* interrupt threshold ctl. 1 µframe */
+	Citc4		= 0x40000,	/* same. 2 µframes */
+	/* ... */
+	Citc8		= 0x80000,	/* same. 8 µframes (can go up to 64) */
+
+	/* Sts reg. */
+	Sasyncss	= 0x08000,	/* aync schedule status */
+	Speriodss	= 0x04000,	/* periodic schedule status */
+	Srecl		= 0x02000,	/* reclamnation (empty async sched.) */
+	Shalted		= 0x01000,	/* h.c. is halted */
+	Sasync		= 0x00020,	/* interrupt on async advance */
+	Sherr		= 0x00010,	/* host system error */
+	Sfrroll		= 0x00008,	/* frame list roll over */
+	Sportchg	= 0x00004,	/* port change detect */
+	Serrintr	= 0x00002,		/* error interrupt */
+	Sintr		= 0x00001,	/* interrupt */
+	Sintrs		= 0x0003F,	/* interrupts status */
+
+	/* Intr reg. */
+	Iusb		= 0x01,		/* intr. on usb */
+	Ierr		= 0x02,		/* intr. on usb error */
+	Iportchg	= 0x04,		/* intr. on port change */
+	Ifrroll		= 0x08,		/* intr. on frlist roll over */
+	Ihcerr		= 0x10,		/* intr. on host error */
+	Iasync		= 0x20,		/* intr. on async advance enable */
+	Iall		= 0x3F,		/* all interrupts */
+
+	/* Config reg. */
+	Callmine		= 1,		/* route all ports to us */
+
+	/* Portsc reg. */
+	Pspresent	= 0x00000001,	/* device present */
+	Psstatuschg	= 0x00000002,	/* Pspresent changed */
+	Psenable	= 0x00000004,	/* device enabled */
+	Pschange	= 0x00000008,	/* Psenable changed */
+	Psresume	= 0x00000040,	/* resume detected */
+	Pssuspend	= 0x00000080,	/* port suspended */
+	Psreset		= 0x00000100,	/* port reset */
+	Pspower		= 0x00001000,	/* port power on */
+	Psowner		= 0x00002000,	/* port owned by companion */
+	Pslinemask	= 0x00000C00,	/* line status bits */
+	Pslow		= 0x00000400,	/* low speed device */
+
+	/* Itd bits (csw[]) */
+	Itdactive	= 0x80000000,	/* execution enabled */
+	Itddberr	= 0x40000000,	/* data buffer error */
+	Itdbabble	= 0x20000000,	/* babble error */
+	Itdtrerr	= 0x10000000,	/* transaction error */
+	Itdlenshift	= 16,		/* transaction length */
+	Itdlenmask	= 0xFFF,
+	Itdioc		= 0x00008000,	/* interrupt on complete */
+	Itdpgshift	= 12,		/* page select field */
+	Itdoffshift	= 0,		/* transaction offset */
+	/* Itd bits, buffer[] */
+	Itdepshift	= 8,		/* endpoint address (buffer[0]) */
+	Itddevshift	= 0,		/* device address (buffer[0]) */
+	Itdin		= 0x800,	/* is input (buffer[1]) */
+	Itdout		= 0,
+	Itdmaxpktshift	= 0,		/* max packet (buffer[1]) */
+	Itdntdsshift	= 0,		/* nb. of tds per µframe (buffer[2]) */
+
+	Itderrors	= Itddberr|Itdbabble|Itdtrerr,
+
+	/* Sitd bits (epc) */
+	Stdin		= 0x80000000,	/* input direction */
+	Stdportshift	= 24,		/* hub port number */
+	Stdhubshift	= 16,		/* hub address */
+	Stdepshift	= 8,		/* endpoint address */
+	Stddevshift	= 0,		/* device address */
+	/* Sitd bits (mfs) */
+	Stdssmshift	= 0,		/* split start mask */
+	Stdscmshift	= 8,		/* split complete mask */
+	/* Sitd bits (csw) */
+	Stdioc		= 0x80000000,	/* interrupt on complete */
+	Stdpg		= 0x40000000,	/* page select */
+	Stdlenshift	= 16,		/* total bytes to transfer */
+	Stdlenmask	= 0x3FF,
+	Stdactive	= 0x00000080,	/* active */
+	Stderr		= 0x00000040,	/* tr. translator error */
+	Stddberr	= 0x00000020,	/* data buffer error */
+	Stdbabble	= 0x00000010,	/* babble error */
+	Stdtrerr	= 0x00000008,	/* transanction error */
+	Stdmmf		= 0x00000004,	/* missed µframe */
+	Stddcs		= 0x00000002,	/* do complete split */
+
+	Stderrors	= Stderr|Stddberr|Stdbabble|Stdtrerr|Stdmmf,
+
+	/* Sitd bits buffer[1] */
+	Stdtpall	= 0x00000000,	/* all payload here (188 bytes) */
+	Stdtpbegin	= 0x00000008,	/* first payload for fs trans. */
+	Stdtcntmask	= 0x00000007,	/* T-count */
+
+	/* Td bits (csw) */
+	Tddata1		= 0x80000000,	/* data toggle 1 */
+	Tddata0		= 0x00000000,	/* data toggle 0 */
+	Tdlenshift	= 16,		/* total bytes to transfer */
+	Tdlenmask	= 0x7FFF,
+	Tdmaxpkt	= 0x5000,	/* max buffer for a Td */
+	Tdioc		= 0x00008000,	/* interrupt on complete */
+	Tdpgshift	= 12,		/* current page */
+	Tdpgmask	= 7,
+	Tderr1		= 0x00000400,	/* bit 0 of error counter */
+	Tderr2		= 0x00000800,	/* bit 1 of error counter */
+	Tdtokout	= 0x00000000,	/* direction out */
+	Tdtokin		= 0x00000100,	/* direction in */
+	Tdtoksetup	= 0x00000200,	/* setup packet */
+	Tdtok		= 0x00000300,	/* token bits */
+	Tdactive		= 0x00000080,	/* active */
+	Tdhalt		= 0x00000040,	/* halted */
+	Tddberr		= 0x00000020,	/* data buffer error */
+	Tdbabble	= 0x00000010,	/* babble error */
+	Tdtrerr		= 0x00000008,	/* transanction error */
+	Tdmmf		= 0x00000004,	/* missed µframe */
+	Tddcs		= 0x00000002,	/* do complete split */
+	Tdping		= 0x00000001,	/* do ping */
+
+	Tderrors	= Tdhalt|Tddberr|Tdbabble|Tdtrerr|Tdmmf,
+
+	/* Qh bits (eps0) */
+	Qhrlcmask	= 0xF,		/* nak reload count */
+	Qhrlcshift	= 28,		/* nak reload count */
+	Qhnhctl		= 0x08000000,	/* not-high speed ctl */
+	Qhmplmask	= 0x7FF,	/* max packet */
+	Qhmplshift	= 16,
+	Qhhrl		= 0x00008000,	/* head of reclamation list */
+	Qhdtc		= 0x00004000,	/* data toggle ctl. */
+	Qhint		= 0x00000080,	/* inactivate on next transition */
+	Qhspeedmask	= 0x00003000,	/* speed bits */
+	Qhfull		= 0x00000000,	/* full speed */
+	Qhlow		= 0x00001000,	/* low speed */
+	Qhhigh		= 0x00002000,	/* high speed */
+
+	/* Qh bits (eps1) */
+	Qhmultshift	= 30,		/* multiple tds per µframe */
+	Qhmultmask	= 3,
+	Qhportshift	= 23,		/* hub port number */
+	Qhhubshift	= 16,		/* hub address */
+	Qhscmshift	= 8,		/* split completion mask bits */
+	Qhismshift	= 0,		/* interrupt sched. mask bits */
+};
+
+/*
+ * Endpoint tree (software)
+ */
+struct Qtree
+{
+	int	nel;
+	int	depth;
+	ulong*	bw;
+	Qh**	root;
+};
+
+/*
+ * One per endpoint per direction, to control I/O. 
+ */
+struct Qio
+{
+	QLock;			/* for the entire I/O process */
+	Rendez;			/* wait for completion */
+	Qh*	qh;		/* Td list (field const after init) */
+	int	usbid;		/* usb address for endpoint/device */
+	int	toggle;		/* Tddata0/Tddata1 */
+	int	tok;		/* Tdtoksetup, Tdtokin, Tdtokout */
+	ulong	iotime;		/* last I/O time; to hold interrupt polls */
+	int	debug;		/* debug flag from the endpoint */
+	char*	err;		/* error string */
+	char*	tag;		/* debug (no room in Qh for this) */
+	ulong	bw;
+};
+
+struct Ctlio
+{
+	Qio;			/* a single Qio for each RPC */
+	uchar*	data;		/* read from last ctl req. */
+	int	ndata;		/* number of bytes read */
+};
+
+struct Isoio
+{
+	QLock;
+	Rendez;			/* wait for space/completion/errors */
+	int	usbid;		/* address used for device/endpoint */
+	int	tok;		/* Tdtokin or Tdtokout */
+	int	state;		/* Qrun -> Qdone -> Qrun... -> Qclose */
+	int	nframes;	/* number of frames ([S]Itds) used */
+	uchar*	data;		/* iso data buffers if not embedded */
+	char*	err;		/* error string */
+	int	nerrs;		/* nb of consecutive I/O errors */
+	ulong	maxsize;		/* ntds * ep->maxpkt */
+	long	nleft;		/* number of bytes left from last write */
+	int	debug;		/* debug flag from the endpoint */
+	int	hs;		/* is high speed? */
+	Isoio*	next;		/* in list of active Isoios */
+	ulong	td0frno;	/* first frame used in ctlr */
+	union{
+		Itd*	tdi;	/* next td processed by interrupt */
+		Sitd*	stdi;
+	};
+	union{
+		Itd*	tdu;	/* next td for user I/O in tdps */
+		Sitd*	stdu;
+	};
+	union{
+		Itd**	itdps;	/* itdps[i]: ptr to Itd for i-th frame or nil */
+		Sitd**	sitdps;	/* sitdps[i]: ptr to Sitd for i-th frame or nil */
+		ulong**	tdps;	/* same thing, as seen by hw */
+	};
+};
+
+struct Poll
+{
+	Lock;
+	Rendez;
+	int must;
+	int does;
+};
+
+struct Ctlr
+{
+	Rendez;			/* for waiting to async advance doorbell */
+	Lock;			/* for ilock. qh lists and basic ctlr I/O */
+	QLock	portlck;	/* for port resets/enable... (and doorbell) */
+	int	active;		/* in use or not */
+	Pcidev*	pcidev;
+	Ecapio*	capio;		/* Capability i/o regs */
+	Eopio*	opio;		/* Operational i/o regs */
+
+	int	nframes;	/* 1024, 512, or 256 frames in the list */
+	ulong*	frames;		/* periodic frame list (hw) */
+	Qh*	qhs;		/* async Qh circular list for bulk/ctl */
+	Qtree*	tree;		/* tree of Qhs for the periodic list */
+	int	ntree;		/* number of dummy qhs in tree */
+	Qh*	intrqhs;		/* list of (not dummy) qhs in tree  */
+	Isoio*	iso;		/* list of active Iso I/O */
+	ulong	load;
+	ulong	isoload;
+	int	nintr;		/* number of interrupts attended */
+	int	ntdintr;		/* number of intrs. with something to do */
+	int	nqhintr;		/* number of async td intrs. */
+	int	nisointr;	/* number of periodic td intrs. */
+	int	nreqs;
+	Poll	poll;
+};
+
+struct Edpool
+{
+	Lock;
+	Ed*	free;
+	int	nalloc;
+	int	ninuse;
+	int	nfree;
+};
+
+/*
+ * Capability registers (hw)
+ */
+struct Ecapio
+{
+	ulong	cap;		/* 00 controller capability register */
+	ulong	parms;		/* 04 structural parameters register */
+	ulong	capparms;	/* 08 capability parameters */
+	ulong	portroute;	/* 0c not on the CS5536 */
+};
+
+/*
+ * Operational registers (hw)
+ */
+struct Eopio
+{
+	ulong	cmd;		/* 00 command */
+	ulong	sts;		/* 04 status */
+	ulong	intr;		/* 08 interrupt enable */
+	ulong	frno;		/* 0c frame index */
+	ulong	seg;		/* 10 bits 63:32 of EHCI datastructs (unused) */
+	ulong	frbase;		/* 14 frame list base addr, 4096-byte boundary */
+	ulong	link;		/* 18 link for async list */
+	uchar	d2c[0x40-0x1c];	/* 1c dummy */
+	ulong	config;		/* 40 1: all ports default-routed to this HC */
+	ulong	portsc[1];	/* 44 Port status and control, one per port */
+};
+
+/*
+ * We use the 64-bit version for Itd, Sitd, Td, and Qh.
+ * If the ehci is 64-bit capable it assumes we are using those
+ * structures even when the system is 32 bits.
+ */
+
+/*
+ * Iso transfer descriptor. hw. 92 bytes, 104 bytes total
+ * aligned to 32.
+ */
+struct Itd
+{
+	ulong	link;		/* to next hw struct */
+	ulong	csw[8];		/* sts/length/pg/off. updated by hw */
+	ulong	buffer[7];	/* buffer pointers, addrs, maxsz */
+	ulong	xbuffer[7];	/* high 32 bits of buffer for 64-bits */
+
+	/* software */
+	Itd*	next;
+	ulong	ndata;		/* number of bytes in data */
+	ulong	mdata;		/* max number of bytes in data */
+	uchar*	data;
+};
+
+/*
+ * Split transaction iso transfer descriptor.
+ * hw: 36 bytes, 52 bytes total. aligned to 32.
+ */
+struct Sitd
+{
+	ulong	link;		/* to next hw struct */
+	ulong	epc;		/* static endpoint state. addrs */
+	ulong	mfs;		/* static endpoint state. µ-frame sched. */
+	ulong	csw;		/* transfer state. updated by hw */
+	ulong	buffer[2];	/* buf. ptr/offset. offset updated by hw */
+				/* buf ptr/TP/Tcnt. TP/Tcnt updated by hw */
+	ulong	blink;		/* back pointer */
+	ulong	xbuffer[2];	/* high 32 bits of buffer for 64-bits */
+
+	/* software */
+	Sitd*	next;
+	ulong	ndata;		/* number of bytes in data */
+	ulong	mdata;		/* max number of bytes in data */
+	uchar*	data;
+};
+
+/*
+ * Queue element transfer descriptor.
+ * hw: first 52 bytes; total 68+sbuff bytes aligned to 32 bytes.
+ */
+struct Td
+{
+	ulong	nlink;		/* to next Td */
+	ulong	alink;		/* alternate link to next Td */
+	ulong	csw;		/* cmd/sts. updated by hw */
+	ulong	buffer[5];	/* buf ptrs. offset updated by hw */
+	ulong	xbuffer[5];	/* high 32 bits of buffer for 64-bits */
+
+	Td*	next;		/* in qh or Isoio or free list */
+	ulong	ndata;		/* bytes available/used at data */
+	uchar*	data;		/* pointer to actual data */
+	uchar*	buff;		/* allocated data buffer or nil */
+	uchar	sbuff[1];	/* first byte of embedded buffer */
+};
+
+/*
+ * Queue head. Aligned to 32 bytes.
+ * hw uses the first 68 bytes, 92 total.
+ */
+struct Qh
+{
+	ulong	link;		/* to next Qh in round robin */
+	ulong	eps0;		/* static endpoint state. addrs */
+	ulong	eps1;		/* static endpoint state. µ-frame sched. */
+
+	/* updated by hw */
+	ulong	clink;		/* current Td (No Term bit here!) */
+	ulong	nlink;		/* to next Td */
+	ulong	alink;		/* alternate link to next Td */
+	ulong	csw;		/* cmd/sts. updated by hw */
+	ulong	buffer[5];	/* buf ptrs. offset updated by hw */
+	ulong	xbuffer[5];	/* high 32 bits of buffer for 64-bits */
+
+	/* software */
+	Qh*	next;		/* in controller list/tree of Qhs */
+	int	state;		/* Qidle -> Qinstall -> Qrun -> Qdone | Qclose */
+	Qio*	io;		/* for this queue */
+	Td*	tds;		/* for this queue */
+	int	sched;		/* slot for for intr. Qhs */
+	Qh*	inext;		/* next in list of intr. qhs */
+};
+
+/*
+ * We can avoid frame span traversal nodes if we don't span frames.
+ * Just schedule transfer that can fit on the current frame and
+ * wait a little bit otherwise.
+ */
+
+/*
+ * Software. Ehci descriptors provided by pool.
+ * There are soo few because we avoid using Fstn.
+ */
+union Ed
+{
+	Ed*	next;		/* in free list */
+	Qh	qh;
+	Td	td;
+	Itd	itd;
+	Sitd	sitd;
+	uchar	align[Align];
+};
+
+#define diprint		if(debug || iso->debug)print
+#define ddiprint		if(debug>1 || iso->debug>1)print
+#define dqprint		if(debug || (qh->io && qh->io->debug))print
+#define ddqprint		if(debug>1 || (qh->io && qh->io->debug>1))print
+#define TRUNC(x, sz)	((x) & ((sz)-1))
+#define LPTR(q)		((ulong*)KADDR((q) & ~0x1F))
+
+static int debug;
+static Edpool edpool;
+static Ctlr* ctlrs[Nhcis];
+static char Ebug[] = "not yet implemented";
+static char* qhsname[] = { "idle", "install", "run", "done", "close", "FREE" };
+
+
+static void
+ehcirun(Ctlr *ctlr, int on)
+{
+	int i;
+	Eopio *opio;
+
+	ddprint("ehci %#p %s\n", ctlr->capio, on ? "starting" : "halting");
+	opio = ctlr->opio;
+	if(on)
+		opio->cmd |= Crun;
+	else
+		opio->cmd = Cstop;
+	for(i = 0; i < 100; i++)
+		if(on == 0 && (opio->sts & Shalted) != 0)
+			break;
+		else if(on != 0 && (opio->sts & Shalted) == 0)
+			break;
+		else
+			delay(1);
+	if(i == 100)
+		print("ehci %#p %s cmd timed out\n",
+			ctlr->capio, on ? "run" : "halt");
+	ddprint("ehci %#p cmd %#ulx sts %#ulx\n", ctlr->capio, opio->cmd, opio->sts);
+}
+
+static void*
+edalloc(void)
+{
+	Ed *ed;
+	Ed *pool;
+	int i;
+
+	lock(&edpool);
+	if(edpool.free == nil){
+		pool = xspanalloc(Incr*sizeof(Ed), Align, 0);
+		if(pool == nil)
+			panic("edalloc");
+		for(i=Incr; --i>=0;){
+			pool[i].next = edpool.free;
+			edpool.free = &pool[i];
+		}
+		edpool.nalloc += Incr;
+		edpool.nfree += Incr;
+		dprint("ehci: edalloc: %d eds\n", edpool.nalloc);
+	}
+	ed = edpool.free;
+	edpool.free = ed->next;
+	edpool.ninuse++;
+	edpool.nfree--;
+	unlock(&edpool);
+
+	memset(ed, 0, sizeof(Ed));	/* safety */
+	assert(((ulong)ed & 0xF) == 0);
+	return ed;
+}
+
+static void
+edfree(void *a)
+{
+	Ed *ed;
+
+	ed = a;
+	lock(&edpool);
+	ed->next = edpool.free;
+	edpool.free = ed;
+	edpool.ninuse--;
+	edpool.nfree++;
+	unlock(&edpool);
+}
+
+/*
+ * Allocate and so same initialization.
+ * Free after releasing buffers used.
+ */
+
+static Itd*
+itdalloc(void)
+{
+	Itd *td;
+
+	td = edalloc();
+	td->link = Lterm;
+	return td;
+}
+
+static void
+itdfree(Itd *td)
+{
+	edfree(td);
+}
+
+static Sitd*
+sitdalloc(void)
+{
+	Sitd *td;
+
+	td = edalloc();
+	td->link = td->blink = Lterm;
+	return td;
+}
+
+static void
+sitdfree(Sitd *td)
+{
+	edfree(td);
+}
+
+static Td*
+tdalloc(void)
+{
+	Td *td;
+
+	td = edalloc();
+	td->nlink = td->alink = Lterm;
+	return td;
+}
+
+static void
+tdfree(Td *td)
+{
+	if(td == nil)
+		return;
+	free(td->buff);
+	edfree(td);
+}
+
+static void
+tdlinktd(Td *td, Td *next)
+{
+	td->next = next;
+	td->alink = Lterm;
+	if(next == nil)
+		td->nlink = Lterm;
+	else
+		td->nlink = PADDR(next);
+}
+
+static Qh*
+qhlinkqh(Qh *qh, Qh *next)
+{
+	qh->next = next;
+	qh->link = PADDR(next)|Lqh;
+	return qh;
+}
+
+static void
+qhsetaddr(Qh *qh, ulong addr)
+{
+	ulong eps0;
+	ulong ep;
+	ulong dev;
+
+	eps0 = qh->eps0 & ~((Epmax<<8)|Devmax);
+	ep = (addr >> 7) & Epmax;
+	dev = addr & Devmax;
+	eps0 |= ep << 8;
+	eps0 |= dev;
+	qh->eps0 = eps0;
+}
+
+/*
+ * return smallest power of 2 <= n
+ */
+static int
+flog2lower(int n)
+{
+	int i;
+
+	for(i = 0; (1 << (i + 1)) <= n; i++)
+		;
+	return i;
+}
+
+static int
+pickschedq(Qtree *qt, int pollival, ulong bw, ulong limit)
+{
+	int i, j, d, upperb, q;
+	ulong best, worst, total;
+
+	d = flog2lower(pollival);
+	if(d > qt->depth)
+		d = qt->depth;
+	q = -1;
+	worst = 0;
+	best = ~0;
+	upperb = (1 << (d+1)) - 1;
+	for(i = (1 << d) - 1; i < upperb; i++){
+		total = qt->bw[0];
+		for(j = i; j > 0; j = (j - 1) / 2)
+			total += qt->bw[j];
+		if(total < best){
+			best = total;
+			q = i;
+		}
+		if(total > worst)
+			worst = total;
+	}
+	if(worst + bw >= limit)
+		return -1;
+	return q;
+}
+
+static int
+schedq(Ctlr *ctlr, Qh *qh, int pollival)
+{
+	int q;
+	Qh *tqh;
+	ulong bw;
+
+	bw = qh->io->bw;
+	q = pickschedq(ctlr->tree, pollival, 0, ~0);
+	ddqprint("ehci: sched %#p q %d, ival %d, bw %uld\n",
+		qh->io, q, pollival, bw);
+	if(q < 0){
+		print("ehci: no room for ed\n");
+		return -1;
+	}
+	ctlr->tree->bw[q] += bw;
+	tqh = ctlr->tree->root[q];
+	qh->sched = q;
+	qhlinkqh(qh, tqh->next);
+	qhlinkqh(tqh, qh);
+	qh->inext = ctlr->intrqhs;
+	ctlr->intrqhs = qh;
+	return 0;
+}
+
+static void
+unschedq(Ctlr *ctlr, Qh *qh)
+{
+	int q;
+	Qh *prev, *this, *next;
+	Qh **l;
+	ulong bw;
+
+	bw = qh->io->bw;
+	q = qh->sched;
+	if(q < 0)
+		return;
+	ctlr->tree->bw[q] -= bw;
+
+	prev = ctlr->tree->root[q];
+	this = prev->next;
+	while(this != nil && this != qh){
+		prev = this;
+		this = this->next;
+	}
+	if(this == nil)
+		print("ehci: unschedq %d: not found\n", q);
+	else{
+		next = this->next;
+		qhlinkqh(prev, next);
+	}
+	for(l = &ctlr->intrqhs; *l != nil; l = &(*l)->inext)
+		if(*l == qh){
+			*l = (*l)->inext;
+			return;
+		}
+	print("ehci: unschedq: qh %#p not found\n", qh);
+}
+
+static ulong
+qhmaxpkt(Qh *qh)
+{
+	return (qh->eps0 >> Qhmplshift) & Qhmplmask;
+}
+
+static void
+qhsetmaxpkt(Qh *qh, int maxpkt)
+{
+	ulong eps0;
+
+	eps0 = qh->eps0 & ~(Qhmplmask << Qhmplshift);
+	eps0 |= (maxpkt & Qhmplmask) << Qhmplshift;
+	qh->eps0 = eps0;
+}
+
+/*
+ * Initialize the round-robin circular list of ctl/bulk Qhs
+ * if ep is nil. Otherwise, allocate and link a new Qh in the ctlr.
+ */
+static Qh*
+qhalloc(Ctlr *ctlr, Ep *ep, Qio *io, char* tag)
+{
+	Qh *qh;
+	int ttype;
+
+	qh = edalloc();
+	qh->nlink = Lterm;
+	qh->alink = Lterm;
+	qh->csw = Tdhalt;
+	qh->state = Qidle;
+	qh->sched = -1;
+	qh->io = io;
+	if(ep != nil){
+		qh->eps0 = 0;
+		qhsetmaxpkt(qh, ep->maxpkt);
+		if(ep->dev->speed == Lowspeed)
+			qh->eps0 |= Qhlow;
+		if(ep->dev->speed == Highspeed)
+			qh->eps0 |= Qhhigh;
+		else if(ep->ttype == Tctl)
+			qh->eps0 |= Qhnhctl;
+		qh->eps0 |= Qhdtc;
+		qh->eps0 |= (8 << Qhrlcshift);	/* 8 naks max */
+		qhsetaddr(qh, io->usbid);
+		qh->eps1 = (ep->ntds & Qhmultmask) << Qhmultshift;
+		qh->eps1 |= ep->dev->port << Qhportshift;
+		qh->eps1 |= ep->dev->hub << Qhhubshift;
+		qh->eps1 |= 034 << Qhscmshift;
+		if(ep->ttype == Tintr)
+			qh->eps1 |= (1 << Qhismshift); /* intr. start µf. */
+		if(io != nil)
+			io->tag = tag;
+	}
+	ilock(ctlr);
+	ttype = Tctl;
+	if(ep != nil)
+		ttype = ep->ttype;
+	switch(ttype){
+	case Tctl:
+	case Tbulk:
+		if(ctlr->qhs == nil){
+			ctlr->qhs = qhlinkqh(qh, qh);
+			ctlr->opio->link = PADDR(qh)|Lqh;
+			qh->eps0 |= Qhhigh | Qhhrl;
+		}else{
+			qhlinkqh(qh, ctlr->qhs->next);
+			qhlinkqh(ctlr->qhs, qh);
+		}
+		break;
+	case Tintr:
+		schedq(ctlr, qh, ep->pollival);
+		break;
+	default:
+		print("ehci: qhalloc called for ttype != ctl/bulk\n");
+	}
+	iunlock(ctlr);
+	return qh;
+}
+
+static int
+qhadvanced(void *a)
+{
+	Ctlr *ctlr;
+
+	ctlr = a;
+	return (ctlr->opio->cmd & Ciasync) == 0;
+}
+
+/*
+ * called when a qh is removed, to be sure the hw is not
+ * keeping pointers into it.
+ */
+static void
+qhcoherency(Ctlr *ctlr)
+{
+	int i;
+
+	qlock(&ctlr->portlck);
+	ctlr->opio->cmd |= Ciasync;	/* ask for intr. on async advance */
+	for(i = 0; i < 3 && qhadvanced(ctlr) == 0; i++)
+		if(!waserror()){
+			tsleep(ctlr, qhadvanced, ctlr, Abortdelay);
+			poperror();
+		}
+	dprint("ehci: qhcoherency: doorbell %d\n", qhadvanced(ctlr));
+	if(i == 3)
+		print("ehci: async advance doorbell did not ring\n");
+	ctlr->opio->cmd &= ~Ciasync;	/* try to clean */
+	qunlock(&ctlr->portlck);
+}
+
+static void
+qhfree(Ctlr *ctlr, Qh *qh)
+{
+	Td *td;
+	Td *ltd;
+	Qh *q;
+
+	if(qh == nil)
+		return;
+	ilock(ctlr);
+	if(qh->sched < 0){
+		for(q = ctlr->qhs; q != nil; q = q->next)
+			if(q->next == qh)
+				break;
+		if(q == nil)
+			panic("qhfree: nil q");
+		q->next = qh->next;
+		q->link = qh->link;
+	}else
+		unschedq(ctlr, qh);
+	iunlock(ctlr);
+
+	qhcoherency(ctlr);
+
+	for(td = qh->tds; td != nil; td = ltd){
+		ltd = td->next;
+		tdfree(td);
+	}
+
+	edfree(qh);
+}
+
+static void
+qhlinktd(Qh *qh, Td *td)
+{
+	ulong csw;
+	int i;
+
+	if(td == nil){
+		qh->tds = nil;
+		qh->csw |= Tdhalt;
+		qh->csw &= ~Tdactive;
+	}else{
+		qh->tds = td;
+		csw = qh->csw & (Tddata1|Tdping);	/* save */
+		qh->csw = Tdhalt;
+		qh->clink = 0;
+		qh->alink = Lterm;
+		qh->nlink = PADDR(td);
+		for(i = 0; i < nelem(qh->buffer); i++)
+			qh->buffer[i] = 0;
+		qh->csw = csw & ~(Tdhalt|Tdactive);	/* activate next */
+	}
+}
+
+static char*
+seprintlink(char *s, char *se, char *name, ulong l, int typed)
+{
+	s = seprint(s, se, "%s %ulx", name, l);
+	if((l & Lterm) != 0)
+		return seprint(s, se, "T");
+	if(typed == 0)
+		return s;
+	switch(l & (3<<1)){
+	case Litd:
+		return seprint(s, se, "I");
+	case Lqh:
+		return seprint(s, se, "Q");
+	case Lsitd:
+		return seprint(s, se, "S");
+	default:
+		return seprint(s, se, "F");
+	}
+}
+
+static char*
+seprintitd(char *s, char *se, Itd *td)
+{
+	int i;
+	char flags[6];
+	ulong b0;
+	ulong b1;
+	char *rw;
+
+	if(td == nil)
+		return seprint(s, se, "<nil itd>\n");
+	b0 = td->buffer[0];
+	b1 = td->buffer[1];
+
+	s = seprint(s, se, "itd %#p", td);
+	rw = (b1 & Itdin) ? "in" : "out";
+	s = seprint(s, se, " %s ep %uld dev %uld max %uld mult %uld",
+		rw, (b0>>8)&Epmax, (b0&Devmax),
+		td->buffer[1] & 0x7ff, b1 & 3);
+	s = seprintlink(s, se, " link", td->link, 1);
+	s = seprint(s, se, "\n");
+	for(i = 0; i < nelem(td->csw); i++){
+		memset(flags, '-', 5);
+		if((td->csw[i] & Itdactive) != 0)
+			flags[0] = 'a';
+		if((td->csw[i] & Itdioc) != 0)
+			flags[1] = 'i';
+		if((td->csw[i] & Itddberr) != 0)
+			flags[2] = 'd';
+		if((td->csw[i] & Itdbabble) != 0)
+			flags[3] = 'b';
+		if((td->csw[i] & Itdtrerr) != 0)
+			flags[4] = 't';
+		flags[5] = 0;
+		s = seprint(s, se, "\ttd%d %s", i, flags);
+		s = seprint(s, se, " len %uld", (td->csw[i] >> 16) & 0x7ff);
+		s = seprint(s, se, " pg %uld", (td->csw[i] >> 12) & 0x7);
+		s = seprint(s, se, " off %uld\n", td->csw[i] & 0xfff);
+	}
+	s = seprint(s, se, "\tbuffs:");
+	for(i = 0; i < nelem(td->buffer); i++)
+		s = seprint(s, se, " %#ulx", td->buffer[i] >> 12);
+	return seprint(s, se, "\n");
+}
+
+static char*
+seprintsitd(char *s, char *se, Sitd *td)
+{
+	static char pc[4] = { 'a', 'b', 'm', 'e' };
+	char rw;
+	char pg;
+	char ss;
+	char flags[8];
+
+	if(td == nil)
+		return seprint(s, se, "<nil sitd>\n");
+	s = seprint(s, se, "sitd %#p", td);
+	rw = (td->epc & Stdin) ? 'r' : 'w';
+	s = seprint(s, se, " %c ep %uld dev %uld",
+		rw, (td->epc>>8)&0xf, td->epc&0x7f);
+	s = seprint(s, se, " max %uld", (td->csw >> 16) & 0x3ff);
+	s = seprint(s, se, " hub %uld", (td->epc >> 16) & 0x7f);
+	s = seprint(s, se, " port %uld\n", (td->epc >> 24) & 0x7f);
+	memset(flags, '-', 7);
+	if((td->csw & Stdactive) != 0)
+		flags[0] = 'a';
+	if((td->csw & Stdioc) != 0)
+		flags[1] = 'i';
+	if((td->csw & Stderr) != 0)
+		flags[2] = 'e';
+	if((td->csw & Stddberr) != 0)
+		flags[3] = 'd';
+	if((td->csw & Stdbabble) != 0)
+		flags[4] = 'b';
+	if((td->csw & Stdtrerr) != 0)
+		flags[5] = 't';
+	if((td->csw & Stdmmf) != 0)
+		flags[6] = 'n';
+	flags[7] = 0;
+	ss = (td->csw & Stddcs) ? 'c' : 's';
+	pg = (td->csw & Stdpg) ? '1' : '0';
+	s = seprint(s, se, "\t%s %cs pg%c", flags, ss, pg);
+	s = seprint(s, se, " b0 %#ulx b1 %#ulx off %uld\n",
+		td->buffer[0] >> 12, td->buffer[1] >> 12, td->buffer[0] & 0xfff);
+	s = seprint(s, se, "\ttpos %c tcnt %uld",
+		pc[(td->buffer[0]>>3)&3], td->buffer[1] & 7);
+	s = seprint(s, se, " ssm %#ulx csm %#ulx cspm %#ulx",
+		td->mfs & 0xff, (td->mfs>>8) & 0xff, (td->csw>>8) & 0xff);
+	s = seprintlink(s, se, " link", td->link, 1);
+	s = seprintlink(s, se, " blink", td->blink, 0);
+	return seprint(s, se, "\n");
+}
+
+static long
+maxtdlen(Td *td)
+{
+	return (td->csw >> Tdlenshift) & Tdlenmask;
+}
+
+static long
+tdlen(Td *td)
+{
+	if(td->data == nil)
+		return 0;
+	return td->ndata - maxtdlen(td);
+}
+
+static char*
+seprinttd(char *s, char *se, Td *td, char *tag)
+{
+	static char *tok[4] = { "out", "in", "setup", "BUG" };
+	char flags[9];
+	char t;
+	char ss;
+	int i;
+
+	s = seprint(s, se, "%s %#p", tag, td);
+	s = seprintlink(s, se, " nlink", td->nlink, 0);
+	s = seprintlink(s, se, " alink", td->alink, 0);
+	s = seprint(s, se, " %s", tok[(td->csw & Tdtok) >> 8]);
+	if((td->csw & Tdping) != 0)
+		s = seprint(s, se, " png");
+	memset(flags, '-', 8);
+	if((td->csw & Tdactive) != 0)
+		flags[0] = 'a';
+	if((td->csw & Tdioc) != 0)
+		flags[1] = 'i';
+	if((td->csw & Tdhalt) != 0)
+		flags[2] = 'h';
+	if((td->csw & Tddberr) != 0)
+		flags[3] = 'd';
+	if((td->csw & Tdbabble) != 0)
+		flags[4] = 'b';
+	if((td->csw & Tdtrerr) != 0)
+		flags[5] = 't';
+	if((td->csw & Tdmmf) != 0)
+		flags[6] = 'n';
+	if((td->csw & (Tderr2|Tderr1)) == 0)
+		flags[7] = 'z';
+	flags[8] = 0;
+	t = (td->csw & Tddata1) ? '1' : '0';
+	ss = (td->csw & Tddcs) ? 'c' : 's';
+	s = seprint(s, se, "\n\td%c %s %cs", t, flags, ss);
+	s = seprint(s, se, " max %uld", maxtdlen(td));
+	s = seprint(s, se, " pg %uld off %#ulx\n",
+		(td->csw >> Tdpgshift) & Tdpgmask, td->buffer[0] & 0xFFF);
+	s = seprint(s, se, "\tbuffs:");
+	for(i = 0; i < nelem(td->buffer); i++)
+		s = seprint(s, se, " %#ulx", td->buffer[i]>>12);
+	if(td->data != nil)
+		s = seprintdata(s, se, td->data, td->ndata);
+	return seprint(s, se, "\n");
+}
+
+static void
+dumptd(Td *td, char *pref)
+{
+	char buf[256];
+	char *se;
+	int i;
+
+	i = 0;
+	se = buf+sizeof(buf);
+	for(; td != nil; td = td->next){
+		seprinttd(buf, se, td, pref);
+		print("%s", buf);
+		if(i++ > 20){
+			print("...more tds...\n");
+			break;
+		}
+	}
+}
+
+static void
+qhdump(Qh *qh)
+{
+	static char *speed[] = {"full", "low", "high", "BUG"};
+	char buf[256];
+	char *s;
+	char *se;
+	char *tag;
+	Td td;
+
+	if(qh == nil){
+		print("<nil qh>\n");
+		return;
+	}
+	if(qh->io == nil)
+		tag = "qh";
+	else
+		tag = qh->io->tag;
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "%s %#p", tag, qh);
+	s = seprint(s, se, " ep %uld dev %uld",
+		(qh->eps0>>8)&0xf, qh->eps0&0x7f);
+	s = seprint(s, se, " hub %uld", (qh->eps1 >> 16) & 0x7f);
+	s = seprint(s, se, " port %uld", (qh->eps1 >> 23) & 0x7f);
+	s = seprintlink(s, se, " link", qh->link, 1);
+	seprint(s, se, "  clink %#ulx", qh->clink);
+	print("%s\n", buf);
+	s = seprint(buf, se, "\tnrld %uld", (qh->eps0 >> Qhrlcshift) & Qhrlcmask);
+	s = seprint(s, se, " nak %uld", (qh->alink >> 1) & 0xf);
+	s = seprint(s, se, " max %uld ", qhmaxpkt(qh));
+	if((qh->eps0 & Qhnhctl) != 0)
+		s = seprint(s, se, "c");
+	if((qh->eps0 & Qhhrl) != 0)
+		s = seprint(s, se, "h");
+	if((qh->eps0 & Qhdtc) != 0)
+		s = seprint(s, se, "d");
+	if((qh->eps0 & Qhint) != 0)
+		s = seprint(s, se, "i");
+	s = seprint(s, se, " %s", speed[(qh->eps0 >> 12) & 3]);
+	s = seprint(s, se, " mult %uld", (qh->eps1 >> Qhmultshift) & Qhmultmask);
+	seprint(s, se, " scm %#ulx ism %#ulx\n",
+		(qh->eps1 >> 8 & 0xff), qh->eps1 & 0xff);
+	print("%s\n", buf);
+	memset(&td, 0, sizeof(td));
+	memmove(&td, &qh->nlink, 32);	/* overlay area */
+	seprinttd(buf, se, &td, "\tovl");
+	print("%s", buf);
+}
+
+static void
+isodump(Isoio* iso, int all)
+{
+	Itd *td, *tdi, *tdu;
+	Sitd *std, *stdi, *stdu;
+	char buf[256];
+	int i;
+
+	if(iso == nil){
+		print("<nil iso>\n");
+		return;
+	}
+	print("iso %#p %s %s speed state %d nframes %d maxsz %uld",
+		iso, iso->tok == Tdtokin ? "in" : "out",
+		iso->hs ? "high" : "full",
+		iso->state, iso->nframes, iso->maxsize);
+	print(" td0 %uld tdi %#p tdu %#p data %#p\n",
+		iso->td0frno, iso->tdi, iso->tdu, iso->data);
+	if(iso->err != nil)
+		print("\terr %s\n", iso->err);
+	if(iso->err != nil)
+		print("\terr='%s'\n", iso->err);
+	if(all == 0)
+		if(iso->hs != 0){
+			tdi = iso->tdi;
+			seprintitd(buf, buf+sizeof(buf), tdi);
+			print("\ttdi %s\n", buf);
+			tdu = iso->tdu;
+			seprintitd(buf, buf+sizeof(buf), tdu);
+			print("\ttdu %s\n", buf);
+		}else{
+			stdi = iso->stdi;
+			seprintsitd(buf, buf+sizeof(buf), stdi);
+			print("\tstdi %s\n", buf);
+			stdu = iso->stdu;
+			seprintsitd(buf, buf+sizeof(buf), stdu);
+			print("\tstdu %s\n", buf);
+		}
+	else{
+		for(i = 0; i < Nisoframes; i++)
+			if(iso->tdps[i] != nil)
+			if(iso->hs != 0){
+				td = iso->itdps[i];
+				seprintitd(buf, buf+sizeof(buf), td);
+				if(td == iso->tdi)
+					print("i->");
+				if(td == iso->tdu)
+					print("i->");
+				print("[%d]\t%s", i, buf);
+			}else{
+				std = iso->sitdps[i];
+				seprintsitd(buf, buf+sizeof(buf), std);
+				if(std == iso->stdi)
+					print("i->");
+				if(std == iso->stdu)
+					print("u->");
+				print("[%d]\t%s", i, buf);
+			}
+	}
+}
+
+static void
+dump(Hci *hp)
+{
+	Ctlr *ctlr;
+	Isoio *iso;
+	Eopio *opio;
+	int i;
+	char buf[128];
+	char *s;
+	char *se;
+	Qh *qh;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	ilock(ctlr);
+	print("ehci port %#p frames %#p (%d fr.) nintr %d ntdintr %d",
+		ctlr->capio, ctlr->frames, ctlr->nframes,
+		ctlr->nintr, ctlr->ntdintr);
+	print(" nqhintr %d nisointr %d\n", ctlr->nqhintr, ctlr->nisointr);
+	print("\tcmd %#ulx sts %#ulx intr %#ulx frno %uld",
+		opio->cmd, opio->sts, opio->intr, opio->frno);
+	print(" base %#ulx link %#ulx fr0 %#ulx\n",
+		opio->frbase, opio->link, ctlr->frames[0]);
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "\t");
+	for(i = 0; i < hp->nports; i++){
+		s = seprint(s, se, "p%d %#ulx ", i, opio->portsc[i]);
+		if(hp->nports > 4 && i == hp->nports/2 - 1)
+			s = seprint(s, se, "\n\t");
+	}
+	print("%s\n", buf);
+	qh = ctlr->qhs;
+	i = 0;
+	do{
+		qhdump(qh);
+		qh = qh->next;
+	}while(qh != ctlr->qhs && i++ < 100);
+	if(i > 100)
+		print("...too many Qhs...\n");
+	if(ctlr->intrqhs != nil)
+		print("intr qhs:\n");
+	for(qh = ctlr->intrqhs; qh != nil; qh = qh->inext)
+		qhdump(qh);
+	if(ctlr->iso != nil)
+		print("iso:\n");
+	for(iso = ctlr->iso; iso != nil; iso = iso->next)
+		isodump(ctlr->iso, 0);
+	print("%d eds in tree\n", ctlr->ntree);
+	iunlock(ctlr);
+	lock(&edpool);
+	print("%d eds allocated = %d in use + %d free\n",
+		edpool.nalloc, edpool.ninuse, edpool.nfree);
+	unlock(&edpool);
+}
+
+static char*
+errmsg(int err)
+{
+	if(err == 0)
+		return "ok";
+	if(err & Tddberr)
+		return "data buffer error";
+	if(err & Tdbabble)
+		return "babble detected";
+	if(err & Tdtrerr)
+		return "transaction error";
+	if(err & Tdmmf)
+		return "missed µframe";
+	if(err & Tdhalt)
+		return Estalled;	/* [uo]hci report this error */
+	return Eio;
+}
+
+static char*
+ierrmsg(int err)
+{
+	if(err == 0)
+		return "ok";
+	if(err & Itddberr)
+		return "data buffer error";
+	if(err & Itdbabble)
+		return "babble detected";
+	if(err & Itdtrerr)
+		return "transaction error";
+	return Eio;
+}
+
+static char*
+serrmsg(int err)
+{
+	if(err & Stderr)
+		return "translation translator error";
+	/* other errors have same numbers than Td errors */
+	return errmsg(err);
+}
+
+static int
+isocanread(void *a)
+{
+	Isoio *iso;
+
+	iso = a;
+	if(iso->state == Qclose)
+		return 1;
+	if(iso->state == Qrun && iso->tok == Tdtokin){
+		if(iso->hs != 0 && iso->tdi != iso->tdu)
+			return 1;
+		if(iso->hs == 0 && iso->stdi != iso->stdu)
+			return 1;
+	}
+	return 0;
+}
+
+static int
+isocanwrite(void *a)
+{
+	Isoio *iso;
+
+	iso = a;
+	if(iso->state == Qclose)
+		return 1;
+	if(iso->state == Qrun && iso->tok == Tdtokout){
+		if(iso->hs != 0 && iso->tdu->next != iso->tdi)
+			return 1;
+		if(iso->hs == 0 && iso->stdu->next != iso->stdi)
+			return 1;
+	}
+	return 0;
+}
+
+static void
+itdinit(Isoio *iso, Itd *td)
+{
+	ulong pa;
+	int p;
+	int t;
+	ulong tsize;
+	ulong size;
+
+	/*
+	 * BUG: This does not put an integral number of samples
+	 * on each µframe unless samples per packet % 8 == 0
+	 * Also, all samples are packed early on each frame.
+	 */
+	p = 0;
+	size = td->ndata = td->mdata;
+	pa = PADDR(td->data);
+	for(t = 0; size > 0 && t < 8; t++){
+		tsize = size;
+		if(tsize > iso->maxsize)
+			tsize = iso->maxsize;
+		size -= tsize;
+		td->csw[t] = tsize << Itdlenshift;
+		assert(p < nelem(td->buffer));
+		td->csw[t] |= p << Itdpgshift;
+		td->csw[t] |= (pa & 0xFFF) << Itdoffshift;
+		td->csw[t] |= Itdactive|Itdioc;
+		if(((pa+tsize) & ~0xFFF) != (pa & ~0xFFF))
+			p++;
+		pa += tsize;
+	}
+}
+
+static void
+sitdinit(Isoio *iso, Sitd *td)
+{
+	td->ndata = td->mdata & Stdlenmask;
+	td->csw = (td->ndata << Stdlenshift) | Stdactive | Stdioc;
+	td->buffer[0] = PADDR(td->data);
+	td->buffer[1] = (td->buffer[0] & ~0xFFF) + 0x1000;
+	if(iso->tok == Tdtokin || td->ndata <= 188)
+		td->buffer[1] |= Stdtpall;
+	else
+		td->buffer[1] |= Stdtpbegin;
+	if(iso->tok == Tdtokin)
+		td->buffer[1] |= 1;
+	else
+		td->buffer[1] |= ((td->ndata + 187 ) / 188) & Stdtcntmask;
+}
+
+static int
+itdactive(Itd *td)
+{
+	int i;
+
+	for(i = 0; i < nelem(td->csw); i++)
+		if((td->csw[i] & Itdactive) != 0)
+			return 1;
+	return 0;
+}
+
+static int
+isohsinterrupt(Ctlr *ctlr, Isoio *iso)
+{
+	Itd *tdi;
+	int err;
+	int i;
+	int t;
+	int nframes;
+
+	tdi = iso->tdi;
+	assert(tdi != nil);
+	if(itdactive(tdi))	/* not all tds are done */
+		return 0;
+	ctlr->nisointr++;
+	ddiprint("isohsintr: iso %#p: tdi %#p tdu %#p\n", iso, tdi, iso->tdu);
+	if(iso->state != Qrun && iso->state != Qdone)
+		panic("isofsintr: iso state");
+	if(debug > 1 || iso->debug > 1)
+		isodump(iso, 0);
+
+	nframes = iso->nframes / 2;		/* limit how many we look */
+	if(nframes > Nisoframes)
+		nframes = Nisoframes;
+
+	if(iso->tok == Tdtokin)
+		tdi->ndata = 0;
+	/* else, it has the number of bytes transferred */
+
+	for(i = 0; i < nframes && itdactive(tdi) == 0; i++){
+		err = 0;
+		if(iso->tok == Tdtokin)
+			tdi->ndata += (tdi->csw[i] >> Itdlenshift)&Itdlenmask;
+		for(t = 0; t < nelem(tdi->csw); t++){
+			tdi->csw[i] &= ~Itdioc;
+			err |= tdi->csw[i] & Itderrors;
+		}
+		if(err == 0)
+			iso->nerrs = 0;
+		else if(iso->nerrs++ > iso->nframes/2){
+			if(iso->err == nil){
+				iso->err = ierrmsg(err);
+				diprint("isohsintr: tdi %#p error %#ux %s\n",
+					tdi, err, iso->err);
+				diprint("ctlr load %uld\n", ctlr->load);
+			}
+			tdi->ndata = 0;
+		}else
+			tdi->ndata = 0;
+		if(tdi->next == iso->tdu || tdi->next->next == iso->tdu){
+			memset(iso->tdu->data, 0, iso->tdu->mdata);
+			itdinit(iso, iso->tdu);
+			iso->tdu = iso->tdu->next;
+			iso->nleft = 0;
+		}
+		tdi = tdi->next;
+	}
+	ddiprint("isohsintr: %d frames processed\n", nframes);
+	if(i == nframes)
+		tdi->csw[0] |= Itdioc;
+	iso->tdi = tdi;
+	if(isocanwrite(iso) || isocanread(iso)){
+		diprint("wakeup iso %#p tdi %#p tdu %#p\n", iso,
+			iso->tdi, iso->tdu);
+		wakeup(iso);
+	}
+	return 1;
+}
+
+static int
+isofsinterrupt(Ctlr *ctlr, Isoio *iso)
+{
+	Sitd *stdi;
+	int err;
+	int i;
+	int nframes;
+
+	stdi = iso->stdi;
+	assert(stdi != nil);
+	if((stdi->csw & Stdactive) != 0)		/* nothing new done */
+		return 0;
+	ctlr->nisointr++;
+	ddiprint("isofsintr: iso %#p: tdi %#p tdu %#p\n", iso, stdi, iso->stdu);
+	if(iso->state != Qrun && iso->state != Qdone)
+		panic("isofsintr: iso state");
+	if(debug > 1 || iso->debug > 1)
+		isodump(iso, 0);
+
+	nframes = iso->nframes / 2;		/* limit how many we look */
+	if(nframes > Nisoframes)
+		nframes = Nisoframes;
+
+	for(i = 0; i < nframes && (stdi->csw & Stdactive) == 0; i++){
+		stdi->csw &= ~Stdioc;
+		err = stdi->csw & Stderrors;
+		if(err == 0){
+			iso->nerrs = 0;
+			if(iso->tok == Tdtokin)
+				stdi->ndata = (stdi->csw>>Stdlenshift)&Stdlenmask;
+			/* else len is assumed correct */
+		}else if(iso->nerrs++ > iso->nframes/2){
+			if(iso->err == nil){
+				iso->err = serrmsg(err);
+				diprint("isofsintr: tdi %#p error %#ux %s\n",
+					stdi, err, iso->err);
+				diprint("ctlr load %uld\n", ctlr->load);
+			}
+			stdi->ndata = 0;
+		}else
+			stdi->ndata = 0;
+
+		if(stdi->next == iso->stdu || stdi->next->next == iso->stdu){
+			memset(iso->stdu->data, 0, iso->stdu->mdata);
+			sitdinit(iso, iso->stdu);
+			iso->stdu = iso->stdu->next;
+			iso->nleft = 0;
+		}
+		stdi = stdi->next;
+	}
+	ddiprint("isofsintr: %d frames processed\n", nframes);
+	if(i == nframes)
+		stdi->csw |= Stdioc;
+	iso->stdi = stdi;
+	if(isocanwrite(iso) || isocanread(iso)){
+		diprint("wakeup iso %#p tdi %#p tdu %#p\n", iso,
+			iso->stdi, iso->stdu);
+		wakeup(iso);
+	}
+	return 1;
+}
+
+static int
+qhinterrupt(Ctlr *ctlr, Qh *qh)
+{
+	Td *td;
+	int err;
+	char buf[256];
+
+	if(qh->state != Qrun)
+		panic("qhinterrupt: qh state");
+	if(qh->tds == nil)
+		panic("qhinterrupt: no tds");
+	if((qh->tds->csw & Tdactive) == 0)
+		ddqprint("qhinterrupt port %#p qh %#p\n",ctlr->capio, qh);
+	for(td = qh->tds; td != nil; td = td->next){
+		if(td->csw & Tdactive)
+			return 0;
+		if((td->csw & Tderrors) != 0){
+			err = td->csw & Tderrors;
+if(debug || qh->io->debug){
+seprinttd(buf, buf+sizeof(buf), td, "intr-fail-td");
+print("qh %#p io %#p\n\t%s\n", qh, qh->io, buf);
+}				
+			if(qh->io->err == nil){
+				qh->io->err = errmsg(td->csw & Tderrors);
+				dqprint("qhintr: td %#p csw %#ulx error %#ux %s\n",
+					td, td->csw, err, qh->io->err);
+			}
+			break;
+		}
+		td->ndata = tdlen(td);
+		if(td->ndata < maxtdlen(td)){	/* EOT */
+			td = td->next;
+			break;
+		}
+	}
+	/*
+	 * Done. Make void the Tds not used (errors or EOT) and wakeup epio.
+	 */
+	for(; td != nil; td = td->next)
+		td->ndata = 0;
+	qh->state = Qdone;	
+	wakeup(qh->io);
+	return 1;
+}
+
+static int
+ehciintr(Hci *hp)
+{
+	Ctlr *ctlr;
+	Eopio *opio;
+	Isoio *iso;
+	ulong sts;
+	Qh *qh;
+	int i;
+	int some;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+
+	/*
+	 * Will we know in USB 3.0 who the interrupt was for?.
+	 * Do they still teach indexing in CS?
+	 * This is Intel's doing.
+	 */
+	ilock(ctlr);
+	ctlr->nintr++;
+	sts = opio->sts & Sintrs;
+	if(sts == 0){		/* not ours; shared intr. */
+		iunlock(ctlr);
+		return 0;
+	}
+	opio->sts = sts;
+	if((sts & Sherr) != 0)
+		print("ehci: port %#p fatal host system error\n", ctlr->capio);
+	if((sts & Shalted) != 0)
+		print("ehci: port %#p: halted\n", ctlr->capio);
+	if((sts & Sasync) != 0){
+		dprint("ehci: doorbell\n");
+		wakeup(ctlr);
+	}
+	/*
+	 * We enter always this if, even if it seems the
+	 * interrupt does not report anything done/failed.
+	 * Some controllers don't post interrupts right.
+	 */
+	some = 0;
+	if((sts & (Serrintr|Sintr)) != 0){
+		ctlr->ntdintr++;
+		if(debug > 1){
+			print("ehci port %#p frames %#p nintr %d ntdintr %d",
+				ctlr->capio, ctlr->frames,
+				ctlr->nintr, ctlr->ntdintr);
+			print(" nqhintr %d nisointr %d\n",
+				ctlr->nqhintr, ctlr->nisointr);
+			print("\tcmd %#ulx sts %#ulx intr %#ulx frno %uld",
+				opio->cmd, opio->sts, opio->intr, opio->frno);
+		}
+
+		/* process the Iso transfers */
+		for(iso = ctlr->iso; iso != nil; iso = iso->next)
+			if(iso->state == Qrun || iso->state == Qdone)
+				if(iso->hs != 0)
+					some += isohsinterrupt(ctlr, iso);
+				else
+					some += isofsinterrupt(ctlr, iso);
+
+		/* process the qhs in the periodic tree */
+		for(qh = ctlr->intrqhs; qh != nil; qh = qh->inext)
+			if(qh->state == Qrun)
+				some += qhinterrupt(ctlr, qh);
+
+		/* process the async Qh circular list */
+		qh = ctlr->qhs;
+		i = 0;
+		do{
+			if(qh->state == Qrun)
+				some += qhinterrupt(ctlr, qh);
+			qh = qh->next;
+		}while(qh != ctlr->qhs && i++ < 100);
+		if(i > 100)
+			print("echi: interrupt: qh loop?\n");
+	}
+	iunlock(ctlr);
+	return some;
+}
+
+static void
+interrupt(Ureg*, void* a)
+{
+	ehciintr(a);
+}
+
+static int
+portenable(Hci *hp, int port, int on)
+{
+	Ctlr *ctlr;
+	Eopio *opio;
+	int s;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	s = opio->portsc[port-1];
+	qlock(&ctlr->portlck);
+	if(waserror()){
+		qunlock(&ctlr->portlck);
+		nexterror();
+	}
+	dprint("ehci %#p port %d enable=%d; sts %#x\n",
+		ctlr->capio, port, on, s);
+	ilock(ctlr);
+	if(s & (Psstatuschg | Pschange))
+		opio->portsc[port-1] = s;
+	if(on)
+		opio->portsc[port-1] |= Psenable;
+	else
+		opio->portsc[port-1] &= ~Psenable;
+	microdelay(64);
+	iunlock(ctlr);
+	tsleep(&up->sleep, return0, 0, Enabledelay);
+	dprint("ehci %#p port %d enable=%d: sts %#ulx\n",
+		ctlr->capio, port, on, opio->portsc[port-1]);
+	qunlock(&ctlr->portlck);
+	poperror();
+	return 0;
+}
+
+/*
+ * If we detect during status that the port is low-speed or
+ * during reset that it's full-speed, the device is not for
+ * ourselves. The companion controller will take care.
+ * Low-speed devices will not be seen by usbd. Full-speed
+ * ones are seen because it's only after reset that we know what
+ * they are (usbd may notice a device not enabled in this case).
+ */
+static void
+portlend(Ctlr *ctlr, int port, char *ss)
+{
+	Eopio *opio;
+	ulong s;
+
+	opio = ctlr->opio;
+
+	dprint("ehci %#p port %d: %s speed device: no longer owned\n",
+		ctlr->capio, port, ss);
+	s = opio->portsc[port-1];
+	s &= ~(Pschange|Psstatuschg);
+	s |= Psowner;
+	opio->portsc[port-1] = s;
+
+}
+
+static int
+portreset(Hci *hp, int port, int on)
+{
+	ulong s;
+	Eopio *opio;
+	Ctlr *ctlr;
+	int i;
+
+	if(on == 0)
+		return 0;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	qlock(&ctlr->portlck);
+	if(waserror()){
+		iunlock(ctlr);
+		qunlock(&ctlr->portlck);
+		nexterror();
+	}
+	s = opio->portsc[port-1];
+	dprint("ehci %#p port %d reset; sts %#ulx\n", ctlr->capio, port, s);
+	ilock(ctlr);
+	s &= ~(Psenable|Psreset);
+	opio->portsc[port-1] = s|Psreset;
+	for(i = 0; i < 10; i++){
+		delay(10);
+		if((opio->portsc[port-1] & Psreset) == 0)
+			break;
+	}
+	opio->portsc[port-1] &= ~Psreset;
+	delay(10);
+	if((opio->portsc[port-1] & Psenable) == 0)
+		portlend(ctlr, port, "full");
+
+	iunlock(ctlr);
+	dprint("ehci %#p after port %d reset; sts %#ulx\n",
+		ctlr->capio, port, opio->portsc[port-1]);
+	qunlock(&ctlr->portlck);
+	poperror();
+	return 0;
+}
+
+static int
+portstatus(Hci *hp, int port)
+{
+	int s;
+	int r;
+	Eopio *opio;
+	Ctlr *ctlr;
+
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	qlock(&ctlr->portlck);
+	if(waserror()){
+		iunlock(ctlr);
+		qunlock(&ctlr->portlck);
+		nexterror();
+	}
+	ilock(ctlr);
+	s = opio->portsc[port-1];
+	if(s & (Psstatuschg | Pschange)){
+		opio->portsc[port-1] = s;
+		ddprint("ehci %#p port %d status %#x\n", ctlr->capio, port, s);
+	}
+	/*
+	 * If the port is a low speed port we yield ownership now
+	 * to the [uo]hci companion controller and pretend it's not here.
+	 */
+	if((s & Pspresent) != 0 && (s & Pslinemask) == Pslow){
+		portlend(ctlr, port, "low");
+		s &= ~Pspresent;			/* not for us this time */
+	}
+	iunlock(ctlr);
+	qunlock(&ctlr->portlck);
+	poperror();
+
+	/*
+	 * We must return status bits as a
+	 * get port status hub request would do.
+	 */
+	r = 0;
+	if(s & Pspresent)
+		r |= HPpresent|HPhigh;
+	if(s & Psenable)
+		r |= HPenable;
+	if(s & Pssuspend)
+		r |= HPsuspend;
+	if(s & Psreset)
+		r |= HPreset;
+	if(s & Psstatuschg)
+		r |= HPstatuschg;
+	if(s & Pschange)
+		r |= HPchange;
+	return r;
+}
+
+static char*
+seprintio(char *s, char *e, Qio *io, char *pref)
+{
+	s = seprint(s,e,"%s io %#p qh %#p id %#x", pref, io, io->qh, io->usbid);
+	s = seprint(s,e," iot %ld", io->iotime);
+	s = seprint(s,e," tog %#x tok %#x err %s", io->toggle, io->tok, io->err);
+	return s;
+}
+
+static char*
+seprintep(char *s, char *e, Ep *ep)
+{
+	Qio *io;
+	Ctlio *cio;
+	Ctlr *ctlr;
+
+	ctlr = ep->hp->aux;
+	ilock(ctlr);
+	if(ep->aux == nil){
+		*s = 0;
+		iunlock(ctlr);
+		return s;
+	}
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		s = seprintio(s, e, cio, "c");
+		s = seprint(s, e, "\trepl %d ndata %d\n", ep->rhrepl, cio->ndata);
+		break;
+	case Tbulk:
+	case Tintr:
+		io = ep->aux;
+		if(ep->mode != OWRITE)
+			s = seprintio(s, e, &io[OREAD], "r");
+		if(ep->mode != OREAD)
+			s = seprintio(s, e, &io[OWRITE], "w");
+		break;
+	case Tiso:
+		*s = 0;
+		break;
+	}
+	iunlock(ctlr);
+	return s;
+}
+
+/*
+ * halt condition was cleared on the endpoint. update our toggles.
+ */
+static void
+clrhalt(Ep *ep)
+{
+	Qio *io;
+	ep->clrhalt = 0;
+	switch(ep->ttype){
+	case Tintr:
+	case Tbulk:
+		io = ep->aux;
+		if(ep->mode != OREAD){
+			qlock(&io[OWRITE]);
+			io[OWRITE].toggle = Tddata0;
+			deprint("ep clrhalt for io %#p\n", io+OWRITE);
+			qunlock(&io[OWRITE]);
+		}
+		if(ep->mode != OWRITE){
+			qlock(&io[OREAD]);
+			io[OREAD].toggle = Tddata0;
+			deprint("ep clrhalt for io %#p\n", io+OREAD);
+			qunlock(&io[OREAD]);
+		}
+		break;
+	}
+}
+
+static void
+xdump(char* pref, void *qh)
+{
+	int i;
+	ulong *u;
+
+	u = qh;
+	print("%s %#p:", pref, u);
+	for(i = 0; i < 16; i++)
+		if((i%4) == 0)
+			print("\n %#8.8ulx", u[i]);
+		else
+			print(" %#8.8ulx", u[i]);
+	print("\n");
+}
+
+static long
+episohscpy(Ctlr *ctlr, Ep *ep, Isoio* iso, uchar *b, long count)
+{
+	int nr;
+	long tot;
+	Itd *tdu;
+
+	for(tot = 0; iso->tdi != iso->tdu && tot < count; tot += nr){
+		tdu = iso->tdu;
+		if(itdactive(tdu))
+			break;
+		nr = tdu->ndata;
+		if(tot + nr > count)
+			nr = count - tot;
+		if(nr == 0)
+			print("ehci: ep%d.%d: too many polls\n",
+				ep->dev->nb, ep->nb);
+		else{
+			iunlock(ctlr);		/* We could page fault here */
+			memmove(b+tot, tdu->data, nr);
+			ilock(ctlr);
+			if(nr < tdu->ndata)
+				memmove(tdu->data, tdu->data+nr, tdu->ndata - nr);
+			tdu->ndata -= nr;
+		}
+		if(tdu->ndata == 0){
+			itdinit(iso, tdu);
+			iso->tdu = tdu->next;
+		}
+	}
+	return tot;
+}
+
+static long
+episofscpy(Ctlr *ctlr, Ep *ep, Isoio* iso, uchar *b, long count)
+{
+	int nr;
+	long tot;
+	Sitd *stdu;
+
+	for(tot = 0; iso->stdi != iso->stdu && tot < count; tot += nr){
+		stdu = iso->stdu;
+		if(stdu->csw & Stdactive){
+			diprint("ehci: episoread: %#p tdu active\n", iso);
+			break;
+		}
+		nr = stdu->ndata;
+		if(tot + nr > count)
+			nr = count - tot;
+		if(nr == 0)
+			print("ehci: ep%d.%d: too many polls\n",
+				ep->dev->nb, ep->nb);
+		else{
+			iunlock(ctlr);		/* We could page fault here */
+			memmove(b+tot, stdu->data, nr);
+			ilock(ctlr);
+			if(nr < stdu->ndata)
+				memmove(stdu->data,stdu->data+nr,stdu->ndata - nr);
+			stdu->ndata -= nr;
+		}
+		if(stdu->ndata == 0){
+			sitdinit(iso, stdu);
+			iso->stdu = stdu->next;
+		}
+	}
+	return tot;
+}
+
+static long
+episoread(Ep *ep, Isoio *iso, void *a, long count)
+{
+	Ctlr *ctlr;
+	uchar *b;
+	long tot;
+
+	iso->debug = ep->debug;
+	diprint("ehci: episoread: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb);
+
+	b = a;
+	ctlr = ep->hp->aux;
+	qlock(iso);
+	if(waserror()){
+		qunlock(iso);
+		nexterror();
+	}
+	iso->err = nil;
+	iso->nerrs = 0;
+	ilock(ctlr);
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		error(iso->err ? iso->err : Eio);
+	}
+	iso->state = Qrun;
+	while(isocanread(iso) == 0){
+		iunlock(ctlr);
+		diprint("ehci: episoread: %#p sleep\n", iso);
+		if(waserror()){
+			if(iso->err == nil)
+				iso->err = "I/O timed out";
+			ilock(ctlr);
+			break;
+		}
+		tsleep(iso, isocanread, iso, Isotmout);
+		poperror();
+		ilock(ctlr);
+	}
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		error(iso->err ? iso->err : Eio);
+	}
+	iso->state = Qdone;
+	assert(iso->tdu != iso->tdi);
+
+	if(iso->hs != 0)
+		tot = episohscpy(ctlr, ep, iso, b, count);
+	else
+		tot = episofscpy(ctlr, ep, iso, b, count);
+	iunlock(ctlr);
+	qunlock(iso);
+	poperror();
+	diprint("uhci: episoread: %#p %uld bytes err '%s'\n", iso, tot, iso->err);
+	if(iso->err != nil)
+		error(iso->err);
+	return tot;
+}
+
+/*
+ * iso->tdu is the next place to put data. When it gets full
+ * it is activated and tdu advanced.
+ */
+static long
+putsamples(Isoio *iso, uchar *b, long count)
+{
+	long tot;
+	long n;
+
+	for(tot = 0; isocanwrite(iso) && tot < count; tot += n){
+		n = count-tot;
+		if(iso->hs != 0){
+			if(n > iso->tdu->mdata - iso->nleft)
+				n = iso->tdu->mdata - iso->nleft;
+			memmove(iso->tdu->data+iso->nleft, b+tot, n);
+			iso->nleft += n;
+			if(iso->nleft == iso->tdu->mdata){
+				itdinit(iso, iso->tdu);
+				iso->nleft = 0;
+				iso->tdu = iso->tdu->next;
+			}
+		}else{
+			if(n > iso->stdu->mdata - iso->nleft)
+				n = iso->stdu->mdata - iso->nleft;
+			memmove(iso->stdu->data+iso->nleft, b+tot, n);
+			iso->nleft += n;
+			if(iso->nleft == iso->stdu->mdata){
+				sitdinit(iso, iso->stdu);
+				iso->nleft = 0;
+				iso->stdu = iso->stdu->next;
+			}
+		}
+	}
+	return tot;
+}
+
+/*
+ * Queue data for writing and return error status from
+ * last writes done, to maintain buffered data.
+ */
+static long
+episowrite(Ep *ep, Isoio *iso, void *a, long count)
+{
+	Ctlr *ctlr;
+	uchar *b;
+	int tot;
+	int nw;
+	char *err;
+
+	iso->debug = ep->debug;
+	diprint("ehci: episowrite: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb);
+
+	ctlr = ep->hp->aux;
+	qlock(iso);
+	if(waserror()){
+		qunlock(iso);
+		nexterror();
+	}
+	ilock(ctlr);
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		error(iso->err ? iso->err : Eio);
+	}
+	iso->state = Qrun;
+	b = a;
+	for(tot = 0; tot < count; tot += nw){
+		while(isocanwrite(iso) == 0){
+			iunlock(ctlr);
+			diprint("ehci: episowrite: %#p sleep\n", iso);
+			if(waserror()){
+				if(iso->err == nil)
+					iso->err = "I/O timed out";
+				ilock(ctlr);
+				break;
+			}
+			tsleep(iso, isocanwrite, iso, Isotmout);
+			poperror();
+			ilock(ctlr);
+		}
+		err = iso->err;
+		iso->err = nil;
+		if(iso->state == Qclose || err != nil){
+			iunlock(ctlr);
+			error(err ? err : Eio);
+		}
+		if(iso->state != Qrun)
+			panic("episowrite: iso not running");
+		iunlock(ctlr);		/* We could page fault here */
+		nw = putsamples(iso, b+tot, count-tot);
+		ilock(ctlr);
+	}
+	if(iso->state != Qclose)
+		iso->state = Qdone;
+	iunlock(ctlr);
+	err = iso->err;		/* in case it failed early */
+	iso->err = nil;
+	qunlock(iso);
+	poperror();
+	if(err != nil)
+		error(err);
+	diprint("ehci: episowrite: %#p %d bytes\n", iso, tot);
+	return tot;
+}
+
+static int
+nexttoggle(int toggle, int count, int maxpkt)
+{
+	int np;
+
+	np = count / maxpkt;
+	if(np == 0)
+		np = 1;
+	if((np % 2) == 0)
+		return toggle;
+	if(toggle == Tddata1)
+		return Tddata0;
+	else
+		return Tddata1;
+}
+
+static Td*
+epgettd(Qio *io, int flags, void *a, int count, int maxpkt)
+{
+	Td *td;
+	ulong pa;
+	int i;
+	if(count > Tdmaxpkt)
+		panic("ehci: epgettd: too many bytes");
+	td = tdalloc();
+	td->csw = flags;
+	td->csw |= io->toggle | io->tok | (count << Tdlenshift);
+	td->csw |= Tderr2|Tderr1;
+
+	/*
+	 * use the space wasted by alignment as an
+	 * embedded buffer if count bytes fit in there.
+	 */
+	assert(Align > sizeof(Td));
+	if(count <= Align - sizeof(Td))
+		td->data = td->sbuff;
+	else
+		td->data = td->buff = smalloc(Tdmaxpkt);
+
+	pa = PADDR(td->data);
+	for(i = 0; i < nelem(td->buffer); i++){
+		td->buffer[i] = pa;
+		if(i > 0)
+			td->buffer[i] &= ~0xFFF;
+		pa += 0x1000;
+	}
+	td->ndata = count;
+	if(a != nil && count > 0)
+		memmove(td->data, a, count);
+	io->toggle = nexttoggle(io->toggle, count, maxpkt);
+	return td;
+}
+
+/*
+ * Try to get them idle
+ */
+static void
+aborttds(Qh *qh)
+{
+	Td *td;
+
+	qh->state = Qdone;
+	if(qh->sched >= 0 && (qh->eps0&Qhspeedmask) != Qhhigh)
+		qh->eps0 |= Qhint;	/* inactivate on next pass */
+	for(td = qh->tds; td != nil; td = td->next){
+		if(td->csw & Tdactive)
+			td->ndata = 0;
+		td->csw |= Tdhalt;
+	}
+}
+
+/*
+ * Some controllers do not post the usb/error interrupt after
+ * the work has been done. It seems that we must poll for them.
+ */
+static int
+workpending(void *a)
+{
+	Ctlr *ctlr;
+
+	ctlr = a;
+	return ctlr->nreqs > 0;
+}
+
+static void
+ehcipoll(void* a)
+{
+	Hci *hp;
+	Ctlr *ctlr;
+	Poll *poll;
+	int i;
+
+	hp = a;
+	ctlr = hp->aux;
+	poll = &ctlr->poll;
+	for(;;){
+		if(ctlr->nreqs == 0){
+			if(0)ddprint("ehcipoll %#p sleep\n", ctlr->capio);
+			sleep(poll, workpending, ctlr);
+			if(0)ddprint("ehcipoll %#p awaken\n", ctlr->capio);
+		}
+		for(i = 0; i < 16 && ctlr->nreqs > 0; i++)
+			if(ehciintr(hp) == 0)
+				 break;
+		do{
+			tsleep(&up->sleep, return0, 0, 1);
+			ehciintr(hp);
+		}while(ctlr->nreqs > 0);
+	}
+}
+
+static void
+pollcheck(Hci *hp)
+{
+	Ctlr *ctlr;
+	Poll *poll;
+
+	ctlr = hp->aux;
+	poll = &ctlr->poll;
+
+	if(poll->must != 0 && poll->does == 0){
+		lock(poll);
+		if(poll->must != 0 && poll->does == 0){
+			poll->does++;
+			print("ehci %#p: polling\n", ctlr->capio);
+			kproc("ehcipoll", ehcipoll, hp);
+		}
+		unlock(poll);
+	}
+}
+
+static int
+epiodone(void *a)
+{
+	Qh *qh;
+
+	qh = a;
+	return qh->state != Qrun;
+}
+
+static void
+epiowait(Hci *hp, Qio *io, int tmout, ulong load)
+{
+	Qh *qh;
+	int timedout;
+	Ctlr *ctlr;
+
+	ctlr = hp->aux;
+	qh = io->qh;
+	ddqprint("ehci io %#p sleep on qh %#p state %s\n",
+		io, qh, qhsname[qh->state]);
+	timedout = 0;
+	if(waserror()){
+		dqprint("ehci io %#p qh %#p timed out\n", io, qh);
+		timedout++;
+	}else{
+		if(tmout == 0)
+			sleep(io, epiodone, qh);
+		else
+			tsleep(io, epiodone, qh, tmout);
+		poperror();
+	}
+
+	ilock(ctlr);
+	/* Are we missing interrupts? */
+	if(qh->state == Qrun){
+		iunlock(ctlr);
+		ehciintr(hp);
+		ilock(ctlr);
+		if(qh->state == Qdone){
+			dqprint("ehci %#p: polling required\n", ctlr->capio);
+			ctlr->poll.must = 1;
+			pollcheck(hp);
+		}
+	}
+
+	if(qh->state == Qrun){
+		dqprint("ehci io %#p qh %#p timed out (no intr?)\n", io, qh);
+		timedout = 1;
+	}else if(qh->state != Qdone && qh->state != Qclose)
+		panic("ehci: epio: queue state %d\n", qh->state);
+	if(timedout){
+		aborttds(io->qh);
+		io->err = "request timed out";
+		iunlock(ctlr);
+		if(!waserror()){
+			tsleep(&up->sleep, return0, 0, Abortdelay);
+			poperror();
+		} 
+		ilock(ctlr);
+	}
+	if(qh->state != Qclose)
+		qh->state = Qidle;
+	qhlinktd(qh, nil);
+	ctlr->load -= load;
+	ctlr->nreqs--;
+	iunlock(ctlr);	
+}
+
+/*
+ * Non iso I/O.
+ * To make it work for control transfers, the caller may
+ * lock the Qio for the entire control transfer.
+ * If tmout is not 0 it is a timeout value in ms.
+ *
+ */
+static long
+epio(Ep *ep, Qio *io, void *a, long count, int tmout, int mustlock)
+{
+	Td *td;
+	Td *ltd;
+	Td *td0;
+	Td *ntd;
+	Ctlr *ctlr;
+	Qh* qh;
+	long n;
+	long tot;
+	char buf[128];
+	uchar *c;
+	int saved;
+	int ntds;
+	ulong load;
+	char *err;
+
+	qh = io->qh;
+	ctlr = ep->hp->aux;
+	io->debug = ep->debug;
+	ddeprint("epio: %s ep%d.%d io %#p count %ld load %uld\n",
+		io->tok == Tdtokin ? "in" : "out",
+		ep->dev->nb, ep->nb, io, count, ctlr->load);
+	if((debug > 1 || ep->debug > 1) && io->tok != Tdtokin){
+		seprintdata(buf, buf+sizeof(buf), a, count);
+		print("echi epio: user data: %s\n", buf);
+	}
+	if(mustlock){
+		qlock(io);
+		if(waserror()){
+			qunlock(io);
+			nexterror();
+		}
+	}
+	io->err = nil;
+	ilock(ctlr);
+	if(qh->state == Qclose){	/* Tds released by cancelio */
+		iunlock(ctlr);
+		error(io->err ? io->err : Eio);
+	}
+	if(qh->state != Qidle)
+		panic("epio: qh not idle");
+	qh->state = Qinstall;
+	iunlock(ctlr);
+
+	c = a;
+	td0 = ltd = nil;
+	load = tot = 0;
+	do{
+		n = (Tdmaxpkt / ep->maxpkt) * ep->maxpkt;
+		if(count-tot < n)
+			n = count-tot;
+		if(io->tok != Tdtokin)
+			td = epgettd(io, Tdactive, c+tot, n, ep->maxpkt);
+		else
+			td = epgettd(io, Tdactive, nil, n, ep->maxpkt);
+		if(td0 == nil)
+			td0 = td;
+		else
+			tdlinktd(ltd, td);
+		ltd = td;
+		tot += n;
+		load += ep->load;
+	}while(tot < count);
+	if(td0 == nil || ltd == nil)
+		panic("epio: no td");
+
+	ltd->csw |= Tdioc;	/* the last one interrupts */
+
+	ddeprint("ehci: load %uld ctlr load %uld\n", load, ctlr->load);
+	if(debug > 1 || ep->debug > 1)
+		dumptd(td0, "epio: put: ");
+
+	ilock(ctlr);
+	if(qh->state != Qclose){
+		io->iotime = TK2MS(MACHP(0)->ticks);
+		qh->state = Qrun;
+		qhlinktd(qh, td0);
+		ctlr->nreqs++;
+		ctlr->load += load;
+	}
+	iunlock(ctlr);
+
+	if(ctlr->poll.does)
+		wakeup(&ctlr->poll);
+
+	epiowait(ep->hp, io, tmout, load);
+	if(debug > 1 || ep->debug > 1){
+		dumptd(td0, "epio: got: ");
+		qhdump(qh);
+	}
+
+	tot = 0;
+	c = a;
+	saved = 0;
+	ntds = 0;
+	for(td = td0; td != nil; td = ntd){
+		ntds++;
+		/*
+		 * Use td tok, not io tok, because of setup packets.
+		 * Also, if the Td was stalled or active (previous Td
+		 * was a short packet), we must save the toggle as it is.
+		 */
+		if(td->csw & (Tdhalt|Tdactive)){
+			if(saved++ == 0)
+				io->toggle = td->csw & Tddata1;
+		}else{
+			tot += td->ndata;
+			if((td->csw & Tdtok) == Tdtokin && td->ndata > 0){
+				memmove(c, td->data, td->ndata);
+				c += td->ndata;
+			}
+		}
+		ntd = td->next;
+		tdfree(td);
+	}
+	err = io->err;
+	if(mustlock){
+		qunlock(io);
+		poperror();
+	}
+	ddeprint("epio: io %#p: %d tds: return %ld err '%s'\n",
+		io, ntds, tot, err);
+	if(err == Estalled)
+		return 0;	/* that's our convention */
+	if(err != nil)
+		error(err);
+	if(tot < 0)
+		error(Eio);
+	return tot;
+}
+
+static long
+epread(Ep *ep, void *a, long count)
+{
+	Ctlio *cio;
+	Qio *io;
+	Isoio *iso;
+	char buf[160];
+	ulong delta;
+
+	ddeprint("ehci: epread\n");
+	if(ep->aux == nil)
+		panic("epread: not open");
+
+	pollcheck(ep->hp);
+
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		qlock(cio);
+		if(waserror()){
+			qunlock(cio);
+			nexterror();
+		}
+		ddeprint("epread ctl ndata %d\n", cio->ndata);
+		if(cio->ndata < 0)
+			error("request expected");
+		else if(cio->ndata == 0){
+			cio->ndata = -1;
+			count = 0;
+		}else{
+			if(count > cio->ndata)
+				count = cio->ndata;
+			if(count > 0)
+				memmove(a, cio->data, count);
+			/* BUG for big transfers */
+			free(cio->data);
+			cio->data = nil;
+			cio->ndata = 0;	/* signal EOF next time */
+		}
+		qunlock(cio);
+		poperror();
+		if(debug>1 || ep->debug){
+			seprintdata(buf, buf+sizeof(buf), a, count);
+			print("epread: %s\n", buf);
+		}
+		return count;
+	case Tbulk:
+		io = ep->aux;
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OREAD], a, count, Bulktmout, 1);
+	case Tintr:
+		io = ep->aux;
+		delta = TK2MS(MACHP(0)->ticks) - io[OREAD].iotime + 1;
+		if(delta < ep->pollival / 2)
+			tsleep(&up->sleep, return0, 0, ep->pollival/2 - delta);
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OREAD], a, count, 0, 1);
+	case Tiso:
+		iso = ep->aux;
+		return episoread(ep, iso, a, count);
+	}
+	return -1;
+}
+
+/*
+ * Control transfers are one setup write (data0)
+ * plus zero or more reads/writes (data1, data0, ...)
+ * plus a final write/read with data1 to ack.
+ * For both host to device and device to host we perform
+ * the entire transfer when the user writes the request,
+ * and keep any data read from the device for a later read.
+ * We call epio three times instead of placing all Tds at
+ * the same time because doing so leads to crc/tmout errors
+ * for some devices.
+ * Upon errors on the data phase we must still run the status
+ * phase or the device may cease responding in the future.
+ */
+static long
+epctlio(Ep *ep, Ctlio *cio, void *a, long count)
+{
+	uchar *c;
+	long len;
+
+	ddeprint("epctlio: cio %#p ep%d.%d count %ld\n",
+		cio, ep->dev->nb, ep->nb, count);
+	if(count < Rsetuplen)
+		error("short usb comand");
+	qlock(cio);
+	free(cio->data);
+	cio->data = nil;
+	cio->ndata = 0;
+	if(waserror()){
+		qunlock(cio);
+		free(cio->data);
+		cio->data = nil;
+		cio->ndata = 0;
+		nexterror();
+	}
+
+	/* set the address if unset and out of configuration state */
+	if(ep->dev->state != Dconfig && cio->usbid == 0){
+		cio->usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb&Devmax);
+		qhsetaddr(cio->qh, cio->usbid);
+	}
+	/* adjust maxpkt if the user has learned a different one */
+	if(qhmaxpkt(cio->qh) != ep->maxpkt)
+		qhsetmaxpkt(cio->qh, ep->maxpkt);
+	c = a;
+	cio->tok = Tdtoksetup;
+	cio->toggle = Tddata0;
+	if(epio(ep, cio, a, Rsetuplen, Ctltmout, 0) < Rsetuplen)
+		error(Eio);
+	a = c + Rsetuplen;
+	count -= Rsetuplen;
+
+	cio->toggle = Tddata1;
+	if(c[Rtype] & Rd2h){
+		cio->tok = Tdtokin;
+		len = GET2(c+Rcount);
+		if(len <= 0)
+			error("bad length in d2h request");
+		if(len > Maxctllen)
+			error("d2h data too large to fit in ehci");
+		a = cio->data = smalloc(len+1);
+	}else{
+		cio->tok = Tdtokout;
+		len = count;
+	}
+	if(len > 0)
+		if(waserror())
+			len = -1;
+		else{
+			len = epio(ep, cio, a, len, Ctltmout, 0);
+			poperror();
+		}
+	if(c[Rtype] & Rd2h){
+		count = Rsetuplen;
+		cio->ndata = len;
+		cio->tok = Tdtokout;
+	}else{
+		if(len < 0)
+			count = -1;
+		else
+			count = Rsetuplen + len;
+		cio->tok = Tdtokin;
+	}
+	cio->toggle = Tddata1;
+	epio(ep, cio, nil, 0, Ctltmout, 0);
+	qunlock(cio);
+	poperror();
+	ddeprint("epctlio cio %#p return %ld\n", cio, count);
+	return count;
+}
+
+static long
+epwrite(Ep *ep, void *a, long count)
+{
+	Qio *io;
+	Ctlio *cio;
+	Isoio *iso;
+	ulong delta;
+
+	pollcheck(ep->hp);
+
+	ddeprint("ehci: epwrite ep%d.%d\n", ep->dev->nb, ep->nb);
+	if(ep->aux == nil)
+		panic("ehci: epwrite: not open");
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		return epctlio(ep, cio, a, count);
+	case Tbulk:
+		io = ep->aux;
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OWRITE], a, count, Bulktmout, 1);
+	case Tintr:
+		io = ep->aux;
+		delta = TK2MS(MACHP(0)->ticks) - io[OWRITE].iotime + 1;
+		if(delta < ep->pollival)
+			tsleep(&up->sleep, return0, 0, ep->pollival - delta);
+		if(ep->clrhalt)
+			clrhalt(ep);
+		return epio(ep, &io[OWRITE], a, count, 0, 1);
+	case Tiso:
+		iso = ep->aux;
+		return episowrite(ep, iso, a, count);
+	}
+	return -1;
+}
+
+static void
+isofsinit(Ep *ep, Isoio *iso)
+{
+	long left;
+	Sitd *td;
+	Sitd *ltd;
+	int i;
+	ulong frno;
+
+	left = 0;
+	ltd = nil;
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		td = iso->sitdps[frno] = sitdalloc();
+		td->data = iso->data + i * ep->maxpkt;
+		td->epc = ep->dev->port << Stdportshift;
+		td->epc |= ep->dev->hub << Stdhubshift;
+		td->epc |= ep->nb << Stdepshift;
+		td->epc |= ep->dev->nb << Stddevshift;
+		td->mfs = (034 << Stdscmshift) | (1 << Stdssmshift);
+		if(ep->mode == OREAD){
+			td->epc |= Stdin;
+			td->mdata = ep->maxpkt;
+		}else{
+			td->mdata = (ep->hz+left) * ep->pollival / 1000;
+			td->mdata *= ep->samplesz;
+			left = (ep->hz+left) * ep->pollival % 1000;
+			if(td->mdata > ep->maxpkt){
+				print("ehci: ep%d.%d: size > maxpkt\n",
+					ep->dev->nb, ep->nb);
+				print("size = %ld max = %ld\n",
+					td->mdata,ep->maxpkt);
+				td->mdata = ep->maxpkt;
+			}
+		}
+
+		sitdinit(iso, td);
+		if(ltd != nil)
+			ltd->next = td;
+		ltd = td;
+		frno = TRUNC(frno+ep->pollival, Nisoframes);
+	}
+	ltd->next = iso->sitdps[iso->td0frno];
+}
+
+static void
+isohsinit(Ep *ep, Isoio *iso)
+{
+	long left;
+	Itd *td;
+	Itd *ltd;
+	ulong i;
+	ulong pa;
+	int p;
+	ulong frno;
+	int ival;
+
+	iso->hs = 1;
+	ival = 1;
+	if(ep->pollival > 8)
+		ival = ep->pollival/8;
+	left = 0;
+	ltd = nil;
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		td = iso->itdps[frno] = itdalloc();
+		td->data = iso->data + i * 8  * iso->maxsize;
+		pa = PADDR(td->data) & ~0xFFF;
+		for(p = 0; p < 8; p++)
+			td->buffer[i] = pa + p * 0x1000;
+		td->buffer[0] = PADDR(iso->data) & ~0xFFF;
+		td->buffer[0] |= ep->nb << Itdepshift;
+		td->buffer[0] |= ep->dev->nb << Itddevshift;
+		if(ep->mode == OREAD)
+			td->buffer[1] |= Itdin;
+		else
+			td->buffer[1] |= Itdout;
+		td->buffer[1] |= ep->maxpkt << Itdmaxpktshift;
+		td->buffer[2] |= ep->ntds << Itdntdsshift;
+
+		if(ep->mode == OREAD)
+			td->mdata = 8 * iso->maxsize;
+		else{
+			td->mdata = (ep->hz + left) * ep->pollival / 1000;
+			td->mdata *= ep->samplesz;
+			left = (ep->hz + left) * ep->pollival % 1000;
+		}
+		itdinit(iso, td);
+		if(ltd != nil)
+			ltd->next = td;
+		ltd = td;
+		frno = TRUNC(frno + ival, Nisoframes);
+	}
+}
+
+static void
+isoopen(Ctlr *ctlr, Ep *ep)
+{
+	Isoio *iso;
+	int ival;	/* pollival in ms */
+	int n;
+	ulong frno;
+	int i;
+	int w;
+	int woff;
+	int tpf;		/* tds per frame */
+
+	iso = ep->aux;
+	switch(ep->mode){
+	case OREAD:
+		iso->tok = Tdtokin;
+		break;
+	case OWRITE:
+		iso->tok = Tdtokout;
+		break;
+	default:
+		error("iso i/o is half-duplex");
+	}
+	iso->usbid = (ep->nb<<7)|(ep->dev->nb & Devmax);
+	iso->state = Qidle;
+	iso->debug = ep->debug;
+	ival = ep->pollival;
+	tpf = 1;
+	if(ep->dev->speed == Highspeed){
+		tpf = 8;
+		if(ival <= 8)
+			ival = 1;
+		else
+			ival /= 8;
+	}
+	iso->nframes = Nisoframes / ival;
+	if(iso->nframes < 3)
+		error("uhci isoopen bug");	/* we need at least 3 tds */
+	iso->maxsize = ep->ntds * ep->maxpkt;
+	ilock(ctlr);
+	if(ctlr->load + ep->load > 800){
+		iunlock(ctlr);
+		error("bandwidth exceeded");
+	}
+	ctlr->load += ep->load;
+	ctlr->isoload += ep->load;
+	ctlr->nreqs++;
+	dprint("ehci: load %uld isoload %uld\n", ctlr->load, ctlr->isoload);
+	diprint("iso nframes %d pollival %uld ival %d maxpkt %uld ntds %d\n",
+		iso->nframes, ep->pollival, ival, ep->maxpkt, ep->ntds);
+	iunlock(ctlr);
+	if(ctlr->poll.does)
+		wakeup(&ctlr->poll);
+
+	/*
+	 * From here on this cannot raise errors
+	 * unless we catch them and release here all memory allocated.
+	 */
+	assert(ep->maxpkt > 0 && ep->ntds > 0 && ep->ntds < 4);
+	assert(ep->maxpkt <= 1024);
+	iso->tdps = smalloc(sizeof(uintptr) * Nisoframes);
+	iso->data = smalloc(iso->nframes * tpf * ep->ntds * ep->maxpkt);
+	iso->td0frno = TRUNC(ctlr->opio->frno + 10, Nisoframes);
+	/* read: now; write: 1s ahead */
+
+	if(ep->dev->speed == Highspeed)
+		isohsinit(ep, iso);
+	else
+		isofsinit(ep, iso);
+	iso->tdu = iso->tdi = iso->itdps[iso->td0frno];
+	iso->stdu = iso->stdi = iso->sitdps[iso->td0frno];
+
+	ilock(ctlr);
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		*iso->tdps[frno] = ctlr->frames[frno];
+		frno = TRUNC(frno+ival, Nisoframes);
+	}
+
+	/*
+	 * Iso uses a virtual frame window of Nisoframes, and we must
+	 * fill the actual ctlr frame array by placing ctlr->nframes/Nisoframes
+	 * copies of the window in the frame array.
+	 */
+	assert(ctlr->nframes >= Nisoframes && Nisoframes >= iso->nframes);
+	assert(Nisoframes >= Nintrleafs);
+	n = ctlr->nframes / Nisoframes;
+	for(w = 0; w < n; w++){
+		frno = iso->td0frno;
+		woff = w * Nisoframes;
+		for(i = 0; i < iso->nframes ; i++){
+			assert(woff+frno < ctlr->nframes);
+			assert(iso->tdps[frno] != nil);
+			if(ep->dev->speed == Highspeed)
+				ctlr->frames[woff+frno] = PADDR(iso->tdps[frno])|Litd;
+			else
+				ctlr->frames[woff+frno] = PADDR(iso->tdps[frno])|Lsitd;
+			frno = TRUNC(frno+ep->pollival, Nisoframes);
+		}
+	}
+	iso->next = ctlr->iso;
+	ctlr->iso = iso;
+	iso->state = Qdone;
+	iunlock(ctlr);
+	if(debug > 1 || iso->debug >1)
+		isodump(iso, 0);
+
+
+}
+
+/*
+ * Allocate the endpoint and set it up for I/O
+ * in the controller. This must follow what's said
+ * in Ep regarding configuration, including perhaps
+ * the saved toggles (saved on a previous close of
+ * the endpoint data file by epclose).
+ */
+static void
+epopen(Ep *ep)
+{
+	Ctlr *ctlr;
+	Ctlio *cio;
+	Qio *io;
+	int usbid;
+
+	ctlr = ep->hp->aux;
+	deprint("ehci: epopen ep%d.%d\n", ep->dev->nb, ep->nb);
+	if(ep->aux != nil)
+		panic("ehci: epopen called with open ep");
+	if(waserror()){
+		free(ep->aux);
+		ep->aux = nil;
+		nexterror();
+	}
+	switch(ep->ttype){
+	case Tnone:
+		error("endpoint not configured");
+	case Tiso:
+		ep->aux = smalloc(sizeof(Isoio));
+		isoopen(ctlr, ep);
+		break;
+	case Tctl:
+		cio = ep->aux = smalloc(sizeof(Ctlio));
+		cio->debug = ep->debug;
+		cio->ndata = -1;
+		cio->data = nil;
+		if(ep->dev->isroot != 0 && ep->nb == 0)	/* root hub */
+			break;
+		cio->qh = qhalloc(ctlr, ep, cio, "epc");
+		break;
+	case Tbulk:
+		ep->pollival = 1;	/* assume this; doesn't really matter */
+		/* and fall... */
+	case Tintr:
+		io = ep->aux = smalloc(sizeof(Qio)*2);
+		io[OREAD].debug = io[OWRITE].debug = ep->debug;
+		usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb &Devmax);
+		if(ep->mode != OREAD){
+			if(ep->toggle[OWRITE] != 0)
+				io[OWRITE].toggle = Tddata1;
+			else
+				io[OWRITE].toggle = Tddata0;
+			io[OWRITE].tok = Tdtokout;
+			io[OWRITE].usbid = usbid;
+			io[OWRITE].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
+			io[OWRITE].qh = qhalloc(ctlr, ep, io+OWRITE, "epw");
+		}
+		if(ep->mode != OWRITE){
+			if(ep->toggle[OREAD] != 0)
+				io[OREAD].toggle = Tddata1;
+			else
+				io[OREAD].toggle = Tddata0;
+			io[OREAD].tok = Tdtokin;
+			io[OREAD].usbid = usbid;
+			io[OREAD].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
+			io[OREAD].qh = qhalloc(ctlr, ep, io+OREAD, "epr");
+		}
+		break;
+	}
+	if(debug>1 || ep->debug)
+		dump(ep->hp);
+	deprint("ehci: epopen done\n");
+	poperror();
+}
+
+static void
+cancelio(Ctlr *ctlr, Qio *io)
+{
+	Qh *qh;
+
+	ilock(ctlr);
+	qh = io->qh;
+	if(io == nil || io->qh == nil || io->qh->state == Qclose){
+		iunlock(ctlr);
+		return;
+	}
+	dqprint("ehci: cancelio for qh %#p state %s\n",
+		qh, qhsname[qh->state]);
+	aborttds(qh);
+	qh->state = Qclose;
+	iunlock(ctlr);
+	if(!waserror()){
+		tsleep(&up->sleep, return0, 0, Abortdelay);
+		poperror();
+	}
+	wakeup(io);
+	qlock(io);
+	/* wait for epio if running */
+	qunlock(io);
+
+	qhfree(ctlr, qh);
+	io->qh = nil;
+}
+
+static void
+cancelisoio(Ctlr *ctlr, Isoio *iso, int pollival, ulong load)
+{
+	Isoio **il;
+	ulong *lp;
+	int i;
+	int frno;
+	int w;
+	int n;
+	int woff;
+	ulong *tp;
+	Itd *td;
+	Sitd *std;
+	int t;
+
+	ilock(ctlr);
+	if(iso->state == Qclose){
+		iunlock(ctlr);
+		return;
+	}
+	ctlr->nreqs--;
+	if(iso->state != Qrun && iso->state != Qdone)
+		panic("bad iso state");
+	iso->state = Qclose;
+	if(ctlr->isoload < load)
+		panic("ehci: low isoload");
+	ctlr->isoload -= load;
+	ctlr->load -= load;
+	for(il = &ctlr->iso; *il != nil; il = &(*il)->next)
+		if(*il == iso)
+			break;
+	if(*il == nil)
+		panic("cancleiso: not found");
+	*il = iso->next;
+
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		tp = iso->tdps[frno];
+		if(iso->hs != 0){
+			td = iso->itdps[frno];
+			for(t = 0; t < nelem(td->csw); t++)
+				td->csw[1] &= ~(Itdioc|Itdactive);
+		}else{
+			std = iso->sitdps[frno];
+			std->csw &= ~(Stdioc|Stdactive);
+		}
+		for(lp=&ctlr->frames[frno]; !(*lp & Lterm); lp = &LPTR(*lp)[0])
+			if(LPTR(*lp) == tp)
+				break;
+		if(*lp & Lterm)
+			panic("cancelisoio: td not found");
+		*lp = tp[0];
+		/*
+		 * Iso uses a virtual frame window of Nisoframes, and we must
+		 * restore pointers in copies of the window kept at ctlr->frames.
+		 */
+		if(lp == &ctlr->frames[frno]){
+			n = ctlr->nframes / Nisoframes;
+			for(w = 1; w < n; w++){
+				woff = w * Nisoframes;
+				ctlr->frames[woff+frno] = *lp;
+			}
+		}
+		frno = TRUNC(frno+pollival, Nisoframes);
+	}
+	iunlock(ctlr);
+
+	/*
+	 * wakeup anyone waiting for I/O and
+	 * wait to be sure no I/O is in progress in the controller.
+	 * and then wait to be sure episo* is no longer running.
+	 */
+	wakeup(iso);
+	diprint("cancelisoio iso %#p waiting for I/O to cease\n", iso);
+	tsleep(&up->sleep, return0, 0, 5);
+	qlock(iso);
+	qunlock(iso);
+	diprint("cancelisoio iso %#p releasing iso\n", iso);
+
+	frno = iso->td0frno;
+	for(i = 0; i < iso->nframes; i++){
+		if(iso->hs != 0)
+			itdfree(iso->itdps[frno]);
+		else
+			sitdfree(iso->sitdps[frno]);
+		iso->tdps[frno] = nil;
+		frno = TRUNC(frno+pollival, Nisoframes);
+	}
+	free(iso->tdps);
+	iso->tdps = nil;
+	free(iso->data);
+	iso->data = nil;
+}
+
+static void
+epclose(Ep *ep)
+{
+	Qio *io;
+	Ctlio *cio;
+	Isoio *iso;
+	Ctlr *ctlr;
+
+	ctlr = ep->hp->aux;
+	deprint("ehci: epclose ep%d.%d\n", ep->dev->nb, ep->nb);
+
+	if(ep->aux == nil)
+		panic("ehci: epclose called with closed ep");
+	switch(ep->ttype){
+	case Tctl:
+		cio = ep->aux;
+		cancelio(ctlr, cio);
+		free(cio->data);
+		cio->data = nil;
+		break;
+	case Tintr:
+	case Tbulk:
+		io = ep->aux;
+		ep->toggle[OREAD] = ep->toggle[OWRITE] = 0;
+		if(ep->mode != OWRITE){
+			cancelio(ctlr, &io[OREAD]);
+			if(io[OREAD].toggle == Tddata1)
+				ep->toggle[OREAD] = 1;
+		}
+		if(ep->mode != OREAD){
+			cancelio(ctlr, &io[OWRITE]);
+			if(io[OWRITE].toggle == Tddata1)
+				ep->toggle[OWRITE] = 1;
+		}
+		break;
+	case Tiso:
+		iso = ep->aux;
+		cancelisoio(ctlr, iso, ep->pollival, ep->load);
+		break;
+		break;
+	default:
+		panic("epclose: bad ttype");
+	}
+	free(ep->aux);
+	ep->aux = nil;
+}
+
+static void
+scanpci(void)
+{
+	static int already = 0;
+	int i;
+	ulong io;
+	Ctlr *ctlr;
+	Pcidev *p;
+	Ecapio *capio;
+
+	if(already)
+		return;
+	already = 1;
+	p = nil;
+	while ((p = pcimatch(p, 0, 0)) != nil) {
+		/*
+		 * Find EHCI controllers (Programming Interface = 0x20).
+		 */
+		if(p->ccrb != Pcibcserial || p->ccru != Pciscusb)
+			continue;
+		switch(p->ccrp){
+		case 0x20:
+			io = p->mem[0].bar & ~0x0f;
+			break;
+		default:
+			continue;
+		}
+		if(io == 0){
+			print("usbehci: %x %x: failed to map registers\n",
+				p->vid, p->did);
+			continue;
+		}
+		if(p->intl == 0xff || p->intl == 0) {
+			print("usbehci: no irq assigned for port %#ulx\n", io);
+			continue;
+		}
+		dprint("usbehci: %#x %#x: port %#ulx size %#x irq %d\n",
+			p->vid, p->did, io, p->mem[0].size, p->intl);
+
+		ctlr = mallocz(sizeof(Ctlr), 1);
+		ctlr->pcidev = p;
+		capio = ctlr->capio = vmap(io, p->mem[0].size);
+		ctlr->opio = (Eopio*)((uintptr)capio + (capio->cap & 0xff));
+		pcisetbme(p);
+		pcisetpms(p, 0);
+		for(i = 0; i < Nhcis; i++)
+			if(ctlrs[i] == nil){
+				ctlrs[i] = ctlr;
+				break;
+			}
+		if(i == Nhcis)
+			print("ehci: bug: no more controllers\n");
+	}
+}
+
+/*
+ * return smallest power of 2 >= n
+ */
+static int
+flog2(int n)
+{
+	int i;
+
+	for(i = 0; (1 << i) < n; i++)
+		;
+	return i;
+}
+
+/*
+ * build the periodic scheduling tree:
+ * framesize must be a multiple of the tree size
+ */
+static void
+mkqhtree(Ctlr *ctlr)
+{
+	int i, n, d, o, leaf0, depth;
+	Qh **tree;
+	Qtree *qt;
+	Qh *qh;
+	ulong leafs[Nintrleafs];
+
+	depth = flog2(Nintrleafs);
+	n = (1 << (depth+1)) - 1;
+	qt = mallocz(sizeof(*qt), 1);
+	if(qt == nil)
+		panic("ehci: mkqhtree: no memory");
+	qt->nel = n;
+	qt->depth = depth;
+	qt->bw = mallocz(n * sizeof(qt->bw), 1);
+	qt->root = tree = mallocz(n * sizeof(Qh *), 1);
+	if(qt->bw == nil || tree == nil)
+		panic("ehci: mkqhtree: no memory");
+	for(i = 0; i < n; i++){
+		qh = tree[i] = edalloc();
+		if(qh == nil)
+			panic("ehci: mkqhtree: no memory");
+		qh->nlink = qh->alink = qh->link = Lterm;
+		qh->csw = Tdhalt;
+		qh->state = Qidle;
+		if(i > 0)
+			qhlinkqh(tree[i], tree[(i-1)/2]);
+	}
+	ctlr->ntree = i;
+	dprint("ehci: tree: %d endpoints allocated\n", i);
+
+	/* distribute leaves evenly round the frame list */
+	leaf0 = n / 2;
+	for(i = 0; i < Nintrleafs; i++){
+		o = 0;
+		for(d = 0; d < depth; d++){
+			o <<= 1;
+			if(i & (1 << d))
+				o |= 1;
+		}
+		if(leaf0 + o >= n){
+			print("leaf0=%d o=%d i=%d n=%d\n", leaf0, o, i, n);
+			break;
+		}
+		leafs[i] = PADDR(tree[leaf0 + o]) | Lqh;
+	}
+	assert((ctlr->nframes % Nintrleafs) == 0);
+	for(i = 0; i < ctlr->nframes; i += Nintrleafs)
+		memmove(ctlr->frames + i, leafs, sizeof(leafs));
+	ctlr->tree = qt;
+}
+
+static void
+ehcimeminit(Ctlr *ctlr)
+{
+	int frsize;
+	Eopio *opio;
+	int i;
+
+	opio = ctlr->opio;
+	frsize = ctlr->nframes*sizeof(ulong);
+	assert((frsize & 0xFFF) == 0);		/* must be 4k aligned */
+	ctlr->frames = xspanalloc(frsize, frsize, 0);
+	if(ctlr->frames == nil)
+		panic("ehci reset: no memory\n");
+
+	for (i = 0; i < ctlr->nframes; i++)
+		ctlr->frames[i] = Lterm;
+	opio->frbase = PADDR(ctlr->frames);
+	opio->frno = 0;
+
+	qhalloc(ctlr, nil, nil, nil);	/* init async list */
+	mkqhtree(ctlr);			/* init sync list */
+	edfree(edalloc());		/* try to get some ones pre-allocated */
+
+	dprint("ehci %#p flb %#ulx frno %#ulx\n",
+		ctlr->capio, opio->frbase, opio->frno);
+}
+
+static void
+init(Hci *hp)
+{
+	Ctlr *ctlr;
+	Eopio *opio;
+	int i;
+
+	hp->highspeed = 1;
+	ctlr = hp->aux;
+	opio = ctlr->opio;
+	dprint("ehci %#p init\n", ctlr->capio);
+
+	ilock(ctlr);
+	/*
+	 * Unless we activate frroll interrupt
+	 * some machines won't post other interrupts.
+	 */
+	opio->intr = Iusb|Ierr|Iportchg|Ihcerr|Iasync;
+	opio->config = Callmine;	/* reclaim all ports */
+	opio->cmd |= Cpse;
+	opio->cmd |= Case;
+	ehcirun(ctlr, 1);
+
+	for (i = 0; i < hp->nports; i++)
+		opio->portsc[i] = Pspower;
+	iunlock(ctlr);
+
+	if(debug > 1)
+		dump(hp);
+
+}
+
+static void
+ehcireset(Ctlr *ctlr)
+{
+	Eopio *opio;
+	int i;
+
+	ilock(ctlr);
+	dprint("ehci %#p reset\n", ctlr->capio);
+
+	/*
+	 * Turn off legacy mode. Some controllers won't
+	 * interrupt us as expected otherwise.
+	 */
+	ehcirun(ctlr, 0);
+	pcicfgw16(ctlr->pcidev, 0xc0, 0x2000);
+
+	/* clear high 32 bits of address signals if it's 64 bits capable.
+	 * This is probably not needed but it does not hurt and others do it.
+	 */
+	if((ctlr->capio->capparms & C64) != 0){
+		dprint("ehci: 64 bits\n");
+		ctlr->opio->seg = 0;
+	}
+
+	opio = ctlr->opio;
+	opio->cmd |= Chcreset;	/* controller reset */
+	for(i = 0; i < 100; i++){
+		if((opio->cmd & Chcreset) == 0)
+			break;
+		delay(1);
+	}
+	if(i == 100)
+		print("ehci %#p controller reset timed out\n", ctlr->capio);
+
+	/* requesting more interrupts per µframe may miss interrupts */
+	opio->cmd |= Citc8;	/* 1 intr. per ms */
+	switch(opio->cmd & Cflsmask){
+	case Cfls1024:
+		ctlr->nframes = 1024;
+		break;
+	case Cfls512:
+		ctlr->nframes = 512;
+		break;
+	case Cfls256:
+		ctlr->nframes = 256;
+		break;
+	default:
+		panic("ehci: unknown fls %d\n", opio->cmd & Cflsmask);
+	}
+	dprint("ehci: %d frames\n", ctlr->nframes);
+	iunlock(ctlr);
+}
+
+static void
+setdebug(Hci*, int d)
+{
+	debug = d;
+}
+
+static int
+reset(Hci *hp)
+{
+	static Lock resetlck;
+	int i;
+	Ctlr *ctlr;
+	Ecapio *capio;
+	Pcidev *p;
+
+	if(getconf("*nousbehci"))
+		return -1;
+	ilock(&resetlck);
+	scanpci();
+
+	/*
+	 * Any adapter matches if no hp->port is supplied,
+	 * otherwise the ports must match.
+	 */
+	ctlr = nil;
+	for(i = 0; i < Nhcis && ctlrs[i] != nil; i++){
+		ctlr = ctlrs[i];
+		if(ctlr->active == 0)
+		if(hp->port == 0 || hp->port == (uintptr)ctlr->capio){
+			ctlr->active = 1;
+			break;
+		}
+	}
+	iunlock(&resetlck);
+	if(ctlrs[i] == nil || i == Nhcis)
+		return -1;
+
+	p = ctlr->pcidev;
+	hp->aux = ctlr;
+	hp->port = (uintptr)ctlr->capio;
+	hp->irq = p->intl;
+	hp->tbdf = p->tbdf;
+
+	capio = ctlr->capio;
+	hp->nports = capio->parms & Cnports;
+
+	ddprint("echi: %s, ncc %lud npcc %lud\n",
+		capio->parms & 0x10000 ? "leds" : "no leds",
+		(capio->parms >> 12) & 0xf, (capio->parms >> 8) & 0xf);
+	ddprint("ehci: routing %s, %sport power ctl, %d ports\n",
+		capio->parms & 0x40 ? "explicit" : "automatic",
+		capio->parms & 0x10 ? "" : "no ", hp->nports);
+
+	ehcireset(ctlr);
+	ehcimeminit(ctlr);
+
+	/*
+	 * Linkage to the generic HCI driver.
+	 */
+	hp->init = init;
+	hp->dump = dump;
+	hp->interrupt = interrupt;
+	hp->epopen = epopen;
+	hp->epclose = epclose;
+	hp->epread = epread;
+	hp->epwrite = epwrite;
+	hp->seprintep = seprintep;
+	hp->portenable = portenable;
+	hp->portreset = portreset;
+	hp->portstatus = portstatus;
+	hp->debug = setdebug;
+	hp->type = "ehci";
+	return 0;
+}
+
+void
+usbehcilink(void)
+{
+	addhcitype("ehci", reset);
+}
+

File diff suppressed because it is too large
+ 521 - 855
sys/src/9/pc/usbohci.c


File diff suppressed because it is too large
+ 1834 - 1152
sys/src/9/pc/usbuhci.c


+ 2 - 0
sys/src/boot/pc/dat.h

@@ -209,11 +209,13 @@ struct Boot {
 };
 };
 
 
 extern int	debug;
 extern int	debug;
+extern int	debugload;
 extern Apminfo	apm;
 extern Apminfo	apm;
 extern char	*defaultpartition;
 extern char	*defaultpartition;
 extern int	iniread;
 extern int	iniread;
 extern int	pxe;
 extern int	pxe;
 extern int	vga;
 extern int	vga;
+
 extern int	onlybios0;
 extern int	onlybios0;
 extern int	biosinited;
 extern int	biosinited;
 extern int	biosload;
 extern int	biosload;

+ 2 - 1
sys/src/boot/pc/devbios.c

@@ -140,7 +140,8 @@ biosinit(void)
 		if (lba < 0) {
 		if (lba < 0) {
 			if (devid > 0)
 			if (devid > 0)
 				continue;
 				continue;
-			print("bios call failed; bios loading disabled\n");	
+			if (debugload)
+				print("bios call failed; bios loading disabled\n");	
 			biosload = 0;
 			biosload = 0;
 			return 0;
 			return 0;
 		}
 		}

+ 149 - 173
sys/src/cmd/usb/audio/usbaudio.c → sys/src/cmd/usb/audio/audio.c

@@ -1,13 +1,20 @@
 /*
 /*
  * USB audio driver for Plan 9
  * USB audio driver for Plan 9
+ * This needs a full rewrite.
+ * As it is, it does not check for all errors,
+ * mixes the audio data structures with the usb configuration,
+ * may cross nil pointers, and is hard to debug and fix.
+ * Also, it does not issue a dettach request to the endpoint
+ * after the device is unplugged. This means that the old
+ * endpoint would still be around until manually reclaimed.
  */
  */
 
 
 #include <u.h>
 #include <u.h>
 #include <libc.h>
 #include <libc.h>
 #include <thread.h>
 #include <thread.h>
 #include "usb.h"
 #include "usb.h"
-#include "usbaudio.h"
-#include "usbaudioctl.h"
+#include "audio.h"
+#include "audioctl.h"
 
 
 #define STACKSIZE 16*1024
 #define STACKSIZE 16*1024
 
 
@@ -16,55 +23,48 @@ char * mntpt;
 
 
 Channel *controlchan;
 Channel *controlchan;
 
 
-char audstr[]		= "Enabled 0x000101";	/* audio.control.0 */
-
+int verbose;
 int setrec = 0;
 int setrec = 0;
 int defaultspeed[2] = {44100, 44100};
 int defaultspeed[2] = {44100, 44100};
+Dev *buttondev;
+Dev *epdev[2];
 
 
 static void
 static void
-audio_endpoint(Device *d, int c, ulong csp, void *bb, int n)
+audio_endpoint(Dev *, Desc *dd)
 {
 {
-	int ifc;
-	int dalt;
-	byte *b = bb;
-
-	if (c >= nelem(d->config)) {
-		fprint(2, "Too many interfaces (%d of %d)\n",
-			c, nelem(d->config));
-		return;
-	}
-	dalt=csp>>24;
-	ifc = csp>>16 & 0xff;
+	byte *b = (uchar*)&dd->data;
+	int n = dd->data.bLength;
+	char *hd;
 
 
-	switch(b[2]) {
+	switch(b[2]){
 	case 0x01:
 	case 0x01:
-		if (debug){
+		if(usbdebug){
 			fprint(2, "CS_ENDPOINT for attributes 0x%x, lockdelayunits %d, lockdelay %#ux, ",
 			fprint(2, "CS_ENDPOINT for attributes 0x%x, lockdelayunits %d, lockdelay %#ux, ",
 				b[3], b[4], b[5] | (b[6]<<8));
 				b[3], b[4], b[5] | (b[6]<<8));
-			if (b[3] & has_setspeed)
+			if(b[3] & has_setspeed)
 				fprint(2, "has sampling-frequency control");
 				fprint(2, "has sampling-frequency control");
 			else
 			else
 				fprint(2, "does not have sampling-frequency control");
 				fprint(2, "does not have sampling-frequency control");
-			if (b[3] & 0x1<<1)
+			if(b[3] & 0x1<<1)
 				fprint(2, ", has pitch control");
 				fprint(2, ", has pitch control");
 			else
 			else
 				fprint(2, ", does not have pitch control");
 				fprint(2, ", does not have pitch control");
-			if (b[3] & 0x1<<7)
+			if(b[3] & 0x1<<7)
 				fprint(2, ", max packets only");
 				fprint(2, ", max packets only");
 			fprint(2, "\n");
 			fprint(2, "\n");
 		}
 		}
-		if (d->config[c] == nil)
-			sysfatal("d->config[%d] == nil", c);
-		if (d->config[c]->iface[ifc] == nil)
-			sysfatal("d->config[%d]->iface[%d] == nil", c, ifc);
-		if (d->config[c]->iface[ifc]->dalt[dalt] == nil)
-			d->config[c]->iface[ifc]->dalt[dalt] = mallocz(sizeof(Dalt),1);
-		if (d->config[c]->iface[ifc]->dalt[dalt]->devspec == nil)
-			d->config[c]->iface[ifc]->dalt[dalt]->devspec= mallocz(sizeof(Audioalt),1);
-		((Audioalt*)d->config[c]->iface[ifc]->dalt[dalt]->devspec)->caps |= b[3];
+		if(dd->conf == nil)
+			sysfatal("conf == nil");
+		if(dd->iface == nil)
+			sysfatal("iface == nil");
+		if(dd->altc == nil)
+			sysfatal("alt == nil");
+		if(dd->altc->aux == nil)
+			dd->altc->aux= mallocz(sizeof(Audioalt),1);
+		((Audioalt*)dd->altc->aux)->caps |= b[3];
 		break;
 		break;
 	case 0x02:
 	case 0x02:
-		if (debug){
+		if(usbdebug){
 			fprint(2, "CS_INTERFACE FORMAT_TYPE %d, channels %d, subframesize %d, resolution %d, freqtype %d, ",
 			fprint(2, "CS_INTERFACE FORMAT_TYPE %d, channels %d, subframesize %d, resolution %d, freqtype %d, ",
 				b[3], b[4], b[5], b[6], b[7]);
 				b[3], b[4], b[5], b[6], b[7]);
 			fprint(2, "freq0 %d, freq1 %d\n",
 			fprint(2, "freq0 %d, freq1 %d\n",
@@ -72,18 +72,14 @@ audio_endpoint(Device *d, int c, ulong csp, void *bb, int n)
 		}
 		}
 		break;
 		break;
 	default:
 	default:
-		if (debug) pcs_raw("CS_INTERFACE", bb, n);
+		if(usbdebug){
+			hd = hexstr(b, n);
+			fprint(2, "CS_INTERFACE: %s\n", hd);
+			free(hd);
+		}
 	}
 	}
 }
 }
 
 
-void (*dprinter[])(Device *, int, ulong, void *b, int n) = {
-	[STRING] pstring,
-	[DEVICE] pdevice,
-	[0x21] phid,
-	[0x24] audio_interface,
-	[0x25] audio_endpoint,
-};
-
 enum {
 enum {
 	None,
 	None,
 	Volumeset,
 	Volumeset,
@@ -107,51 +103,49 @@ controlproc(void *)
 		int rec;
 		int rec;
 
 
 		nf = tokenize(req, args, nelem(args));
 		nf = tokenize(req, args, nelem(args));
-		if (nf < 3)
+		if(nf < 3)
 			sysfatal("controlproc: not enough arguments");
 			sysfatal("controlproc: not enough arguments");
 		replchan = (Channel*)strtol(args[0], nil, 0);
 		replchan = (Channel*)strtol(args[0], nil, 0);
-		if (strcmp(args[2], "playback") == 0)
+		if(strcmp(args[2], "playback") == 0)
 			rec = Play;
 			rec = Play;
-		else if (strcmp(args[2], "record") == 0)
+		else if(strcmp(args[2], "record") == 0)
 			rec = Record;
 			rec = Record;
 		else{
 		else{
 			/* illegal request */
 			/* illegal request */
-			if (debug) fprint(2, "%s must be record or playback", args[2]);
-			if (replchan) chanprint(replchan, "%s must be record or playback", args[2]);
+			dprint(2, "%s must be record or playback", args[2]);
+			if(replchan) chanprint(replchan, "%s must be record or playback", args[2]);
 			free(req);
 			free(req);
 			continue;
 			continue;
 		}
 		}
 		c = nil;
 		c = nil;
-		for (i = 0; i < Ncontrol; i++){
+		for(i = 0; i < Ncontrol; i++){
 			c = &controls[rec][i];
 			c = &controls[rec][i];
-			if (strcmp(args[1], c->name) == 0)
+			if(strcmp(args[1], c->name) == 0)
 				break;
 				break;
 		}
 		}
-		if (i == Ncontrol){
-			if (debug) fprint(2, "Illegal control name: %s", args[1]);
-			if (replchan) chanprint(replchan, "Illegal control name: %s", args[1]);
-		}else if (!c->settable){
-			if (debug & Dbginfo) fprint(2, "%s %s is not settable", args[1], args[2]);
-			if (replchan)
+		if(i == Ncontrol){
+			dprint(2, "Illegal control name: %s", args[1]);
+			if(replchan) chanprint(replchan, "Illegal control name: %s", args[1]);
+		}else if(!c->settable){
+			dprint(2, "%s %s is not settable", args[1], args[2]);
+			if(replchan)
 				chanprint(replchan, "%s %s is not settable", args[1], args[2]);
 				chanprint(replchan, "%s %s is not settable", args[1], args[2]);
-		}else if (nf < 4){
-			if (debug & Dbginfo) fprint(2, "insufficient arguments for %s %s",
-					args[1], args[2]);
-			if (replchan)
+		}else if(nf < 4){
+			dprint(2, "insufficient arguments for %s %s", args[1], args[2]);
+			if(replchan)
 				chanprint(replchan, "insufficient arguments for %s %s",
 				chanprint(replchan, "insufficient arguments for %s %s",
 					args[1], args[2]);
 					args[1], args[2]);
-		}else if (ctlparse(args[3], c, value) < 0) {
-			if (replchan)
+		}else if(ctlparse(args[3], c, value) < 0){
+			if(replchan)
 				chanprint(replchan, "parse error in %s %s", args[1], args[2]);
 				chanprint(replchan, "parse error in %s %s", args[1], args[2]);
-		} else {
-			if (debug & Dbginfo)
-				fprint(2, "controlproc: setcontrol %s %s %s\n",
+		}else{
+			dprint(2, "controlproc: setcontrol %s %s %s\n",
 					rec?"in":"out", args[1], args[3]);
 					rec?"in":"out", args[1], args[3]);
-			if (setcontrol(rec, args[1], value) < 0){
-				if (replchan)
+			if(setcontrol(rec, args[1], value) < 0){
+				if(replchan)
 					chanprint(replchan, "setting %s %s failed", args[1], args[2]);
 					chanprint(replchan, "setting %s %s failed", args[1], args[2]);
 			}else{
 			}else{
-				if (replchan) chanprint(replchan, "ok");
+				if(replchan) chanprint(replchan, "ok");
 			}
 			}
 			ctlevent();
 			ctlevent();
 		}
 		}
@@ -163,111 +157,81 @@ void
 buttonproc(void *)
 buttonproc(void *)
 {
 {
 	int	i, fd, b;
 	int	i, fd, b;
-	char fname[64], err[32];
+	char err[32];
 	byte buf[1];
 	byte buf[1];
 	Audiocontrol *c;
 	Audiocontrol *c;
 
 
-	sprint(fname, "/dev/usb%d/%d/ep%ddata", ad->ctlrno, ad->id, buttonendpt);
-	if (debug & Dbginfo) fprint(2, "buttonproc opening %s\n", fname);
-	if ((fd = open(fname, OREAD)) < 0)
-		sysfatal("Can't open %s: %r", fname);
+	fd = buttondev->dfd;
 
 
 	c = &controls[Play][Volume_control];
 	c = &controls[Play][Volume_control];
 	for(;;){
 	for(;;){
 		if((b = read(fd, buf, 1)) < 0){
 		if((b = read(fd, buf, 1)) < 0){
 			rerrstr(err, sizeof err);
 			rerrstr(err, sizeof err);
-			if (strcmp(err, "interrupted") == 0){
-				if (debug & Dbginfo) fprint(2, "read interrupted\n");
+			if(strcmp(err, "interrupted") == 0){
+				dprint(2, "read interrupted\n");
 				continue;
 				continue;
 			}
 			}
-			sysfatal("read %s: %r", fname);
+			sysfatal("read %s/data: %r", buttondev->dir);
 		}
 		}
 		if(b == 0 || buf[0] == 0){
 		if(b == 0 || buf[0] == 0){
 			continue;
 			continue;
 		}else if(buf[0] == 1){
 		}else if(buf[0] == 1){
-			if (c->chans == 0)
+			if(c->chans == 0)
 				c->value[0] += c->step;
 				c->value[0] += c->step;
 			else
 			else
-				for (i = 1; i < 8; i++)
-					if (c->chans & 1 << i)
+				for(i = 1; i < 8; i++)
+					if(c->chans & 1 << i)
 						c->value[i] += c->step;
 						c->value[i] += c->step;
 			chanprint(controlchan, "0 volume playback %A", c);
 			chanprint(controlchan, "0 volume playback %A", c);
 		}else if(buf[0] == 2){
 		}else if(buf[0] == 2){
-			if (c->chans == 0)
+			if(c->chans == 0)
 				c->value[0] -= c->step;
 				c->value[0] -= c->step;
 			else
 			else
-				for (i = 1; i < 8; i++)
-					if (c->chans & 1 << i)
+				for(i = 1; i < 8; i++)
+					if(c->chans & 1 << i)
 						c->value[i] -= c->step;
 						c->value[i] -= c->step;
 			chanprint(controlchan, "0 volume playback %A", c);
 			chanprint(controlchan, "0 volume playback %A", c);
-		}else if(debug & Dbginfo){
+		}else if(usbdebug){
 			fprint(2, "button");
 			fprint(2, "button");
-			for (i = 0; i < b; i++)
+			for(i = 0; i < b; i++)
 				fprint(2, " %#2.2x", buf[i]);
 				fprint(2, " %#2.2x", buf[i]);
 			fprint(2, "\n");
 			fprint(2, "\n");
 		}
 		}
 	}
 	}
 }
 }
 
 
-void
-findendpoints(void)
-{
-	Endpt *ep;
-	int i, rec;
-
-	for (i = 0; i < Nendpt; i++) {
-		if ((ep = ad->ep[i]) == nil)
-			continue;
-		switch(ep->csp){
-		default:
-			break;
-		case CSP(CL_AUDIO, 2, 0):
-			if (ep->iface == nil)
-				break;
-			rec = (ep->addr &  0x80)?1:0;
-			if (verbose)
-				fprint(2, "%s on endpoint %d\n", rec?"Record":"Playback", i);
-			endpt[rec] = i;
-			interface[rec] = ep->iface->interface;
-			break;
-		case CSP(CL_HID, 0, 0):
-			if (verbose)
-				fprint(2, "Buttons on endpoint %d\n", i);
-			buttonendpt = i;
-			break;
-		}
-	}
-}
 
 
 void
 void
 usage(void)
 usage(void)
 {
 {
-	fprint(2, "usage: usbaudio [-pV] [-m mountpoint] [-s srvname] "
-		"[-v volume] [ctrlno n]\n");
+	fprint(2, "usage: usbaudio [-dpV] [-m mountpoint] [-s srvname] "
+		"[-v volume] [dev]\n");
 	threadexitsall("usage");
 	threadexitsall("usage");
 }
 }
 
 
 void
 void
 threadmain(int argc, char **argv)
 threadmain(int argc, char **argv)
 {
 {
-	int ctlrno, id, i, sfd;
+	char *devdir;
+	int i;
 	long value[8], volume[8];
 	long value[8], volume[8];
 	Audiocontrol *c;
 	Audiocontrol *c;
-	char buf[32], *p, line[256];
+	char *p;
 	extern int attachok;
 	extern int attachok;
+	Ep *ep;
+	int csps[] = { Audiocsp, 0};
 
 
-	ctlrno = id = -1;
+	devdir = nil;
 	volume[0] = Undef;
 	volume[0] = Undef;
-	for (i = 0; i<8; i++)
+	for(i = 0; i<8; i++)
 		value[i] = 0;
 		value[i] = 0;
 	fmtinstall('A', Aconv);
 	fmtinstall('A', Aconv);
+	fmtinstall('U', Ufmt);
 	quotefmtinstall();
 	quotefmtinstall();
 
 
 	ARGBEGIN{
 	ARGBEGIN{
 	case 'd':
 	case 'd':
-		debug = strtol(EARGF(usage()), nil, 0);
-		if (debug == -1)
-			debugdebug++;
+		usbdebug++;
 		verbose++;
 		verbose++;
 		break;
 		break;
 	case 'm':
 	case 'm':
@@ -290,48 +254,60 @@ threadmain(int argc, char **argv)
 	default:
 	default:
 		usage();
 		usage();
 	}ARGEND
 	}ARGEND
-
 	switch(argc){
 	switch(argc){
 	case 0:
 	case 0:
-		for (ctlrno = 0; ctlrno < 16; ctlrno++) {
-			for (i = 1; i < 128; i++) {
-				sprint(buf, "/dev/usb%d/%d/status", ctlrno, i);
-				sfd = open(buf, OREAD);
-				if (sfd < 0)
-					break;
-				if (read(sfd, line, strlen(audstr)) == strlen(audstr)
-				 && strncmp(audstr, line, strlen(audstr)) == 0) {
-					id = i;
-					goto found;
-				}
-				close(sfd);
-			}
-		}
-		if (verbose) fprint(2, "No usb audio\n");
-		threadexitsall("usbaudio not found");
-	found:
 		break;
 		break;
-	case 2:
-		ctlrno = atoi(argv[0]);
-		id = atoi(argv[1]);
+	case 1:
+		devdir = argv[0];
 		break;
 		break;
 	default:
 	default:
 		usage();
 		usage();
 	}
 	}
-
-	ad = opendev(ctlrno, id);
-	if (describedevice(ad) < 0)
-		sysfatal("describedevice");
-
-	for(i=0; i<ad->nconf; i++) {
-		if (ad->config[i] == nil)
-			ad->config[i] = mallocz(sizeof(*ad->config[i]),1);
-		loadconfig(ad, i);
-	}
+	if(devdir == nil)
+		if(finddevs(matchdevcsp, csps, &devdir, 1) < 1){
+			fprint(2, "No usb audio\n");
+			threadexitsall("usbaudio not found");
+		}
+	ad = opendev(devdir);
+	if(ad == nil)
+		sysfatal("opendev: %r");
+	if(configdev(ad) < 0)
+		sysfatal("configdev: %r");
+	
+	for(i = 0; i < nelem(ad->usb->ddesc); i++)
+		if(ad->usb->ddesc[i] != nil)
+		switch(ad->usb->ddesc[i]->data.bDescriptorType){
+		case AUDIO_INTERFACE:
+			audio_interface(ad, ad->usb->ddesc[i]);
+			break;
+		case AUDIO_ENDPOINT:
+			audio_endpoint(ad, ad->usb->ddesc[i]);
+			break;
+		}
 
 
 	controlchan = chancreate(sizeof(char*), 8);
 	controlchan = chancreate(sizeof(char*), 8);
 
 
-	findendpoints();
+	for(i = 0; i < nelem(ad->usb->ep); i++)
+		if((ep = ad->usb->ep[i]) != nil){
+			if(ep->iface->csp == CSP(Claudio, 2, 0) && ep->dir == Eout)
+				endpt[0] = ep->id;
+			if(ep->iface->csp == CSP(Claudio, 2, 0) && ep->dir == Ein)
+				endpt[1] = ep->id;
+			if(buttonendpt<0 && Class(ep->iface->csp) == Clhid)
+				buttonendpt = ep->id;
+		}
+	if(endpt[0] != -1){
+		if(verbose)
+			fprint(2, "usb/audio: playback on ep %d\n", endpt[0]);
+		interface[0] = ad->usb->ep[endpt[0]]->iface->id;
+	}
+	if(endpt[1] != -1){
+		if(verbose)
+			fprint(2, "usb/audio: record on ep %d\n", endpt[0]);
+		interface[1] = ad->usb->ep[endpt[1]]->iface->id;
+	}
+	if(verbose && buttonendpt >= 0)
+		fprint(2, "usb/audio: buttons on ep %d\n", buttonendpt);
 
 
 	if(endpt[Play] >= 0){
 	if(endpt[Play] >= 0){
 		if(verbose)
 		if(verbose)
@@ -345,10 +321,10 @@ threadmain(int argc, char **argv)
 			defaultspeed[Play] = 48000;
 			defaultspeed[Play] = 48000;
 		}
 		}
 		value[0] = 2;
 		value[0] = 2;
-		if (setcontrol(Play, "channels", value) == Undef)
+		if(setcontrol(Play, "channels", value) == Undef)
 			sysfatal("Can't set play channels");
 			sysfatal("Can't set play channels");
 		value[0] = 16;
 		value[0] = 16;
-		if (setcontrol(Play, "resolution", value) == Undef)
+		if(setcontrol(Play, "resolution", value) == Undef)
 			sysfatal("Can't set play resolution");
 			sysfatal("Can't set play resolution");
 	}
 	}
 
 
@@ -364,57 +340,57 @@ threadmain(int argc, char **argv)
 				fprint(2, "Warning, can't configure stereo "
 				fprint(2, "Warning, can't configure stereo "
 					"recording, configuring mono instead\n");
 					"recording, configuring mono instead\n");
 				i = 1;
 				i = 1;
-			} else
+			}else
 				break;
 				break;
-		if(findalt(Record, i, 16, 48000) < 0) {
+		if(findalt(Record, i, 16, 48000) < 0){
 			endpt[Record] = -1;	/* disable recording */
 			endpt[Record] = -1;	/* disable recording */
 			setrec = 0;
 			setrec = 0;
 			fprint(2, "Warning, can't configure record for %d Hz or %d Hz\n",
 			fprint(2, "Warning, can't configure record for %d Hz or %d Hz\n",
 				defaultspeed[Record], 48000);
 				defaultspeed[Record], 48000);
-		} else
+		}else
 			fprint(2, "Warning, can't configure record for %d Hz, "
 			fprint(2, "Warning, can't configure record for %d Hz, "
 				"configuring for %d Hz instead\n",
 				"configuring for %d Hz instead\n",
 				defaultspeed[Record], 48000);
 				defaultspeed[Record], 48000);
 		defaultspeed[Record] = 48000;
 		defaultspeed[Record] = 48000;
-		if (setrec) {
+		if(setrec){
 			value[0] = i;
 			value[0] = i;
-			if (setcontrol(Record, "channels", value) == Undef)
+			if(setcontrol(Record, "channels", value) == Undef)
 				sysfatal("Can't set record channels");
 				sysfatal("Can't set record channels");
 			value[0] = 16;
 			value[0] = 16;
-			if (setcontrol(Record, "resolution", value) == Undef)
+			if(setcontrol(Record, "resolution", value) == Undef)
 				sysfatal("Can't set record resolution");
 				sysfatal("Can't set record resolution");
 		}
 		}
 	}
 	}
 
 
 	getcontrols();	/* Get the initial value of all controls */
 	getcontrols();	/* Get the initial value of all controls */
 	value[0] = defaultspeed[Play];
 	value[0] = defaultspeed[Play];
-	if (endpt[Play] >= 0 && setcontrol(Play, "speed", value) < 0)
+	if(endpt[Play] >= 0 && setcontrol(Play, "speed", value) < 0)
 		sysfatal("can't set play speed");
 		sysfatal("can't set play speed");
 	value[0] = defaultspeed[Record];
 	value[0] = defaultspeed[Record];
-	if (endpt[Record] >= 0 && setcontrol(Record, "speed", value) < 0)
+	if(endpt[Record] >= 0 && setcontrol(Record, "speed", value) < 0)
 		fprint(2, "%s: can't set record speed\n", argv0);
 		fprint(2, "%s: can't set record speed\n", argv0);
 	value[0] = 0;
 	value[0] = 0;
 	setcontrol(Play, "mute", value);
 	setcontrol(Play, "mute", value);
 
 
-	if (volume[0] != Undef){
+	if(volume[0] != Undef){
 		c = &controls[Play][Volume_control];
 		c = &controls[Play][Volume_control];
-		if (*p == '%' && c->min != Undef)
-			for (i = 0; i < 8; i++)
+		if(*p == '%' && c->min != Undef)
+			for(i = 0; i < 8; i++)
 				volume[i] = (volume[i]*c->max + (100-volume[i])*c->min)/100;
 				volume[i] = (volume[i]*c->max + (100-volume[i])*c->min)/100;
-		if (c->settable)
+		if(c->settable)
 			setcontrol(Play, "volume", volume);
 			setcontrol(Play, "volume", volume);
 		c = &controls[Record][Volume_control];
 		c = &controls[Record][Volume_control];
-		if (c->settable && setrec)
+		if(c->settable && setrec)
 			setcontrol(Record, "volume", volume);
 			setcontrol(Record, "volume", volume);
 	}
 	}
 
 
-	if (buttonendpt > 0){
-		sprint(buf, "ep %d 10 r 1", buttonendpt);
-		if (debug) fprint(2, "sending `%s' to /dev/usb/%d/ctl\n", buf, id);
-		if (write(ad->ctl, buf, strlen(buf)) > 0)
-			proccreate(buttonproc, nil, STACKSIZE);
-		else
-			fprint(2, "Could not configure button endpoint: %r\n");
+	if(buttonendpt > 0){
+		buttondev = openep(ad, buttonendpt);
+		if(buttondev == nil)
+			sysfatal("openep: buttons: %r");
+		if(opendevdata(buttondev, OREAD) < 0)
+			sysfatal("open buttons fd: %r");
+		proccreate(buttonproc, nil, STACKSIZE);
 	}
 	}
 	proccreate(controlproc, nil, STACKSIZE);
 	proccreate(controlproc, nil, STACKSIZE);
 	proccreate(serve, nil, STACKSIZE);
 	proccreate(serve, nil, STACKSIZE);

+ 14 - 4
sys/src/cmd/usb/audio/usbaudio.h → sys/src/cmd/usb/audio/audio.h

@@ -19,6 +19,11 @@ enum {
 	Selector_control	= 0x0d,
 	Selector_control	= 0x0d,
 
 
 	sampling_freq_control	= 0x01,
 	sampling_freq_control	= 0x01,
+
+	Audiocsp = 0x000101, /* audio.control.0 */
+
+	AUDIO_INTERFACE = 0x24,
+	AUDIO_ENDPOINT = 0x25,
 };
 };
 
 
 
 
@@ -55,14 +60,19 @@ enum {
 	maxpkt_only = 0x80,	/* packets must be padded to max size */
 	maxpkt_only = 0x80,	/* packets must be padded to max size */
 };
 };
 
 
+typedef uchar byte;
+
 extern int setrec;
 extern int setrec;
+extern int verbose;
 extern int defaultspeed[2];
 extern int defaultspeed[2];
-extern Device *ad;
+extern Dev *ad;
+extern Dev *buttondev;
 extern Channel *controlchan;
 extern Channel *controlchan;
+extern Dev *epdev[2];
 
 
-void	audio_interface(Device *d, int n, ulong csp, void *bb, int nb);
-void	setalt(Device *d, int endpt, int value);
-int	getalt(Device *d, int endpt);
+void	audio_interface(Dev *d, Desc *dd);
+void	setalt(Dev *d, int endpt, int value);
+int	getalt(Dev *d, int endpt);
 int	setspeed(int rec, int speed);
 int	setspeed(int rec, int speed);
 int	setcontrol(int rec, char *name, long *value);
 int	setcontrol(int rec, char *name, long *value);
 int	getspecialcontrol(int rec, int ctl, int req, long *value);
 int	getspecialcontrol(int rec, int ctl, int req, long *value);

+ 205 - 231
sys/src/cmd/usb/audio/usbaudioctl.c → sys/src/cmd/usb/audio/audioctl.c

@@ -2,8 +2,8 @@
 #include <libc.h>
 #include <libc.h>
 #include <thread.h>
 #include <thread.h>
 #include "usb.h"
 #include "usb.h"
-#include "usbaudio.h"
-#include "usbaudioctl.h"
+#include "audio.h"
+#include "audioctl.h"
 
 
 int endpt[2] =		{-1, -1};
 int endpt[2] =		{-1, -1};
 int interface[2] =	{-1, -1};
 int interface[2] =	{-1, -1};
@@ -14,7 +14,7 @@ int curalt[2] =		{-1, -1};
 int buttonendpt =	-1;
 int buttonendpt =	-1;
 
 
 int id;
 int id;
-Device *ad;
+Dev *ad;
 
 
 Audiocontrol controls[2][Ncontrol] = {
 Audiocontrol controls[2][Ncontrol] = {
 	{
 	{
@@ -53,12 +53,11 @@ Audiocontrol controls[2][Ncontrol] = {
 int
 int
 setaudioalt(int rec, Audiocontrol *c, int control)
 setaudioalt(int rec, Audiocontrol *c, int control)
 {
 {
-	if (debug & Dbgcontrol)
-		fprint(2, "setcontrol %s: Set alt %d\n", c->name, control);
+	dprint(2, "setcontrol %s: Set alt %d\n", c->name, control);
 	curalt[rec] = control;
 	curalt[rec] = control;
-	if (setupcmd(ad->ep[0], RH2D|Rstandard|Rinterface, SET_INTERFACE, control, interface[rec], nil, 0) < 0){
-		if (debug & Dbgcontrol) fprint(2, "setcontrol: setupcmd %s failed\n", c->name);
-			return -1;
+	if(usbcmd(ad, Rh2d|Rstd|Riface, Rsetiface, control, interface[rec], nil, 0) < 0){
+		dprint(2, "setcontrol: setupcmd %s failed\n", c->name);
+		return -1;
 	}
 	}
 	return control;
 	return control;
 }
 }
@@ -66,9 +65,9 @@ setaudioalt(int rec, Audiocontrol *c, int control)
 int
 int
 findalt(int rec, int nchan, int res, int speed)
 findalt(int rec, int nchan, int res, int speed)
 {
 {
-	Endpt *ep;
+	Ep *ep;
 	Audioalt *a;
 	Audioalt *a;
-	Dalt *da;
+	Altc *da;
 	int i, j, k, retval;
 	int i, j, k, retval;
 
 
 	retval = -1;
 	retval = -1;
@@ -78,28 +77,28 @@ findalt(int rec, int nchan, int res, int speed)
 	controls[rec][Resolution_control].min = 1000000;
 	controls[rec][Resolution_control].min = 1000000;
 	controls[rec][Resolution_control].max = 0;
 	controls[rec][Resolution_control].max = 0;
 	controls[rec][Resolution_control].step = Undef;
 	controls[rec][Resolution_control].step = Undef;
-	for (i = 0; i < Nendpt; i++) {
-		if ((ep = ad->ep[i]) == nil)
+	for(i = 0; i < nelem(ad->usb->ep); i++){
+		if((ep = ad->usb->ep[i]) == nil)
 			continue;
 			continue;
-		if(ep->csp != CSP(CL_AUDIO, 2, 0))
-			continue;
-		if (ep->iface == nil) {
+		if(ep->iface == nil){
 			fprint(2, "\tno interface\n");
 			fprint(2, "\tno interface\n");
 			return 0;
 			return 0;
 		}
 		}
-		if ((rec == Play && (ep->addr &  0x80))
+		if(ep->iface->csp != CSP(Claudio, 2, 0))
+			continue;
+		if((rec == Play && (ep->addr &  0x80))
 		|| (rec == Record && (ep->addr &  0x80) == 0))
 		|| (rec == Record && (ep->addr &  0x80) == 0))
 			continue;
 			continue;
-		for (j = 0; j < 16; j++) {
-			if ((da = ep->iface->dalt[j]) == nil || (a = da->devspec) == nil)
+		for(j = 0; j < 16; j++){
+			if((da = ep->iface->altc[j]) == nil || (a = da->aux) == nil)
 				continue;
 				continue;
-			if (a->nchan < controls[rec][Channel_control].min)
+			if(a->nchan < controls[rec][Channel_control].min)
 				controls[rec][Channel_control].min = a->nchan;
 				controls[rec][Channel_control].min = a->nchan;
-			if (a->nchan > controls[rec][Channel_control].max)
+			if(a->nchan > controls[rec][Channel_control].max)
 				controls[rec][Channel_control].max = a->nchan;
 				controls[rec][Channel_control].max = a->nchan;
-			if (a->res < controls[rec][Resolution_control].min)
+			if(a->res < controls[rec][Resolution_control].min)
 				controls[rec][Resolution_control].min = a->res;
 				controls[rec][Resolution_control].min = a->res;
-			if (a->res > controls[rec][Resolution_control].max)
+			if(a->res > controls[rec][Resolution_control].max)
 				controls[rec][Resolution_control].max = a->res;
 				controls[rec][Resolution_control].max = a->res;
 			controls[rec][Channel_control].settable = 1;
 			controls[rec][Channel_control].settable = 1;
 			controls[rec][Channel_control].readable = 1;
 			controls[rec][Channel_control].readable = 1;
@@ -107,7 +106,7 @@ findalt(int rec, int nchan, int res, int speed)
 			controls[rec][Resolution_control].readable = 1;
 			controls[rec][Resolution_control].readable = 1;
 			controls[rec][Speed_control].settable = 1;
 			controls[rec][Speed_control].settable = 1;
 			controls[rec][Speed_control].readable = 1;
 			controls[rec][Speed_control].readable = 1;
-			if (a->nchan == nchan && a->res == res){
+			if(a->nchan == nchan && a->res == res){
 				if(speed == Undef)
 				if(speed == Undef)
 					retval = j;
 					retval = j;
 				else if(a->caps & (has_discfreq|onefreq)){
 				else if(a->caps & (has_discfreq|onefreq)){
@@ -117,16 +116,15 @@ findalt(int rec, int nchan, int res, int speed)
 							break;
 							break;
 						}
 						}
 					}
 					}
-				} else {
+				}else{
 					if(speed >= a->minfreq && speed <= a->maxfreq)
 					if(speed >= a->minfreq && speed <= a->maxfreq)
 						retval = j;
 						retval = j;
 				}
 				}
 			}
 			}
 		}
 		}
 	}
 	}
-	if ((debug & Dbgcontrol) && retval < 0){
+	if(usbdebug && retval < 0)
 		fprint(2, "findalt(%d, %d, %d, %d) failed\n", rec, nchan, res, speed);
 		fprint(2, "findalt(%d, %d, %d, %d) failed\n", rec, nchan, res, speed);
-	}
 	return retval;
 	return retval;
 }
 }
 
 
@@ -135,100 +133,87 @@ setspeed(int rec, int speed)
 {
 {
 	int ps, n, no, dist, i;
 	int ps, n, no, dist, i;
 	Audioalt *a;
 	Audioalt *a;
-	Dalt *da;
-	Endpt *ep;
-	char cmdbuf[32];
+	Altc *da;
+	Ep *ep;
 	uchar buf[3];
 	uchar buf[3];
 
 
-	if (rec == Record && !setrec)
+	if(rec == Record && !setrec)
 		return Undef;
 		return Undef;
-	if (curalt[rec] < 0){
+	if(curalt[rec] < 0){
 		fprint(2, "Must set channels and resolution before speed\n");
 		fprint(2, "Must set channels and resolution before speed\n");
 		return Undef;
 		return Undef;
 	}
 	}
-	if (endpt[rec] < 0)
+	if(endpt[rec] < 0)
 		sysfatal("endpt[%s] not set", rec?"Record":"Playback");
 		sysfatal("endpt[%s] not set", rec?"Record":"Playback");
-	ep = ad->ep[endpt[rec]];
-	if (ep->iface == nil)
+	ep = ad->usb->ep[endpt[rec]];
+	if(ep->iface == nil)
 		sysfatal("no interface");
 		sysfatal("no interface");
-	if (curalt[rec] < 0)
+	if(curalt[rec] < 0)
 		sysfatal("curalt[%s] not set", rec?"Record":"Playback");
 		sysfatal("curalt[%s] not set", rec?"Record":"Playback");
-	da = ep->iface->dalt[curalt[rec]];
-	a = da->devspec;
-	if (a->caps & onefreq){
-		if (debug & Dbgcontrol)
-			fprint(2, "setspeed %d: onefreq\n", speed);
+	da = ep->iface->altc[curalt[rec]];
+	a = da->aux;
+	if(a->caps & onefreq){
+		dprint(2, "setspeed %d: onefreq\n", speed);
 		/* speed not settable, but packet size must still be set */
 		/* speed not settable, but packet size must still be set */
 		speed = a->freqs[0];
 		speed = a->freqs[0];
-	}else if (a->caps & has_contfreq){
-		if (debug & Dbgcontrol)
-			fprint(2, "setspeed %d: contfreq\n", speed);
-		if (speed < a->minfreq)
+	}else if(a->caps & has_contfreq){
+		dprint(2, "setspeed %d: contfreq\n", speed);
+		if(speed < a->minfreq)
 			speed = a->minfreq;
 			speed = a->minfreq;
-		else if (speed > a->maxfreq)
+		else if(speed > a->maxfreq)
 			speed = a->maxfreq;
 			speed = a->maxfreq;
-		if (debug & Dbgcontrol)
-			fprint(2, "Setting continuously variable %s speed to %d\n",
+		dprint(2, "Setting continuously variable %s speed to %d\n",
 				rec?"record":"playback", speed);
 				rec?"record":"playback", speed);
-	}else if (a->caps & has_discfreq){
-		if (debug & Dbgcontrol)
-			fprint(2, "setspeed %d: discfreq\n", speed);
+	}else if(a->caps & has_discfreq){
+		dprint(2, "setspeed %d: discfreq\n", speed);
 		dist = 1000000;
 		dist = 1000000;
 		no = -1;
 		no = -1;
-		for (i = 0; a->freqs[i] > 0; i++)
-			if (abs(a->freqs[i] - speed) < dist){
+		for(i = 0; a->freqs[i] > 0; i++)
+			if(abs(a->freqs[i] - speed) < dist){
 				dist = abs(a->freqs[i] - speed);
 				dist = abs(a->freqs[i] - speed);
 				no = i;
 				no = i;
 			}
 			}
-		if (no == -1){
-			if (debug & Dbgcontrol)
-				fprint(2, "no = -1\n");
+		if(no == -1){
+			dprint(2, "no = -1\n");
 			return Undef;
 			return Undef;
 		}
 		}
 		speed = a->freqs[no];
 		speed = a->freqs[no];
-		if (debug & Dbgcontrol)
-			fprint(2, "Setting discreetly variable %s speed to %d\n",
+		dprint(2, "Setting discreetly variable %s speed to %d\n",
 				rec?"record":"playback", speed);
 				rec?"record":"playback", speed);
 	}else{
 	}else{
-		if (debug & Dbgcontrol)
-			fprint(2, "can't happen\n?");
+		dprint(2, "can't happen\n?");
 		return Undef;
 		return Undef;
 	}
 	}
-	if (a->caps & has_setspeed){
-		if (debug & Dbgcontrol)
-			fprint(2, "Setting %s speed to %d Hz;", rec?"record":"playback", speed);
+	if(a->caps & has_setspeed){
+		dprint(2, "Setting %s speed to %d Hz;", rec?"record":"playback", speed);
 		buf[0] = speed;
 		buf[0] = speed;
 		buf[1] = speed >> 8;
 		buf[1] = speed >> 8;
 		buf[2] = speed >> 16;
 		buf[2] = speed >> 16;
 		n = endpt[rec];
 		n = endpt[rec];
-		if (rec)
+		if(rec)
 			n |= 0x80;
 			n |= 0x80;
-		if(setupcmd(ad->ep[0], RH2D|Rclass|Rendpt, SET_CUR, sampling_freq_control<<8, n, buf, 3) < 0){
+		if(usbcmd(ad, Rh2d|Rclass|Rep, Rsetcur, sampling_freq_control<<8, n, buf, 3) < 0){
 			fprint(2, "Error in setupcmd\n");
 			fprint(2, "Error in setupcmd\n");
 			return Undef;
 			return Undef;
 		}
 		}
-		if (setupreq(ad->ep[0], RD2H|Rclass|Rendpt, GET_CUR, sampling_freq_control<<8, n, 3) < 0){
+		if((n=usbcmd(ad, Rd2h|Rclass|Rep, Rgetcur, sampling_freq_control<<8, n, buf, 3)) < 0){
 			fprint(2, "Error in setupreq\n");
 			fprint(2, "Error in setupreq\n");
 			return Undef;
 			return Undef;
 		}
 		}
-		n = setupreply(ad->ep[0], buf, 3);
-		if (n != 3)
+		if(n != 3)
 			fprint(2, "Error in setupreply: %d\n", n);
 			fprint(2, "Error in setupreply: %d\n", n);
 		else{
 		else{
 			n = buf[0] | buf[1] << 8 | buf[2] << 16;
 			n = buf[0] | buf[1] << 8 | buf[2] << 16;
-			if (buf[2] || n == 0){
-				if (debug & Dbgcontrol)
-					fprint(2, "Speed out of bounds %d (0x%x)\n", n, n);
-			}else if (n != speed && ad->vid == 0x077d &&
-			    (ad->did == 0x0223 || ad->did == 0x07af)){
+			if(buf[2] || n == 0){
+				dprint(2, "Speed out of bounds %d (0x%x)\n", n, n);
+			}else if(n != speed && ad->usb->vid == 0x077d &&
+			    (ad->usb->did == 0x0223 || ad->usb->did == 0x07af)){
 				/* Griffin iMic responds incorrectly to sample rate inquiry */
 				/* Griffin iMic responds incorrectly to sample rate inquiry */
-				if (debug & Dbgcontrol)
-					fprint(2, " reported as %d (iMic bug?);", n);
+				dprint(2, " reported as %d (iMic bug?);", n);
 			}else
 			}else
 				speed = n;
 				speed = n;
 		}
 		}
-		if (debug & Dbgcontrol)
-			fprint(2, " speed now %d Hz;", speed);
+		dprint(2, " speed now %d Hz;", speed);
 	}
 	}
 	ps = ((speed * da->interval + 999) / 1000)
 	ps = ((speed * da->interval + 999) / 1000)
 		* controls[rec][Channel_control].value[0]
 		* controls[rec][Channel_control].value[0]
@@ -239,18 +224,21 @@ setspeed(int rec, int speed)
 			argv0, rec, speed, ps, ep->maxpkt);
 			argv0, rec, speed, ps, ep->maxpkt);
 		return Undef;
 		return Undef;
 	}
 	}
-	if (debug & Dbgcontrol)
-		fprint(2, "Configuring %s endpoint for %d Hz\n",
+	dprint(2, "Configuring %s endpoint for %d Hz\n",
 				rec?"record":"playback", speed);
 				rec?"record":"playback", speed);
-	sprint(cmdbuf, "ep %d %d %c %ld %d", endpt[rec], da->interval, rec?'r':'w',
-		controls[rec][Channel_control].value[0] *
-		controls[rec][Resolution_control].value[0]/8, speed);
-	if (write(ad->ctl, cmdbuf, strlen(cmdbuf)) != strlen(cmdbuf)){
-		fprint(2, "writing %s to #U/usb/%d/ctl: %r\n", cmdbuf, id);
-		return Undef;
-	}
-	if (debug & Dbgcontrol)
-		fprint(2, "sent `%s' to /dev/usb/%d/ctl\n", cmdbuf, id);
+	epdev[rec] = openep(ad, endpt[rec]);
+	if(epdev[rec] == nil)
+		sysfatal("openep rec %d: %r", rec);
+
+	devctl(epdev[rec], "pollival %d", da->interval);
+	devctl(epdev[rec], "samplesz %ld", controls[rec][Channel_control].value[0] *
+				controls[rec][Resolution_control].value[0]/8);
+	devctl(epdev[rec], "hz %d", speed);
+
+	/* NO: the client uses the endpoint file directly
+	if(opendevdata(epdev[rec], rec ? OREAD : OWRITE) < 0)
+		sysfatal("openep rec %d: %r", rec);
+	*/
 	return speed;
 	return speed;
 }
 }
 
 
@@ -259,81 +247,74 @@ getspeed(int rec, int which)
 {
 {
 	int i, n;
 	int i, n;
 	Audioalt *a;
 	Audioalt *a;
-	Dalt *da;
-	Endpt *ep;
+	Altc *da;
+	Ep *ep;
 	uchar buf[3];
 	uchar buf[3];
+	int r;
 
 
-	if (curalt[rec] < 0){
+	if(curalt[rec] < 0){
 		fprint(2, "Must set channels and resolution before getspeed\n");
 		fprint(2, "Must set channels and resolution before getspeed\n");
 		return Undef;
 		return Undef;
 	}
 	}
-	if (endpt[rec] < 0)
+	if(endpt[rec] < 0)
 		sysfatal("endpt[%s] not set", rec?"Record":"Playback");
 		sysfatal("endpt[%s] not set", rec?"Record":"Playback");
-	if(debug & Dbgcontrol)
-		fprint(2, "getspeed: endpt[%d] == %d\n", rec, endpt[rec]);
-	ep = ad->ep[endpt[rec]];
-	if (ep->iface == nil)
+	dprint(2, "getspeed: endpt[%d] == %d\n", rec, endpt[rec]);
+	ep = ad->usb->ep[endpt[rec]];
+	if(ep->iface == nil)
 		sysfatal("no interface");
 		sysfatal("no interface");
-	if (curalt[rec] < 0)
+	if(curalt[rec] < 0)
 		sysfatal("curalt[%s] not set", rec?"Record":"Playback");
 		sysfatal("curalt[%s] not set", rec?"Record":"Playback");
-	da = ep->iface->dalt[curalt[rec]];
-	a = da->devspec;
-	if (a->caps & onefreq){
-		if(debug & Dbgcontrol)
-			fprint(2, "getspeed: onefreq\n");
-		if (which == GET_RES)
+	da = ep->iface->altc[curalt[rec]];
+	a = da->aux;
+	if(a->caps & onefreq){
+		dprint(2, "getspeed: onefreq\n");
+		if(which == Rgetres)
 			return Undef;
 			return Undef;
 		return a->freqs[0];		/* speed not settable */
 		return a->freqs[0];		/* speed not settable */
 	}
 	}
-	if (a->caps & has_setspeed){
-		if(debug & Dbgcontrol)
-			fprint(2, "getspeed: has_setspeed, ask\n");
+	if(a->caps & has_setspeed){
+		dprint(2, "getspeed: has_setspeed, ask\n");
 		n = endpt[rec];
 		n = endpt[rec];
-		if (rec)
+		if(rec)
 			n |= 0x80;
 			n |= 0x80;
-		if (setupreq(ad->ep[0], RD2H|Rclass|Rendpt, which, sampling_freq_control<<8, n, 3) < 0)
+		r = Rd2h|Rclass|Rep;
+		if(usbcmd(ad,r,which,sampling_freq_control<<8, n, buf, 3) < 0)
 			return Undef;
 			return Undef;
-		n = setupreply(ad->ep[0], buf, 3);
 		if(n == 3){
 		if(n == 3){
 			if(buf[2]){
 			if(buf[2]){
-				if (debug & Dbgcontrol)
-					fprint(2, "Speed out of bounds\n");
-				if ((a->caps & has_discfreq) && (buf[0] | buf[1] << 8) < 8)
+				dprint(2, "Speed out of bounds\n");
+				if((a->caps & has_discfreq) && (buf[0] | buf[1] << 8) < 8)
 					return a->freqs[buf[0] | buf[1] << 8];
 					return a->freqs[buf[0] | buf[1] << 8];
 			}
 			}
 			return buf[0] | buf[1] << 8 | buf[2] << 16;
 			return buf[0] | buf[1] << 8 | buf[2] << 16;
 		}
 		}
-		if(debug & Dbgcontrol)
-			fprint(2, "getspeed: n = %d\n", n);
+		dprint(2, "getspeed: n = %d\n", n);
 	}
 	}
-	if (a->caps & has_contfreq){
-		if(debug & Dbgcontrol)
-			fprint(2, "getspeed: has_contfreq\n");
-		if (which == GET_CUR)
+	if(a->caps & has_contfreq){
+		dprint(2, "getspeed: has_contfreq\n");
+		if(which == Rgetcur)
 			return controls[rec][Speed_control].value[0];
 			return controls[rec][Speed_control].value[0];
-		if (which == GET_MIN)
+		if(which == Rgetmin)
 			return a->minfreq;
 			return a->minfreq;
-		if (which == GET_MAX)
+		if(which == Rgetmax)
 			return a->maxfreq;
 			return a->maxfreq;
-		if (which == GET_RES)
+		if(which == Rgetres)
 			return 1;
 			return 1;
 	}
 	}
-	if (a->caps & has_discfreq){
-		if(debug & Dbgcontrol)
-			fprint(2, "getspeed: has_discfreq\n");
-		if (which == GET_CUR)
+	if(a->caps & has_discfreq){
+		dprint(2, "getspeed: has_discfreq\n");
+		if(which == Rgetcur)
 			return controls[rec][Speed_control].value[0];
 			return controls[rec][Speed_control].value[0];
-		if (which == GET_MIN)
+		if(which == Rgetmin)
 			return a->freqs[0];
 			return a->freqs[0];
-		for (i = 0; i < 8 && a->freqs[i] > 0; i++)
+		for(i = 0; i < 8 && a->freqs[i] > 0; i++)
 			;
 			;
-		if (which == GET_MAX)
+		if(which == Rgetmax)
 			return a->freqs[i-1];
 			return a->freqs[i-1];
-		if (which == GET_RES)
+		if(which == Rgetres)
 			return Undef;
 			return Undef;
 	}
 	}
-	if (debug & Dbgcontrol)
-		fprint(2, "can't happen\n?");
+	dprint(2, "can't happen\n?");
 	return Undef;
 	return Undef;
 }
 }
 
 
@@ -346,55 +327,55 @@ setcontrol(int rec, char *name, long *value)
 	Audiocontrol *c;
 	Audiocontrol *c;
 
 
 	c = nil;
 	c = nil;
-	for (ctl = 0; ctl < Ncontrol; ctl++){
+	for(ctl = 0; ctl < Ncontrol; ctl++){
 		c = &controls[rec][ctl];
 		c = &controls[rec][ctl];
-		if (strcmp(name, c->name) == 0)
+		if(strcmp(name, c->name) == 0)
 			break;
 			break;
 	}
 	}
-	if (ctl == Ncontrol){
-		if (debug & Dbgcontrol) fprint(2, "setcontrol: control not found\n");
+	if(ctl == Ncontrol){
+		dprint(2, "setcontrol: control not found\n");
 		return -1;
 		return -1;
 	}
 	}
-	if (c->settable == 0) {
-		if (debug & Dbgcontrol) fprint(2, "setcontrol: control %d.%d not settable\n", rec, ctl);
-		if (c->chans){
-			for (i = 0; i < 8; i++)
-				if ((c->chans & 1 << i) && c->value[i] != value[i])
+	if(c->settable == 0){
+		dprint(2, "setcontrol: control %d.%d not settable\n", rec, ctl);
+		if(c->chans){
+			for(i = 0; i < 8; i++)
+				if((c->chans & 1 << i) && c->value[i] != value[i])
 					return -1;
 					return -1;
 			return 0;
 			return 0;
 		}
 		}
-		if (c->value[0] != value[0])
+		if(c->value[0] != value[0])
 			return -1;
 			return -1;
 		return 0;
 		return 0;
 	}
 	}
-	if (c->chans){
+	if(c->chans){
 		value[0] = 0;	// set to average
 		value[0] = 0;	// set to average
 		m = 0;
 		m = 0;
-		for (i = 1; i < 8; i++)
-			if (c->chans & 1 << i){
-				if (c->min != Undef && value[i] < c->min)
+		for(i = 1; i < 8; i++)
+			if(c->chans & 1 << i){
+				if(c->min != Undef && value[i] < c->min)
 					value[i] = c->min;
 					value[i] = c->min;
-				if (c->max != Undef && value[i] > c->max)
+				if(c->max != Undef && value[i] > c->max)
 					value[i] = c->max;
 					value[i] = c->max;
 				value[0] += value[i];
 				value[0] += value[i];
 				m++;
 				m++;
-			} else
+			}else
 				value[i] = Undef;
 				value[i] = Undef;
-		if (m) value[0] /= m;
+		if(m) value[0] /= m;
 	}else{
 	}else{
-		if (c->min != Undef && value[0] < c->min)
+		if(c->min != Undef && value[0] < c->min)
 			value[0] = c->min;
 			value[0] = c->min;
-		if (c->max != Undef && value[0] > c->max)
+		if(c->max != Undef && value[0] > c->max)
 			value[0] = c->max;
 			value[0] = c->max;
 	}
 	}
-	req = SET_CUR;
+	req = Rsetcur;
 	count = 1;
 	count = 1;
 	switch(ctl){
 	switch(ctl){
 	default:
 	default:
-		if (debug & Dbgcontrol) fprint(2, "setcontrol: can't happen\n");
+		dprint(2, "setcontrol: can't happen\n");
 		return -1;
 		return -1;
 	case Speed_control:
 	case Speed_control:
-		if (setrec && (value[0] = setspeed(rec, value[0])) < 0)
+		if((rec != Record || setrec) && (value[0] = setspeed(rec, value[0])) < 0)
 			return -1;
 			return -1;
 		c->value[0] = value[0];
 		c->value[0] = value[0];
 		return 0;
 		return 0;
@@ -404,8 +385,7 @@ setcontrol(int rec, char *name, long *value)
 	case Resolution_control:
 	case Resolution_control:
 		control = findalt(rec, controls[rec][Channel_control].value[0], value[0], defaultspeed[rec]);
 		control = findalt(rec, controls[rec][Channel_control].value[0], value[0], defaultspeed[rec]);
 		if(control < 0 || setaudioalt(rec, c, control) < 0){
 		if(control < 0 || setaudioalt(rec, c, control) < 0){
-			if (debug & Dbgcontrol) fprint(2, "setcontrol: can't find setting for %s\n",
-				c->name);
+			dprint(2, "setcontrol: can't find setting for %s\n", c->name);
 			return -1;
 			return -1;
 		}
 		}
 		c->value[0] = value[0];
 		c->value[0] = value[0];
@@ -422,20 +402,19 @@ setcontrol(int rec, char *name, long *value)
 	case Agc_control:
 	case Agc_control:
 	case Bassboost_control:
 	case Bassboost_control:
 	case Loudness_control:
 	case Loudness_control:
-		type = RH2D|Rclass|Rinterface;
+		type = Rh2d|Rclass|Riface;
 		control = ctl<<8;
 		control = ctl<<8;
 		index = featureid[rec]<<8;
 		index = featureid[rec]<<8;
 		break;
 		break;
 	case Selector_control:
 	case Selector_control:
-		type = RH2D|Rclass|Rinterface;
+		type = Rh2d|Rclass|Riface;
 		control = 0;
 		control = 0;
 		index = selectorid[rec]<<8;
 		index = selectorid[rec]<<8;
 		break;
 		break;
 	case Channel_control:
 	case Channel_control:
 		control = findalt(rec, value[0], controls[rec][Resolution_control].value[0], defaultspeed[rec]);
 		control = findalt(rec, value[0], controls[rec][Resolution_control].value[0], defaultspeed[rec]);
 		if(control < 0 || setaudioalt(rec, c, control) < 0){
 		if(control < 0 || setaudioalt(rec, c, control) < 0){
-			if (debug & Dbgcontrol) fprint(2, "setcontrol: can't find setting for %s\n",
-				c->name);
+			dprint(2, "setcontrol: can't find setting for %s\n", c->name);
 			return -1;
 			return -1;
 		}
 		}
 		c->value[0] = value[0];
 		c->value[0] = value[0];
@@ -443,16 +422,16 @@ setcontrol(int rec, char *name, long *value)
 		return 0;
 		return 0;
 	}
 	}
 	if(c->chans){
 	if(c->chans){
-		for (i = 1; i < 8; i++)
-			if (c->chans & 1 << i){
+		for(i = 1; i < 8; i++)
+			if(c->chans & 1 << i){
 				switch(count){
 				switch(count){
 				case 2:
 				case 2:
 					buf[1] = value[i] >> 8;
 					buf[1] = value[i] >> 8;
 				case 1:
 				case 1:
 					buf[0] = value[i];
 					buf[0] = value[i];
 				}
 				}
-				if (setupcmd(ad->ep[0], type, req, control | i, index, buf, count) < 0){
-					if (debug & Dbgcontrol) fprint(2, "setcontrol: setupcmd %s failed\n",
+				if(usbcmd(ad, type, req, control | i, index, buf, count) < 0){
+					dprint(2, "setcontrol: setupcmd %s failed\n",
 						controls[rec][ctl].name);
 						controls[rec][ctl].name);
 					return -1;
 					return -1;
 				}
 				}
@@ -465,9 +444,8 @@ setcontrol(int rec, char *name, long *value)
 		case 1:
 		case 1:
 			buf[0] = value[0];
 			buf[0] = value[0];
 		}
 		}
-		if (setupcmd(ad->ep[0], type, req, control, index, buf, count) < 0){
-			if (debug & Dbgcontrol) fprint(2, "setcontrol: setupcmd %s failed\n",
-				c->name);
+		if(usbcmd(ad, type, req, control, index, buf, count) < 0){
+			dprint(2, "setcontrol: setupcmd %s failed\n", c->name);
 			return -1;
 			return -1;
 		}
 		}
 	}
 	}
@@ -493,13 +471,13 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 		return 0;
 		return 0;
 	case Channel_control:
 	case Channel_control:
 	case Resolution_control:
 	case Resolution_control:
-		if (req == GET_MIN)
+		if(req == Rgetmin)
 			value[0] = controls[rec][ctl].min;
 			value[0] = controls[rec][ctl].min;
-		if (req == GET_MAX)
+		if(req == Rgetmax)
 			value[0] = controls[rec][ctl].max;
 			value[0] = controls[rec][ctl].max;
-		if (req == GET_RES)
+		if(req == Rgetres)
 			value[0] = controls[rec][ctl].step;
 			value[0] = controls[rec][ctl].step;
-		if (req == GET_CUR)
+		if(req == Rgetcur)
 			value[0] = controls[rec][ctl].value[0];
 			value[0] = controls[rec][ctl].value[0];
 		return 0;
 		return 0;
 	case Volume_control:
 	case Volume_control:
@@ -511,12 +489,12 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 	case Treble_control:
 	case Treble_control:
 	case Equalizer_control:
 	case Equalizer_control:
 		signedbyte = 1;
 		signedbyte = 1;
-		type = RD2H|Rclass|Rinterface;
+		type = Rd2h|Rclass|Riface;
 		control = ctl<<8;
 		control = ctl<<8;
 		index = featureid[rec]<<8;
 		index = featureid[rec]<<8;
 		break;
 		break;
 	case Selector_control:
 	case Selector_control:
-		type = RD2H|Rclass|Rinterface;
+		type = Rd2h|Rclass|Riface;
 		control = 0;
 		control = 0;
 		index = selectorid[rec]<<8;
 		index = selectorid[rec]<<8;
 		break;
 		break;
@@ -524,28 +502,28 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 	case Agc_control:
 	case Agc_control:
 	case Bassboost_control:
 	case Bassboost_control:
 	case Loudness_control:
 	case Loudness_control:
-		if (req != GET_CUR)
+		if(req != Rgetcur)
 			return Undef;
 			return Undef;
-		type = RD2H|Rclass|Rinterface;
+		type = Rd2h|Rclass|Riface;
 		control = ctl<<8;
 		control = ctl<<8;
 		index = featureid[rec]<<8;
 		index = featureid[rec]<<8;
 		break;
 		break;
 	}
 	}
-	if (controls[rec][ctl].chans){
+	if(controls[rec][ctl].chans){
 		m = 0;
 		m = 0;
 		value[0] = 0; // set to average
 		value[0] = 0; // set to average
-		for (i = 1; i < 8; i++){
+		for(i = 1; i < 8; i++){
 			value[i] = Undef;
 			value[i] = Undef;
-			if (controls[rec][ctl].chans & 1 << i){
-				if (setupreq(ad->ep[0], type, req, control | i, index, count) < 0)
+			if(controls[rec][ctl].chans & 1 << i){
+				n=usbcmd(ad, type,req, control|i,index,buf,count);
+				if(n < 0)
 					return Undef;
 					return Undef;
-				n = setupreply(ad->ep[0], buf, count);
-				if (n != count)
+				if(n != count)
 					return -1;
 					return -1;
-				switch (count) {
+				switch (count){
 				case 2:
 				case 2:
 					svalue = buf[1] << 8 | buf[0];
 					svalue = buf[1] << 8 | buf[0];
-					if (req == GET_CUR){
+					if(req == Rgetcur){
 						value[i] = svalue;
 						value[i] = svalue;
 						value[0] += svalue;
 						value[0] += svalue;
 						m++;
 						m++;
@@ -554,9 +532,9 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 					break;
 					break;
 				case 1:
 				case 1:
 					svalue = buf[0];
 					svalue = buf[0];
-					if (signedbyte && (svalue&0x80))
+					if(signedbyte && (svalue&0x80))
 						svalue |= 0xFF00;
 						svalue |= 0xFF00;
-					if (req == GET_CUR){
+					if(req == Rgetcur){
 						value[i] = svalue;
 						value[i] = svalue;
 						value[0] += svalue;
 						value[0] += svalue;
 						m++;
 						m++;
@@ -565,23 +543,20 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 				}
 				}
 			}
 			}
 		}
 		}
-		if (m) value[0] /= m;
+		if(m) value[0] /= m;
 		return 0;
 		return 0;
 	}
 	}
 	value[0] = Undef;
 	value[0] = Undef;
-	if (setupreq(ad->ep[0], type, req, control, index, count) < 0)
-		return -1;
-	n = setupreply(ad->ep[0], buf, count);
-	if (n != count)
+	if(usbcmd(ad, type, req, control, index, buf, count) != count)
 		return -1;
 		return -1;
-	switch (count) {
+	switch (count){
 	case 2:
 	case 2:
 		svalue = buf[1] << 8 | buf[0];
 		svalue = buf[1] << 8 | buf[0];
 		value[0] = svalue;
 		value[0] = svalue;
 		break;
 		break;
 	case 1:
 	case 1:
 		svalue = buf[0];
 		svalue = buf[0];
-		if (signedbyte && (svalue&0x80))
+		if(signedbyte && (svalue&0x80))
 			svalue |= 0xFF00;
 			svalue |= 0xFF00;
 		value[0] = svalue;
 		value[0] = svalue;
 	}
 	}
@@ -593,15 +568,15 @@ getcontrol(int rec, char *name, long *value)
 {
 {
 	int i;
 	int i;
 
 
-	for (i = 0; i < Ncontrol; i++){
-		if (strcmp(name, controls[rec][i].name) == 0)
+	for(i = 0; i < Ncontrol; i++){
+		if(strcmp(name, controls[rec][i].name) == 0)
 			break;
 			break;
 	}
 	}
-	if (i == Ncontrol)
+	if(i == Ncontrol)
 		return -1;
 		return -1;
-	if (controls[rec][i].readable == 0)
+	if(controls[rec][i].readable == 0)
 		return -1;
 		return -1;
-	if(getspecialcontrol(rec, i, GET_CUR, value) < 0)
+	if(getspecialcontrol(rec, i, Rgetcur, value) < 0)
 		return -1;
 		return -1;
 	memmove(controls[rec][i].value, value, sizeof controls[rec][i].value);
 	memmove(controls[rec][i].value, value, sizeof controls[rec][i].value);
 	return 0;
 	return 0;
@@ -614,45 +589,44 @@ getcontrols(void)
 	Audiocontrol *c;
 	Audiocontrol *c;
 	long v[8];
 	long v[8];
 
 
-	for (rec = 0; rec < 2; rec++) {
-		if (rec == Record && !setrec)
+	for(rec = 0; rec < 2; rec++){
+		if(rec == Record && !setrec)
 			continue;
 			continue;
-		for (ctl = 0; ctl < Ncontrol; ctl++){
+		for(ctl = 0; ctl < Ncontrol; ctl++){
 			c = &controls[rec][ctl];
 			c = &controls[rec][ctl];
-			if (c->readable){
-				if (verbose)
+			if(c->readable){
+				if(verbose)
 					fprint(2, "%s %s control",
 					fprint(2, "%s %s control",
 						rec?"Record":"Playback", controls[rec][ctl].name);
 						rec?"Record":"Playback", controls[rec][ctl].name);
-				c->min = (getspecialcontrol(rec, ctl, GET_MIN, v) < 0) ? Undef : v[0];
-				if (verbose && c->min != Undef)
+				c->min = (getspecialcontrol(rec, ctl, Rgetmin, v) < 0) ? Undef : v[0];
+				if(verbose && c->min != Undef)
 					fprint(2, ", min %ld", c->min);
 					fprint(2, ", min %ld", c->min);
-				c->max = (getspecialcontrol(rec, ctl, GET_MAX, v) < 0) ? Undef : v[0];
-				if (verbose && c->max != Undef)
+				c->max = (getspecialcontrol(rec, ctl, Rgetmax, v) < 0) ? Undef : v[0];
+				if(verbose && c->max != Undef)
 					fprint(2, ", max %ld", c->max);
 					fprint(2, ", max %ld", c->max);
-				c->step = (getspecialcontrol(rec, ctl, GET_RES, v) < 0) ? Undef : v[0];
-				if (verbose && c->step != Undef)
+				c->step = (getspecialcontrol(rec, ctl, Rgetres, v) < 0) ? Undef : v[0];
+				if(verbose && c->step != Undef)
 					fprint(2, ", step %ld", c->step);
 					fprint(2, ", step %ld", c->step);
-				if (getspecialcontrol(rec, ctl, GET_CUR, c->value) == 0){
-					if (verbose) {
-						if (c->chans){
+				if(getspecialcontrol(rec, ctl, Rgetcur, c->value) == 0){
+					if(verbose){
+						if(c->chans){
 							fprint(2, ", values");
 							fprint(2, ", values");
-							for (i = 1; i < 8; i++)
-								if (c->chans & 1 << i)
+							for(i = 1; i < 8; i++)
+								if(c->chans & 1 << i)
 									fprint(2, "[%d] %ld  ", i, c->value[i]);
 									fprint(2, "[%d] %ld  ", i, c->value[i]);
 						}else
 						}else
 							fprint(2, ", value %ld", c->value[0]);
 							fprint(2, ", value %ld", c->value[0]);
 					}
 					}
 				}
 				}
-				if (verbose)
+				if(verbose)
 					fprint(2, "\n");
 					fprint(2, "\n");
-			} else {
+			}else{
 				c->min = Undef;
 				c->min = Undef;
 				c->max = Undef;
 				c->max = Undef;
 				c->step = Undef;
 				c->step = Undef;
 				c->value[0] = Undef;
 				c->value[0] = Undef;
-				if (debug & Dbgcontrol)
-					fprint(2, "%s %s control not settable\n",
-						rec?"Playback":"Record", controls[rec][ctl].name);
+				dprint(2, "%s %s control not settable\n",
+					rec?"Playback":"Record", controls[rec][ctl].name);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -667,32 +641,32 @@ ctlparse(char *s, Audiocontrol *c, long *v)
 	long val;
 	long val;
 
 
 	nf = tokenize(s, vals, nelem(vals));
 	nf = tokenize(s, vals, nelem(vals));
-	if (nf <= 0)
+	if(nf <= 0)
 		return -1;
 		return -1;
-	if (c->chans){
+	if(c->chans){
 		j = 0;
 		j = 0;
 		m = 0;
 		m = 0;
 		SET(val);
 		SET(val);
 		v[0] = 0;	// will compute average of v[i]
 		v[0] = 0;	// will compute average of v[i]
-		for (i = 1; i < 8; i++)
-			if (c->chans & 1 << i) {
-				if (j < nf){
+		for(i = 1; i < 8; i++)
+			if(c->chans & 1 << i){
+				if(j < nf){
 					val = strtol(vals[j], &p, 0);
 					val = strtol(vals[j], &p, 0);
-					if (val == 0 && *p != '\0' && *p != '%')
+					if(val == 0 && *p != '\0' && *p != '%')
 						return -1;
 						return -1;
-					if (*p == '%' && c->min != Undef)
+					if(*p == '%' && c->min != Undef)
 						val = (val*c->max + (100-val)*c->min)/100;
 						val = (val*c->max + (100-val)*c->min)/100;
 					j++;
 					j++;
 				}
 				}
 				v[i] = val;
 				v[i] = val;
 				v[0] += val;
 				v[0] += val;
 				m++;
 				m++;
-			} else
+			}else
 				v[i] = Undef;
 				v[i] = Undef;
-		if (m) v[0] /= m;
-	} else {
+		if(m) v[0] /= m;
+	}else{
 		val = strtol(vals[0], &p, 0);
 		val = strtol(vals[0], &p, 0);
-		if (*p == '%' && c->min != Undef)
+		if(*p == '%' && c->min != Undef)
 			val = (val*c->max + (100-val)*c->min)/100;
 			val = (val*c->max + (100-val)*c->min)/100;
 		v[0] = val;
 		v[0] = val;
 	}
 	}
@@ -709,15 +683,15 @@ Aconv(Fmt *fp)
 
 
 	c = va_arg(fp->args, Audiocontrol*);
 	c = va_arg(fp->args, Audiocontrol*);
 	p = str;
 	p = str;
-	if (c->chans) {
+	if(c->chans){
 		fst = 1;
 		fst = 1;
-		for (i = 1; i < 8; i++)
-			if (c->chans & 1 << i){
+		for(i = 1; i < 8; i++)
+			if(c->chans & 1 << i){
 				p = seprint(p, str+sizeof str, "%s%ld", fst?"'":" ", c->value[i]);
 				p = seprint(p, str+sizeof str, "%s%ld", fst?"'":" ", c->value[i]);
 				fst = 0;
 				fst = 0;
 			}
 			}
 		seprint(p, str+sizeof str, "'");
 		seprint(p, str+sizeof str, "'");
-	} else
+	}else
 		seprint(p, str+sizeof str, "%ld", c->value[0]);
 		seprint(p, str+sizeof str, "%ld", c->value[0]);
 	return fmtstrcpy(fp, str);
 	return fmtstrcpy(fp, str);
 }
 }

+ 0 - 0
sys/src/cmd/usb/audio/usbaudioctl.h → sys/src/cmd/usb/audio/audioctl.h


+ 106 - 148
sys/src/cmd/usb/audio/audiofs.c

@@ -5,8 +5,8 @@
 #include <fcall.h>
 #include <fcall.h>
 #include <libsec.h>
 #include <libsec.h>
 #include "usb.h"
 #include "usb.h"
-#include "usbaudio.h"
-#include "usbaudioctl.h"
+#include "audio.h"
+#include "audioctl.h"
 
 
 int attachok;
 int attachok;
 
 
@@ -68,19 +68,14 @@ enum {
 	Qvolume,
 	Qvolume,
 	Qaudioctl,
 	Qaudioctl,
 	Qaudiostat,
 	Qaudiostat,
-	Qaudio,
-	Qaudioin,
 	Nqid,
 	Nqid,
 };
 };
 
 
 Dir dirs[] = {
 Dir dirs[] = {
-	/* Note: Qaudio{in} used as mount point for /dev/usb/%d/ep%ddata only */
-[Qdir] =	{0,0,{Qdir,      0,QTDIR},0555|DMDIR,0,0,0, ".",  nil,nil,nil},
+[Qdir] =		{0,0,{Qdir,      0,QTDIR},0555|DMDIR,0,0,0, ".",  nil,nil,nil},
 [Qvolume] =	{0,0,{Qvolume,   0,QTFILE},0666,0,0,0,	"volume", nil,nil,nil},
 [Qvolume] =	{0,0,{Qvolume,   0,QTFILE},0666,0,0,0,	"volume", nil,nil,nil},
 [Qaudioctl] =	{0,0,{Qaudioctl, 0,QTFILE},0666,0,0,0,	"audioctl",nil,nil,nil},
 [Qaudioctl] =	{0,0,{Qaudioctl, 0,QTFILE},0666,0,0,0,	"audioctl",nil,nil,nil},
 [Qaudiostat] =	{0,0,{Qaudiostat,0,QTFILE},0666,0,0,0,	"audiostat",nil,nil,nil},
 [Qaudiostat] =	{0,0,{Qaudiostat,0,QTFILE},0666,0,0,0,	"audiostat",nil,nil,nil},
-[Qaudio] =	{0,0,{Qaudio,    0,QTFILE},0222,0,0,0, "audio",   nil,nil,nil},
-[Qaudioin] =	{0,0,{Qaudioin,  0,QTFILE},0444,0,0,0, "audioin", nil,nil,nil},
 };
 };
 
 
 int	messagesize = 4*1024+IOHDRSZ;
 int	messagesize = 4*1024+IOHDRSZ;
@@ -166,12 +161,19 @@ post(char *name, char *envname, int srvfd)
 	putenv(envname, name);
 	putenv(envname, name);
 }
 }
 
 
+/*
+ * BUG: If audio is later used on a different name space, the
+ * audio/audioin files are not there because of the bind trick.
+ * We should actually implement those files despite the binds.
+ * If audio is used from within the same ns nothing would change,
+ * otherwise, whoever would mount the audio files could still
+ * play/record audio (unlike now).
+ */
 void
 void
 serve(void *)
 serve(void *)
 {
 {
 	int i;
 	int i;
 	ulong t;
 	ulong t;
-	Dir *dir;
 
 
 	if(pipe(p) < 0)
 	if(pipe(p) < 0)
 		sysfatal("pipe failed");
 		sysfatal("pipe failed");
@@ -181,7 +183,7 @@ serve(void *)
 	atnotify(notifyf, 1);
 	atnotify(notifyf, 1);
 	strcpy(user, getuser());
 	strcpy(user, getuser());
 	t = time(nil);
 	t = time(nil);
-	for (i = 0; i < Nqid; i++){
+	for(i = 0; i < Nqid; i++){
 		dirs[i].uid = user;
 		dirs[i].uid = user;
 		dirs[i].gid = user;
 		dirs[i].gid = user;
 		dirs[i].muid = user;
 		dirs[i].muid = user;
@@ -193,7 +195,7 @@ serve(void *)
 		mntpt = mntdir;
 		mntpt = mntdir;
 	}
 	}
 
 
-	if(debug)
+	if(usbdebug)
 		fmtinstall('F', fcallfmt);
 		fmtinstall('F', fcallfmt);
 
 
 	procrfork(io, nil, STACKSIZE, RFFDG|RFNAMEG);
 	procrfork(io, nil, STACKSIZE, RFFDG|RFNAMEG);
@@ -206,25 +208,10 @@ serve(void *)
 	}
 	}
 	if(mount(p[1], -1, mntpt, MBEFORE, "") < 0)
 	if(mount(p[1], -1, mntpt, MBEFORE, "") < 0)
 		sysfatal("mount failed");
 		sysfatal("mount failed");
-	if (endpt[Play] >= 0){
-		sprint(epdata, "#U/usb%d/%d/ep%ddata", ad->ctlrno, ad->id,
-			 endpt[Play]);
-		sprint(audiofile, "%s/audio", mntpt);
-		if(bind(epdata, audiofile, MREPL) < 0)
-			sysfatal("bind failed");
-
-		dir = dirstat(epdata);
-		if (dir && (dir->mode & 0222) != 0222)
-			sysfatal("%s unwritable", epdata);
-		free(dir);
-	}
-	if (endpt[Record] >= 0){
-		sprint(epdata, "#U/usb%d/%d/ep%ddata", ad->ctlrno, ad->id,
-			endpt[Record]);
-		sprint(audiofile, "%s/audioin", mntpt);
-		if(bind(epdata, audiofile, MREPL) < 0)
-			sysfatal("bind failed");
-	}
+	if(endpt[Play] >= 0 && devctl(epdev[Play], "name audio") < 0)
+		fprint(2, "audio: name audio: %r\n");
+	if(endpt[Record] >= 0 && devctl(epdev[Record], "name audioin") < 0)
+		fprint(2, "audio: name audioin: %r\n");
 	threadexits(nil);
 	threadexits(nil);
 }
 }
 
 
@@ -262,16 +249,15 @@ rflush(Fid *)
 
 
 	do {
 	do {
 		waitflush = 0;
 		waitflush = 0;
-		for (w = workers; w; w = w->next)
-			if (w->tag == thdr.oldtag){
+		for(w = workers; w; w = w->next)
+			if(w->tag == thdr.oldtag){
 				waitflush++;
 				waitflush++;
 				nbsendul(w->eventc, thdr.oldtag << 16 | Flush);
 				nbsendul(w->eventc, thdr.oldtag << 16 | Flush);
 			}
 			}
-		if (waitflush)
+		if(waitflush)
 			sleep(50);
 			sleep(50);
 	} while(waitflush);
 	} while(waitflush);
-	if (debug & Dbgproc)
-		fprint(2, "flush done on tag %d\n", thdr.oldtag);
+	dprint(2, "flush done on tag %d\n", thdr.oldtag);
 	return 0;
 	return 0;
 }
 }
 
 
@@ -305,19 +291,15 @@ dowalk(Fid *f, char *name)
 {
 {
 	int t;
 	int t;
 
 
-	if (strcmp(name, ".") == 0)
+	if(strcmp(name, ".") == 0)
 		return nil;
 		return nil;
-	if (strcmp(name, "..") == 0){
+	if(strcmp(name, "..") == 0){
 		f->dir = &dirs[Qdir];
 		f->dir = &dirs[Qdir];
 		return nil;
 		return nil;
 	}
 	}
 	if(f->dir != &dirs[Qdir])
 	if(f->dir != &dirs[Qdir])
 		return Enotexist;
 		return Enotexist;
-	for (t = 1; t < Nqid; t++){
-		if (t == Qaudio && endpt[Play] < 0)
-			continue;
-		if (t == Qaudioin && endpt[Record] < 0)
-			continue;
+	for(t = 1; t < Nqid; t++){
 		if(strcmp(name, dirs[t].name) == 0){
 		if(strcmp(name, dirs[t].name) == 0){
 			f->dir = &dirs[t];
 			f->dir = &dirs[t];
 			return nil;
 			return nil;
@@ -381,7 +363,7 @@ allocaudioctldata(void)
 	Audioctldata *a;
 	Audioctldata *a;
 
 
 	a = emallocz(sizeof(Audioctldata), 1);
 	a = emallocz(sizeof(Audioctldata), 1);
-	for (i = 0; i < 2; i++)
+	for(i = 0; i < 2; i++)
 		for(j=0; j < Ncontrol; j++)
 		for(j=0; j < Ncontrol; j++)
 			for(k=0; k < 8; k++)
 			for(k=0; k < 8; k++)
 				a->values[i][j][k] = Undef;
 				a->values[i][j][k] = Undef;
@@ -394,8 +376,6 @@ ropen(Fid *f)
 	if(f->flags & Open)
 	if(f->flags & Open)
 		return Eisopen;
 		return Eisopen;
 
 
-	if(f->dir == &dirs[Qaudio] || f->dir == &dirs[Qaudioin])
-		return Eperm;
 	if(thdr.mode != OREAD && (f->dir->mode & 0x2) == 0)
 	if(thdr.mode != OREAD && (f->dir->mode & 0x2) == 0)
 		return Eperm;
 		return Eperm;
 	qlock(f);
 	qlock(f);
@@ -422,11 +402,7 @@ readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
 
 
 	n = 0;
 	n = 0;
 	pos = 0;
 	pos = 0;
-	for (i = 1; i < Nqid; i++){
-		if (endpt[Play] < 0 && i == Qaudio)
-			continue;
-		if (endpt[Record] < 0 && i == Qaudioin)
-			continue;
+	for(i = 1; i < Nqid; i++){
 		m = convD2M(&dirs[i], &buf[n], blen-n);
 		m = convD2M(&dirs[i], &buf[n], blen-n);
 		if(off <= pos){
 		if(off <= pos){
 			if(m <= BIT16SZ || m > cnt)
 			if(m <= BIT16SZ || m > cnt)
@@ -450,32 +426,32 @@ makeaudioctldata(Fid *f)
 	Audiocontrol *c;
 	Audiocontrol *c;
 	Audioctldata *a;
 	Audioctldata *a;
 
 
-	if ((a = f->fiddata) == nil)
+	if((a = f->fiddata) == nil)
 		sysfatal("fiddata");
 		sysfatal("fiddata");
-	if ((p = a->s) == nil)
-		a->s = p = emalloc(Chunk);
+	if((p = a->s) == nil)
+		a->s = p = emallocz(Chunk, 0);
 	e = p + Chunk - 1;	/* e must point *at* last byte, not *after* */
 	e = p + Chunk - 1;	/* e must point *at* last byte, not *after* */
-	for (rec = 0; rec < 2; rec++)
-		for (ctl = 0; ctl < Ncontrol; ctl++) {
+	for(rec = 0; rec < 2; rec++)
+		for(ctl = 0; ctl < Ncontrol; ctl++){
 			c = &controls[rec][ctl];
 			c = &controls[rec][ctl];
 			actls = a->values[rec][ctl];
 			actls = a->values[rec][ctl];
 			diff = 0;
 			diff = 0;
-			if (c->chans){
-				for (i = 1; i < 8; i++)
-					if ((c->chans & 1<<i) &&
+			if(c->chans){
+				for(i = 1; i < 8; i++)
+					if((c->chans & 1<<i) &&
 					    c->value[i] != actls[i])
 					    c->value[i] != actls[i])
 						diff = 1;
 						diff = 1;
 			}else
 			}else
-				if (c->value[0] != actls[0])
+				if(c->value[0] != actls[0])
 					diff = 1;
 					diff = 1;
-			if (diff){
+			if(diff){
 				p = seprint(p, e, "%s %s %A", c->name,
 				p = seprint(p, e, "%s %s %A", c->name,
 					rec? "in": "out", c);
 					rec? "in": "out", c);
 				memmove(actls, c->value, sizeof c->value);
 				memmove(actls, c->value, sizeof c->value);
-				if (c->min != Undef){
+				if(c->min != Undef){
 					p = seprint(p, e, " %ld %ld", c->min,
 					p = seprint(p, e, " %ld %ld", c->min,
 						c->max);
 						c->max);
-					if (c->step != Undef)
+					if(c->step != Undef)
 						p = seprint(p, e, " %ld",
 						p = seprint(p, e, " %ld",
 							c->step);
 							c->step);
 				}
 				}
@@ -500,9 +476,9 @@ readproc(void *x)
 	Worker *w;
 	Worker *w;
 
 
 	w = x;
 	w = x;
-	mdata = emalloc(8*1024+IOHDRSZ);
+	mdata = emallocz(8*1024+IOHDRSZ, 0);
 	while(event = recvul(w->eventc)){
 	while(event = recvul(w->eventc)){
-		if (event != Work)
+		if(event != Work)
 			continue;
 			continue;
 		f = w->fid;
 		f = w->fid;
 		rhdr = w->rhdr;
 		rhdr = w->rhdr;
@@ -515,38 +491,35 @@ readproc(void *x)
 			qunlock(f);
 			qunlock(f);
 			event = recvul(w->eventc);
 			event = recvul(w->eventc);
 			qlock(f);
 			qlock(f);
-			if (debug & Dbgproc)
-				fprint(2, "readproc unblocked fid %d %lld\n",
+			ddprint(2, "readproc unblocked fid %d %lld\n",
 					f->fid, f->dir->qid.path);
 					f->fid, f->dir->qid.path);
-			switch (event & 0xffff) {
+			switch (event & 0xffff){
 			case Work:
 			case Work:
 				sysfatal("readproc phase error");
 				sysfatal("readproc phase error");
 			case Check:
 			case Check:
-				if (f->fiddata && makeaudioctldata(f) == 0)
+				if(f->fiddata && makeaudioctldata(f) == 0)
 					continue;
 					continue;
 				break;
 				break;
 			case Flush:
 			case Flush:
-				if ((event >> 16) == rhdr->tag) {
-					if (debug & Dbgproc)
-						fprint(2, "readproc flushing fid %d, tag %d\n",
-							f->fid, rhdr->tag);
+				if((event >> 16) == rhdr->tag){
+					ddprint(2, "readproc flushing fid %d, tag %d\n",
+						f->fid, rhdr->tag);
 					goto flush;
 					goto flush;
 				}
 				}
 				continue;
 				continue;
 			}
 			}
-			if (f->fiddata){
+			if(f->fiddata){
 				rhdr->data = a->s;
 				rhdr->data = a->s;
 				rhdr->count = a->ns;
 				rhdr->count = a->ns;
 				break;
 				break;
 			}
 			}
 			yield();
 			yield();
 		}
 		}
-		if (rhdr->count > cnt)
+		if(rhdr->count > cnt)
 			rhdr->count = cnt;
 			rhdr->count = cnt;
-		if (rhdr->count)
+		if(rhdr->count)
 			f->flags &= ~Eof;
 			f->flags &= ~Eof;
-		if(debug & (Dbgproc|Dbgfs))
-			fprint(2, "readproc:->%F\n", rhdr);
+		ddprint(2, "readproc:->%F\n", rhdr);
 		n = convS2M(rhdr, mdata, messagesize);
 		n = convS2M(rhdr, mdata, messagesize);
 		if(write(mfd[1], mdata, n) != n)
 		if(write(mfd[1], mdata, n) != n)
 			sysfatal("mount write");
 			sysfatal("mount write");
@@ -591,9 +564,9 @@ rread(Fid *f)
 	if(f->dir == &dirs[Qvolume]){
 	if(f->dir == &dirs[Qvolume]){
 		p = buf;
 		p = buf;
 		n = sizeof buf;
 		n = sizeof buf;
-		for (rec = 0; rec < 2; rec++){
+		for(rec = 0; rec < 2; rec++){
 			c = &controls[rec][Volume_control];
 			c = &controls[rec][Volume_control];
-			if (c->readable){
+			if(c->readable){
 				div = c->max - c->min;
 				div = c->max - c->min;
 				i = snprint(p, n, "audio %s %ld\n",
 				i = snprint(p, n, "audio %s %ld\n",
 					rec? "in": "out", (c->min != Undef?
 					rec? "in": "out", (c->min != Undef?
@@ -603,7 +576,7 @@ rread(Fid *f)
 				n -= i;
 				n -= i;
 			}
 			}
 			c = &controls[rec][Treble_control];
 			c = &controls[rec][Treble_control];
-			if (c->readable){
+			if(c->readable){
 				div = c->max - c->min;
 				div = c->max - c->min;
 				i = snprint(p, n, "treb %s %ld\n",
 				i = snprint(p, n, "treb %s %ld\n",
 					rec? "in": "out", (c->min != Undef?
 					rec? "in": "out", (c->min != Undef?
@@ -613,7 +586,7 @@ rread(Fid *f)
 				n -= i;
 				n -= i;
 			}
 			}
 			c = &controls[rec][Bass_control];
 			c = &controls[rec][Bass_control];
-			if (c->readable){
+			if(c->readable){
 				div = c->max - c->min;
 				div = c->max - c->min;
 				i = snprint(p, n, "bass %s %ld\n",
 				i = snprint(p, n, "bass %s %ld\n",
 					rec? "in": "out", (c->min != Undef?
 					rec? "in": "out", (c->min != Undef?
@@ -623,7 +596,7 @@ rread(Fid *f)
 				n -= i;
 				n -= i;
 			}
 			}
 			c = &controls[rec][Speed_control];
 			c = &controls[rec][Speed_control];
-			if (c->readable){
+			if(c->readable){
 				i = snprint(p, n, "speed %s %ld\n",
 				i = snprint(p, n, "speed %s %ld\n",
 					rec? "in": "out", c->value[0]);
 					rec? "in": "out", c->value[0]);
 				p += i;
 				p += i;
@@ -631,12 +604,12 @@ rread(Fid *f)
 			}
 			}
 		}
 		}
 		n = sizeof buf - n;
 		n = sizeof buf - n;
-		if (off > n)
+		if(off > n)
 			rhdr.count = 0;
 			rhdr.count = 0;
 		else{
 		else{
 			rhdr.data = buf + off;
 			rhdr.data = buf + off;
 			rhdr.count = n - off;
 			rhdr.count = n - off;
-			if (rhdr.count > cnt)
+			if(rhdr.count > cnt)
 				rhdr.count = cnt;
 				rhdr.count = cnt;
 		}
 		}
 		return nil;
 		return nil;
@@ -647,41 +620,41 @@ rread(Fid *f)
 
 
 		qlock(f);
 		qlock(f);
 		a = f->fiddata;
 		a = f->fiddata;
-		if (off - a->offoff < 0){
+		if(off - a->offoff < 0){
 			/* there was a seek */
 			/* there was a seek */
 			a->offoff = off;
 			a->offoff = off;
 			a->ns = 0;
 			a->ns = 0;
 		}
 		}
 		do {
 		do {
-			if (off - a->offoff < a->ns){
+			if(off - a->offoff < a->ns){
 				rhdr.data = a->s + (off - a->offoff);
 				rhdr.data = a->s + (off - a->offoff);
 				rhdr.count = a->ns - (off - a->offoff);
 				rhdr.count = a->ns - (off - a->offoff);
-				if (rhdr.count > cnt)
+				if(rhdr.count > cnt)
 					rhdr.count = cnt;
 					rhdr.count = cnt;
 				qunlock(f);
 				qunlock(f);
 				return nil;
 				return nil;
 			}
 			}
-			if (a->offoff != off){
+			if(a->offoff != off){
 				a->ns = 0;
 				a->ns = 0;
 				a->offoff = off;
 				a->offoff = off;
 				rhdr.count = 0;
 				rhdr.count = 0;
 				qunlock(f);
 				qunlock(f);
 				return nil;
 				return nil;
 			}
 			}
-		} while (makeaudioctldata(f) != 0);
+		} while(makeaudioctldata(f) != 0);
 
 
 		assert(a->offoff == off);
 		assert(a->offoff == off);
 		/* Wait for data off line */
 		/* Wait for data off line */
 		f->readers++;
 		f->readers++;
 		w = nbrecvp(procchan);
 		w = nbrecvp(procchan);
-		if (w == nil){
+		if(w == nil){
 			w = emallocz(sizeof(Worker), 1);
 			w = emallocz(sizeof(Worker), 1);
 			w->eventc = chancreate(sizeof(ulong), 1);
 			w->eventc = chancreate(sizeof(ulong), 1);
 			w->next = workers;
 			w->next = workers;
 			workers = w;
 			workers = w;
 			proccreate(readproc, w, 4096);
 			proccreate(readproc, w, 4096);
 		}
 		}
-		hdr = emalloc(sizeof(Fcall));
+		hdr = emallocz(sizeof(Fcall), 0);
 		w->fid = f;
 		w->fid = f;
 		w->tag = thdr.tag;
 		w->tag = thdr.tag;
 		assert(w->rhdr == nil);
 		assert(w->rhdr == nil);
@@ -719,67 +692,61 @@ rwrite(Fid *f)
 		thdr.data[cnt] = '\0';
 		thdr.data[cnt] = '\0';
 		nlines = getfields(thdr.data, lines, 2*Ncontrol, 1, "\n");
 		nlines = getfields(thdr.data, lines, 2*Ncontrol, 1, "\n");
 		for(i = 0; i < nlines; i++){
 		for(i = 0; i < nlines; i++){
-			if (debug)
-				 fprint(2, "line: %s\n", lines[i]);
+			dprint(2, "line: %s\n", lines[i]);
 			nf = tokenize(lines[i], fields, 4);
 			nf = tokenize(lines[i], fields, 4);
-			if (nf == 0)
+			if(nf == 0)
 				continue;
 				continue;
-			if (nf == 3)
-				if (strcmp(fields[1], "in") == 0 ||
+			if(nf == 3)
+				if(strcmp(fields[1], "in") == 0 ||
 				    strcmp(fields[1], "record") == 0)
 				    strcmp(fields[1], "record") == 0)
 					rec = 1;
 					rec = 1;
-				else if (strcmp(fields[1], "out") == 0 ||
+				else if(strcmp(fields[1], "out") == 0 ||
 				    strcmp(fields[1], "playback") == 0)
 				    strcmp(fields[1], "playback") == 0)
 					rec = 0;
 					rec = 0;
-				else {
-					if (debug)
-						fprint(2, "bad1\n");
+				else{
+					dprint(2, "bad1\n");
 					return Ebadctl;
 					return Ebadctl;
 				}
 				}
-			else if (nf == 2)
+			else if(nf == 2)
 				rec = 0;
 				rec = 0;
-			else {
-				if (debug)
-					fprint(2, "bad2 %d\n", nf);
+			else{
+				dprint(2, "bad2 %d\n", nf);
 				return Ebadctl;
 				return Ebadctl;
 			}
 			}
 			c = nil;
 			c = nil;
-			if (strcmp(fields[0], "audio") == 0) /* special case */
+			if(strcmp(fields[0], "audio") == 0) /* special case */
 				fields[0] = "volume";
 				fields[0] = "volume";
-			for (ctl = 0; ctl < Ncontrol; ctl++){
+			for(ctl = 0; ctl < Ncontrol; ctl++){
 				c = &controls[rec][ctl];
 				c = &controls[rec][ctl];
-				if (strcmp(fields[0], c->name) == 0)
+				if(strcmp(fields[0], c->name) == 0)
 					break;
 					break;
 			}
 			}
-			if (ctl == Ncontrol){
-				if (debug)
-					fprint(2, "bad3\n");
+			if(ctl == Ncontrol){
+				dprint(2, "bad3\n");
 				return Ebadctl;
 				return Ebadctl;
 			}
 			}
-			if (f->dir == &dirs[Qvolume] && ctl != Speed_control &&
+			if(f->dir == &dirs[Qvolume] && ctl != Speed_control &&
 			    c->min != Undef && c->max != Undef){
 			    c->min != Undef && c->max != Undef){
 				nnf = tokenize(fields[nf-1], subfields,
 				nnf = tokenize(fields[nf-1], subfields,
 					nelem(subfields));
 					nelem(subfields));
-				if (nnf <= 0 || nnf > 8){
-					if (debug)
-						fprint(2, "bad4\n");
+				if(nnf <= 0 || nnf > 8){
+					dprint(2, "bad4\n");
 					return Ebadctl;
 					return Ebadctl;
 				}
 				}
 				p = buf;
 				p = buf;
-				for (i = 0; i < nnf; i++){
+				for(i = 0; i < nnf; i++){
 					value = strtol(subfields[i], nil, 0);
 					value = strtol(subfields[i], nil, 0);
 					value = ((100 - value)*c->min +
 					value = ((100 - value)*c->min +
 						value*c->max) / 100;
 						value*c->max) / 100;
-					if (debug)
-						if (p == buf)
-							fprint(2, "rwrite: %s %s '%ld",
+					if(p == buf){
+						dprint(2, "rwrite: %s %s '%ld",
 								c->name, rec?
 								c->name, rec?
 								"record":
 								"record":
 								"playback",
 								"playback",
 								value);
 								value);
-						else
-							fprint(2, " %ld", value);
-					if (p == buf)
+					}else
+						dprint(2, " %ld", value);
+					if(p == buf)
 						p = seprint(p, buf+sizeof buf,
 						p = seprint(p, buf+sizeof buf,
 							"0x%p %s %s '%ld",
 							"0x%p %s %s '%ld",
 							replchan, c->name, rec?
 							replchan, c->name, rec?
@@ -789,13 +756,11 @@ rwrite(Fid *f)
 						p = seprint(p, buf+sizeof buf,
 						p = seprint(p, buf+sizeof buf,
 							" %ld", value);
 							" %ld", value);
 				}
 				}
-				if (debug)
-					fprint(2, "'\n");
+				dprint(2, "'\n");
 				seprint(p, buf+sizeof buf-1, "'");
 				seprint(p, buf+sizeof buf-1, "'");
 				chanprint(controlchan, buf);
 				chanprint(controlchan, buf);
-			} else {
-				if (debug)
-					fprint(2, "rwrite: %s %s %q", c->name,
+			}else{
+				dprint(2, "rwrite: %s %s %q", c->name,
 						rec? "record": "playback",
 						rec? "record": "playback",
 						fields[nf-1]);
 						fields[nf-1]);
 				chanprint(controlchan, "0x%p %s %s %q",
 				chanprint(controlchan, "0x%p %s %s %q",
@@ -803,16 +768,16 @@ rwrite(Fid *f)
 					"playback", fields[nf-1]);
 					"playback", fields[nf-1]);
 			}
 			}
 			p = recvp(replchan);
 			p = recvp(replchan);
-			if (p){
-				if (strcmp(p, "ok") == 0){
+			if(p){
+				if(strcmp(p, "ok") == 0){
 					free(p);
 					free(p);
 					p = nil;
 					p = nil;
 				}
 				}
-				if (err == nil)
+				if(err == nil)
 					err = p;
 					err = p;
 			}
 			}
 		}
 		}
-		for (w = workers; w; w = w->next)
+		for(w = workers; w; w = w->next)
 			nbsendul(w->eventc, Qaudioctl << 16 | Check);
 			nbsendul(w->eventc, Qaudioctl << 16 | Check);
 		rhdr.count = thdr.count;
 		rhdr.count = thdr.count;
 		return err;
 		return err;
@@ -828,9 +793,9 @@ rclunk(Fid *f)
 	qlock(f);
 	qlock(f);
 	f->flags &= ~(Open|Busy);
 	f->flags &= ~(Open|Busy);
 	assert(f->readers ==0);
 	assert(f->readers ==0);
-	if (f->fiddata){
+	if(f->fiddata){
 		a = f->fiddata;
 		a = f->fiddata;
-		if (a->s)
+		if(a->s)
 			free(a->s);
 			free(a->s);
 		free(a);
 		free(a);
 		f->fiddata = nil;
 		f->fiddata = nil;
@@ -850,16 +815,12 @@ rstat(Fid *f)
 {
 {
 	Audioctldata *a;
 	Audioctldata *a;
 
 
-	if (f->dir == &dirs[Qaudio] && endpt[Play] < 0)
-		return Enotexist;
-	if (f->dir == &dirs[Qaudioin] && endpt[Record] < 0)
-		return Enotexist;
-	if (f->dir == &dirs[Qaudioctl]){
+	if(f->dir == &dirs[Qaudioctl]){
 		qlock(f);
 		qlock(f);
-		if (f->fiddata == nil)
+		if(f->fiddata == nil)
 			f->fiddata = allocaudioctldata();
 			f->fiddata = allocaudioctldata();
 		a = f->fiddata;
 		a = f->fiddata;
-		if (a->ns == 0)
+		if(a->ns == 0)
 			makeaudioctldata(f);
 			makeaudioctldata(f);
 		f->dir->length = a->offoff + a->ns;
 		f->dir->length = a->offoff + a->ns;
 		qunlock(f);
 		qunlock(f);
@@ -921,9 +882,8 @@ io(void *)
 			continue;
 			continue;
 		if(n < 0){
 		if(n < 0){
 			rerrstr(e, sizeof e);
 			rerrstr(e, sizeof e);
-			if (strcmp(e, "interrupted") == 0){
-				if (debug)
-					fprint(2, "read9pmsg interrupted\n");
+			if(strcmp(e, "interrupted") == 0){
+				dprint(2, "read9pmsg interrupted\n");
 				continue;
 				continue;
 			}
 			}
 			return;
 			return;
@@ -931,15 +891,14 @@ io(void *)
 		if(convM2S(mdata, n, &thdr) == 0)
 		if(convM2S(mdata, n, &thdr) == 0)
 			continue;
 			continue;
 
 
-		if(debug & Dbgfs)
-			fprint(2, "io:<-%F\n", &thdr);
+		ddprint(2, "io:<-%F\n", &thdr);
 
 
 		rhdr.data = (char*)mdata + messagesize;
 		rhdr.data = (char*)mdata + messagesize;
 		if(!fcalls[thdr.type])
 		if(!fcalls[thdr.type])
 			err = "bad fcall type";
 			err = "bad fcall type";
 		else
 		else
 			err = (*fcalls[thdr.type])(newfid(thdr.fid));
 			err = (*fcalls[thdr.type])(newfid(thdr.fid));
-		if (err == (char*)~0)
+		if(err == (char*)~0)
 			continue;	/* handled off line */
 			continue;	/* handled off line */
 		if(err){
 		if(err){
 			rhdr.type = Rerror;
 			rhdr.type = Rerror;
@@ -949,8 +908,7 @@ io(void *)
 			rhdr.fid = thdr.fid;
 			rhdr.fid = thdr.fid;
 		}
 		}
 		rhdr.tag = thdr.tag;
 		rhdr.tag = thdr.tag;
-		if(debug & Dbgfs)
-			fprint(2, "io:->%F\n", &rhdr);
+		ddprint(2, "io:->%F\n", &rhdr);
 		n = convS2M(&rhdr, mdata, messagesize);
 		n = convS2M(&rhdr, mdata, messagesize);
 		if(write(mfd[1], mdata, n) != n)
 		if(write(mfd[1], mdata, n) != n)
 			sysfatal("mount write");
 			sysfatal("mount write");
@@ -976,6 +934,6 @@ ctlevent(void)
 {
 {
 	Worker *w;
 	Worker *w;
 
 
-	for (w = workers; w; w = w->next)
+	for(w = workers; w; w = w->next)
 		nbsendul(w->eventc, Qaudioctl << 16 | Check);
 		nbsendul(w->eventc, Qaudioctl << 16 | Check);
 }
 }

+ 134 - 107
sys/src/cmd/usb/audio/audiosub.c

@@ -2,8 +2,16 @@
 #include <libc.h>
 #include <libc.h>
 #include <thread.h>
 #include <thread.h>
 #include "usb.h"
 #include "usb.h"
-#include "usbaudio.h"
-#include "usbaudioctl.h"
+#include "audio.h"
+#include "audioctl.h"
+
+typedef struct Namelist Namelist;
+
+struct Namelist
+{
+	short	index;
+	char		*name;
+};
 
 
 Namelist terminal_types[] = {
 Namelist terminal_types[] = {
 	{	0x100, "USB Terminal, undefined type"},
 	{	0x100, "USB Terminal, undefined type"},
@@ -18,111 +26,124 @@ Namelist terminal_types[] = {
 units[2][8];	/* rec and play units */
 units[2][8];	/* rec and play units */
 nunits[2];		/* number in use */
 nunits[2];		/* number in use */
 
 
+char *
+namefor(Namelist *list, int item)
+{
+	while(list->name){
+		if(list->index == item)
+			return list->name;
+		list++;
+	}
+	return "<unnamed>";
+}
+
 static int
 static int
 findunit(int nr)
 findunit(int nr)
 {
 {
 	int rec, i;
 	int rec, i;
-	for (rec = 0; rec < 2; rec++)
-		for (i = 0; i < nunits[rec]; i++)
-			if (units[rec][i] == nr)
+	for(rec = 0; rec < 2; rec++)
+		for(i = 0; i < nunits[rec]; i++)
+			if(units[rec][i] == nr)
 				return rec;
 				return rec;
 	return -1;
 	return -1;
 }
 }
 
 
 void
 void
-audio_interface(Device *d, int n, ulong csp, void *bb, int nb) {
-	byte *b = bb;
+audio_interface(Dev *, Desc *dd)
+{
+	byte *b = (uchar*)&dd->data;
+	byte *bb = b;
+	int nb = dd->data.bLength;
 	int ctl, ch, u, x;
 	int ctl, ch, u, x;
 	byte *p;
 	byte *p;
-	int class, subclass, ifc, dalt;
+	int class, subclass;
 	Audioalt *aa;
 	Audioalt *aa;
+	char *hd;
 
 
-	if ((dalt = (csp >> 24) & 0xff) == 0xff) dalt = -1;
-	if ((ifc = (csp >> 16) & 0xff) == 0xff) ifc = -1;
-	class = Class(csp);
-	subclass = Subclass(csp);
+	class = Class(dd->iface->csp);
+	subclass = Subclass(dd->iface->csp);
 
 
-	if (debug & Dbginfo) fprint(2, "%d.%d: ", class, subclass);
-	switch (subclass) {
+	dprint(2, "%d.%d: ", class, subclass);
+	switch (subclass){
 	case 1:	// control
 	case 1:	// control
-		switch (b[2]) {
+		switch (b[2]){
 		case 0x01:
 		case 0x01:
-			if (debug & Dbginfo){
-				fprint(2, "Class-Specific AC Interface Header Descriptor\n");
-				fprint(2, "\tAudioDeviceClass release (bcd)%c%c%c%c, TotalLength %d, InCollection %d aInterfaceNr %d\n",
+			dprint(2, "Class-Specific AC Interface Header Descriptor\n");
+			dprint(2, "\tAudioDevClass release (bcd)%c%c%c%c, "
+				"TotalLength %d, InCollection %d aInterfaceNr %d\n",
 					'0'+((b[4]>>4)&0xf), '0'+(b[4]&0xf),
 					'0'+((b[4]>>4)&0xf), '0'+(b[4]&0xf),
 					'0'+((b[3]>>4)&0xf), '0'+(b[3]&0xf),
 					'0'+((b[3]>>4)&0xf), '0'+(b[3]&0xf),
 					b[5]|(b[6]<<8), b[7], b[8]);
 					b[5]|(b[6]<<8), b[7], b[8]);
-			}
 			break;
 			break;
 		case 0x02:	// input
 		case 0x02:	// input
-			if (debug & Dbginfo){
-				fprint(2, "Audio Input Terminal Descriptor\n");
-				fprint(2, "\tbTerminalId %d, wTerminalType 0x%x (%s), bAssocTerminal %d bNrChannels %d, wChannelConfig %d, iChannelNames %d iTerminal %d\n",
+			dprint(2, "Audio Input Terminal Descriptor\n");
+			dprint(2, "\tbTerminalId %d, wTerminalType "
+				"0x%x (%s), bAssocTerminal %d bNrChannels %d, "
+				"wChannelConfig %d, iChannelNames %d iTerminal %d\n",
 					b[3], b[4]|(b[5]<<8),
 					b[3], b[4]|(b[5]<<8),
 					namefor(terminal_types, b[4]|(b[5]<<8)),
 					namefor(terminal_types, b[4]|(b[5]<<8)),
 					b[6], b[7], b[8]|(b[9]<<8), b[10], b[11]);
 					b[6], b[7], b[8]|(b[9]<<8), b[10], b[11]);
-			}
-			if ((b[4]|b[5]<<8) == 0x101){
-				if (verbose)
+			if((b[4]|b[5]<<8) == 0x101){
+				if(verbose)
 					fprint(2, "Audio output unit %d\n", b[3]);
 					fprint(2, "Audio output unit %d\n", b[3]);
 				/* USB streaming input: play interface */
 				/* USB streaming input: play interface */
 				units[Play][nunits[Play]++] = b[3];
 				units[Play][nunits[Play]++] = b[3];
 			}else{
 			}else{
-				if (verbose)
-					fprint(2, "Device can record from %s\n",
+				if(verbose)
+					fprint(2, "Dev can record from %s\n",
 						namefor(terminal_types, b[4]|(b[5]<<8)));
 						namefor(terminal_types, b[4]|(b[5]<<8)));
 				/* Non-USB input: record interface */
 				/* Non-USB input: record interface */
 				units[Record][nunits[Record]++] = b[3];
 				units[Record][nunits[Record]++] = b[3];
 			}
 			}
 			break;
 			break;
 		case 0x03:	// output
 		case 0x03:	// output
-			if (debug & Dbginfo){
+			if(usbdebug){
 				fprint(2, "Audio Output Terminal Descriptor\n");
 				fprint(2, "Audio Output Terminal Descriptor\n");
 				fprint(2, "\tbTerminalId %d, wTerminalType 0x%x (%s), bAssocTerminal %d bSourceId %d, iTerminal %d\n",
 				fprint(2, "\tbTerminalId %d, wTerminalType 0x%x (%s), bAssocTerminal %d bSourceId %d, iTerminal %d\n",
 					b[3], b[4]|(b[5]<<8),
 					b[3], b[4]|(b[5]<<8),
 					namefor(terminal_types, b[4]|(b[5]<<8)),
 					namefor(terminal_types, b[4]|(b[5]<<8)),
 					b[6], b[7], b[8]);
 					b[6], b[7], b[8]);
 			}
 			}
-			if ((b[4]|b[5]<<8) == 0x101){
-				if (verbose)
+			if((b[4]|b[5]<<8) == 0x101){
+				if(verbose)
 					fprint(2, "Audio input unit %d\n", b[3]);
 					fprint(2, "Audio input unit %d\n", b[3]);
 				/* USB streaming output: record interface */
 				/* USB streaming output: record interface */
 				units[Record][nunits[Record]++] = b[3];
 				units[Record][nunits[Record]++] = b[3];
-				if (verbose)
-					fprint(2, "Device can play to %s\n",
+				if(verbose)
+					fprint(2, "Dev can play to %s\n",
 						namefor(terminal_types, b[4]|(b[5]<<8)));
 						namefor(terminal_types, b[4]|(b[5]<<8)));
 				/* Non-USB output: play interface */
 				/* Non-USB output: play interface */
 				units[Play][nunits[Play]++] = b[3];
 				units[Play][nunits[Play]++] = b[3];
 			}
 			}
 			break;
 			break;
 		case 0x04:
 		case 0x04:
-			if (verbose)
+			if(verbose)
 				fprint(2, "Audio Mixer Unit %d\n", b[3]);
 				fprint(2, "Audio Mixer Unit %d\n", b[3]);
-			if (debug & Dbginfo){
+			if(usbdebug){
 				fprint(2, "\t%d bytes:", nb);
 				fprint(2, "\t%d bytes:", nb);
 				for(ctl = 0; ctl < nb; ctl++)
 				for(ctl = 0; ctl < nb; ctl++)
 					fprint(2, " 0x%2.2x", b[ctl]);
 					fprint(2, " 0x%2.2x", b[ctl]);
 				fprint(2, "\n\tbUnitId %d, bNrInPins %d", b[3], b[4]);
 				fprint(2, "\n\tbUnitId %d, bNrInPins %d", b[3], b[4]);
 			}
 			}
-			if (b[4]){
-				if(debug & Dbginfo) fprint(2, ", baSourceIDs: [%d", b[5]);
+			if(b[4]){
+				dprint(2, ", baSourceIDs: [%d", b[5]);
 				u = findunit(b[5]);
 				u = findunit(b[5]);
-				for (ctl = 1; ctl < b[4]; ctl++){
-					if (u < 0)
+				for(ctl = 1; ctl < b[4]; ctl++){
+					if(u < 0)
 						u = findunit(b[5+ctl]);
 						u = findunit(b[5+ctl]);
-					else if ((x = findunit(b[5+ctl])) >= 0 && u != x && verbose)
-						fprint(2, "\tMixer %d for output AND input\n", b[3]);
-					if (debug & Dbginfo) fprint(2, ", %d", b[5+ctl]);
+					else if((x = findunit(b[5+ctl])) >= 0 && u != x && verbose)
+						fprint(2, "\tMixer %d for I & O \n", b[3]);
+					dprint(2, ", %d", b[5+ctl]);
 				}
 				}
-				if (debug & Dbginfo) fprint(2, "]\n");
-				if (u >= 0){
+				dprint(2, "]\n");
+				if(u >= 0){
 					units[u][nunits[u]++] = b[3];
 					units[u][nunits[u]++] = b[3];
-					if (mixerid[u] >= 0)
-						fprint(2, "Second mixer (%d, %d) on %s\n", mixerid[u], b[3], u?"record":"playback");
+					if(mixerid[u] >= 0)
+						fprint(2, "Second mixer (%d, %d) on %s\n",
+						 mixerid[u], b[3], u?"record":"playback");
 					mixerid[u] = b[3];
 					mixerid[u] = b[3];
 				}
 				}
-				if (debug & Dbginfo){
+				if(usbdebug){
 					fprint(2, "Channels %d, config %d, ",
 					fprint(2, "Channels %d, config %d, ",
 						b[ctl+5], b[ctl+5+1] | b[ctl+5+2] << 8);
 						b[ctl+5], b[ctl+5+1] | b[ctl+5+2] << 8);
 					x = b[ctl+5] * b[4];
 					x = b[ctl+5] * b[4];
@@ -134,25 +155,25 @@ audio_interface(Device *d, int n, ulong csp, void *bb, int nb) {
 			}
 			}
 			break;
 			break;
 		case 0x05:
 		case 0x05:
-			if (verbose)
+			if(verbose)
 				fprint(2, "Audio Selector Unit %d\n", b[3]);
 				fprint(2, "Audio Selector Unit %d\n", b[3]);
-			if (debug & Dbginfo)
-				fprint(2, "\tbUnitId %d, bNrInPins %d", b[3], b[4]);
-			if (b[4]){
+			dprint(2, "\tbUnitId %d, bNrInPins %d", b[3], b[4]);
+			if(b[4]){
 				u = findunit(b[5]);
 				u = findunit(b[5]);
-				if (debug & Dbginfo) fprint(2, ", baSourceIDs: %s [%d",
+				dprint(2, ", baSourceIDs: %s [%d",
 					u?"record":"playback", b[5]);
 					u?"record":"playback", b[5]);
-				for (ctl = 1; ctl < b[4]; ctl++){
-					if (u < 0)
+				for(ctl = 1; ctl < b[4]; ctl++){
+					if(u < 0)
 						u = findunit(b[5+ctl]);
 						u = findunit(b[5+ctl]);
-					else if ((x = findunit(b[5+ctl])) >= 0 && u != x && verbose)
-						fprint(2, "\tSelector %d for output AND input\n", b[3]);
-					if (debug & Dbginfo) fprint(2, ", %d", b[5+ctl]);
+					else if((x = findunit(b[5+ctl])) >= 0 &&
+						u != x && verbose)
+						fprint(2, "\tSelector %d for I & O\n", b[3]);
+					dprint(2, ", %d", b[5+ctl]);
 				}
 				}
-				if (debug & Dbginfo) fprint(2, "]\n");
-				if (u >= 0){
+				dprint(2, "]\n");
+				if(u >= 0){
 					units[u][nunits[u]++] = b[3];
 					units[u][nunits[u]++] = b[3];
-					if (selectorid[u] >= 0)
+					if(selectorid[u] >= 0)
 						fprint(2, "Second selector (%d, %d) on %s\n", selectorid[u], b[3], u?"record":"playback");
 						fprint(2, "Second selector (%d, %d) on %s\n", selectorid[u], b[3], u?"record":"playback");
 					selectorid[u] = b[3];
 					selectorid[u] = b[3];
 					controls[u][Selector_control].readable = 1;
 					controls[u][Selector_control].readable = 1;
@@ -162,39 +183,41 @@ audio_interface(Device *d, int n, ulong csp, void *bb, int nb) {
 			}
 			}
 			break;
 			break;
 		case 0x06:	// feature
 		case 0x06:	// feature
-			if (verbose) fprint(2, "Audio Feature Unit %d", b[3]);
-			if (debug & Dbginfo)
-				fprint(2, "\tbUnitId %d, bSourceId %d, bControlSize %d\n",
-					b[3], b[4], b[5]);
+			if(verbose) fprint(2, "Audio Feature Unit %d", b[3]);
+			dprint(2, "\tbUnitId %d, bSourceId %d, bControlSize %d\n",
+				b[3], b[4], b[5]);
 			u = findunit(b[4]);
 			u = findunit(b[4]);
-			if (u >= 0){
-				if (verbose) fprint(2, " for %s\n", u?"Record":"Playback");
+			if(u >= 0){
+				if(verbose) fprint(2, " for %s\n", u?"Record":"Playback");
 				units[u][nunits[u]++] = b[3];
 				units[u][nunits[u]++] = b[3];
-				if (featureid[u] >= 0)
-					if (verbose) fprint(2, "Second feature unit (%d, %d) on %s\n", featureid[u], b[3], u?"record":"playback");
+				if(featureid[u] >= 0)
+					if(verbose)
+						fprint(2, "Second feature unit (%d, %d)"
+							" on %s\n", featureid[u], b[3],
+							u?"record":"playback");
 				featureid[u] = b[3];
 				featureid[u] = b[3];
 			}else
 			}else
-				if (verbose) fprint(2, ", not known what for\n");
+				if(verbose) fprint(2, ", not known what for\n");
 			p = b + 6;
 			p = b + 6;
-			for (ctl = 1; ctl < 0x0b; ctl++)
-				if ((1<<(ctl-1)) & (b[6] | ((b[5]>1)?(b[7]<<8):0))) {
-					if (verbose)
+			for(ctl = 1; ctl < 0x0b; ctl++)
+				if((1<<(ctl-1)) & (b[6] | ((b[5]>1)?(b[7]<<8):0))){
+					if(verbose)
 						fprint(2, "\t%s control on master channel\n",
 						fprint(2, "\t%s control on master channel\n",
 							controls[0][ctl].name);
 							controls[0][ctl].name);
-					if (u >= 0){
+					if(u >= 0){
 						controls[u][ctl].readable = 1;
 						controls[u][ctl].readable = 1;
 						controls[u][ctl].settable = 1;
 						controls[u][ctl].settable = 1;
 						controls[u][ctl].chans = 0;
 						controls[u][ctl].chans = 0;
 					}
 					}
 				}
 				}
 			p += (b[5]>1)?2:1;
 			p += (b[5]>1)?2:1;
-			for (ch = 0; ch < (nb - 8)/b[5]; ch++) {
-				for (ctl = 1; ctl < 0x0b; ctl++)
-					if ((1<<(ctl-1)) & (p[0] | ((b[5]>1)?(p[1]<<8):0))) {
-						if (verbose)
-							fprint(2, "\t%s control on channel %d\n",
-								controls[0][ctl].name, ch+1);
-						if (u >= 0){
+			for(ch = 0; ch < (nb - 8)/b[5]; ch++){
+				for(ctl = 1; ctl < 0x0b; ctl++)
+					if((1<<(ctl-1)) & (p[0] | ((b[5]>1)?(p[1]<<8):0))){
+						if(verbose)
+						  fprint(2, "\t%s control on channel %d\n",
+							controls[0][ctl].name, ch+1);
+						if(u >= 0){
 							controls[u][ctl].readable = 1;
 							controls[u][ctl].readable = 1;
 							controls[u][ctl].settable = 1;
 							controls[u][ctl].settable = 1;
 							controls[u][ctl].chans |= 1 <<(ch+1);
 							controls[u][ctl].chans |= 1 <<(ch+1);
@@ -204,52 +227,51 @@ audio_interface(Device *d, int n, ulong csp, void *bb, int nb) {
 			}
 			}
 			break;
 			break;
 		default:
 		default:
-			pcs_raw("audio control unknown", bb, nb);
+			hd = hexstr(bb, nb);
+			fprint(2, "audio control unknown: %s\n", hd);
+			free(hd);
 		}
 		}
 		break;
 		break;
 	case 2: // stream
 	case 2: // stream
-		switch (b[2]) {
+		switch (b[2]){
 		case 0x01:
 		case 0x01:
-			if (debug & Dbginfo)
-				fprint(2, "Audio stream for TerminalID %d, delay %d, format_tag %#ux\n",
-					b[3], b[4], b[5] | (b[6]<<8));
+			dprint(2, "Audio stream for TerminalID %d, delay %d, format_tag %#ux\n",
+				b[3], b[4], b[5] | (b[6]<<8));
 			break;
 			break;
 		case 0x02:
 		case 0x02:
-			if (d->config[n]->iface[ifc]->dalt[dalt] == nil)
-				d->config[n]->iface[ifc]->dalt[dalt] = mallocz(sizeof(Dalt),1);
-			aa = (Audioalt *)d->config[n]->iface[ifc]->dalt[dalt]->devspec;
-			if (aa == nil) {
+			aa = (Audioalt *)dd->altc->aux;
+			if(aa == nil){
 				aa = mallocz(sizeof(Audioalt), 1);
 				aa = mallocz(sizeof(Audioalt), 1);
-				d->config[n]->iface[ifc]->dalt[dalt]->devspec = aa;
+				dd->altc->aux = aa;
 			}
 			}
-			if (verbose){
-				if (b[4] <= 2)
-					fprint(2, "Interface %d, alt %d: %s, %d bits, ",
-						ifc, dalt, (b[4] == 1)?"mono":"stereo", b[6]);
+			if(verbose){
+				if(b[4] <= 2)
+					fprint(2, "Interface %d: %s, %d bits, ",
+						dd->iface->id, (b[4] == 1)?"mono":"stereo", b[6]);
 				else
 				else
-					fprint(2, "Interface %d, alt %d: %d channels, %d bits, ",
-						ifc, dalt, b[4], b[6]);
+					fprint(2, "Interface %d, %d channels, %d bits, ",
+						dd->iface->id, b[4], b[6]);
 			}
 			}
 			if(b[7] == 0){
 			if(b[7] == 0){
-				if (verbose)
+				if(verbose)
 					fprint(2, "frequency variable between %d and %d\n",
 					fprint(2, "frequency variable between %d and %d\n",
 						b[8] | b[9]<<8 | b[10]<<16, b[11] | b[12]<<8 | b[13]<<16);
 						b[8] | b[9]<<8 | b[10]<<16, b[11] | b[12]<<8 | b[13]<<16);
 				aa->minfreq = b[8] | b[9]<<8 | b[10]<<16;
 				aa->minfreq = b[8] | b[9]<<8 | b[10]<<16;
 				aa->maxfreq = b[11] | b[12]<<8 | b[13]<<16;
 				aa->maxfreq = b[11] | b[12]<<8 | b[13]<<16;
 				aa->caps |= has_contfreq;
 				aa->caps |= has_contfreq;
 			}else{
 			}else{
-				if (verbose)
+				if(verbose)
 					fprint(2, "discrete frequencies are:");
 					fprint(2, "discrete frequencies are:");
-				for (ch = 0; ch < b[7] && ch < 8; ch++){
+				for(ch = 0; ch < b[7] && ch < 8; ch++){
 					aa->freqs[ch] = b[8+3*ch] | b[9+3*ch]<<8 | b[10+3*ch]<<16;
 					aa->freqs[ch] = b[8+3*ch] | b[9+3*ch]<<8 | b[10+3*ch]<<16;
-					if (verbose)
+					if(verbose)
 						fprint(2, " %d", b[8+3*ch] | b[9+3*ch]<<8 | b[10+3*ch]<<16);
 						fprint(2, " %d", b[8+3*ch] | b[9+3*ch]<<8 | b[10+3*ch]<<16);
 				}
 				}
-				if (ch < 8)
+				if(ch < 8)
 					aa->freqs[ch] = -1;
 					aa->freqs[ch] = -1;
-				if (verbose)
+				if(verbose)
 					fprint(2, "\n");
 					fprint(2, "\n");
-				if (ch > 1)
+				if(ch > 1)
 					aa->caps |= has_discfreq;	/* more than one frequency */
 					aa->caps |= has_discfreq;	/* more than one frequency */
 				else
 				else
 					aa->caps |= onefreq;		/* only one frequency */
 					aa->caps |= onefreq;		/* only one frequency */
@@ -259,15 +281,20 @@ audio_interface(Device *d, int n, ulong csp, void *bb, int nb) {
 			aa->subframesize = b[5];
 			aa->subframesize = b[5];
 			break;
 			break;
 		default:
 		default:
-			if (debug & Dbginfo)
-				pcs_raw("audio stream unknown", bb, nb);
+			if(usbdebug){
+				hd = hexstr(bb, nb);
+				fprint(2, "audio stream unknown: %s\n", hd);
+				free(hd);
+			}
 		}
 		}
 		break;
 		break;
 	case 3: // midi
 	case 3: // midi
 	default:
 	default:
-		if (debug & Dbginfo){
-			fprint(2, "Unknown audio stream type: ");
-			pcs_raw("CS_INTERFACE", bb, nb);
+		if(usbdebug){
+			hd = hexstr(bb, nb);
+			fprint(2, "Unknown audio stream type: CS_INTERFACE: %s\n", hd);
+			free(hd);
 		}
 		}
 	}
 	}
 }
 }
+

+ 5 - 5
sys/src/cmd/usb/audio/mkfile

@@ -1,15 +1,15 @@
 </$objtype/mkfile
 </$objtype/mkfile
 
 
-TARG=usbaudio
+TARG=audio
 OFILES=\
 OFILES=\
 	audiofs.$O\
 	audiofs.$O\
 	audiosub.$O\
 	audiosub.$O\
-	usbaudio.$O\
-	usbaudioctl.$O\
+	audio.$O\
+	audioctl.$O\
 
 
 HFILES=\
 HFILES=\
-	usbaudio.h\
-	usbaudioctl.h\
+	audio.h\
+	audioctl.h\
 	../lib/usb.h\
 	../lib/usb.h\
 
 
 UPDATE=\
 UPDATE=\

+ 481 - 788
sys/src/cmd/usb/disk/disk.c

@@ -1,1019 +1,712 @@
 /*
 /*
  * usb/disk - usb mass storage file server
  * usb/disk - usb mass storage file server
+ * BUG: supports only the scsi command interface.
+ * BUG: This should use /dev/sdfile to
+ * use the kernel ether device code.
  */
  */
+
 #include <u.h>
 #include <u.h>
 #include <libc.h>
 #include <libc.h>
 #include <ctype.h>
 #include <ctype.h>
-#include <bio.h>
 #include <fcall.h>
 #include <fcall.h>
 #include <thread.h>
 #include <thread.h>
-#include <9p.h>
 #include "scsireq.h"
 #include "scsireq.h"
 #include "usb.h"
 #include "usb.h"
+#include "usbfs.h"
+#include "ums.h"
 
 
-/*
- * mass storage transport protocols and subclasses,
- * from usb mass storage class specification overview rev 1.2
- */
-enum {
-	Protocbi =	0,	/* control/bulk/interrupt; mainly floppies */
-	Protocb =	1,	/*   "  with no interrupt; mainly floppies */
-	Protobulk =	0x50,	/* bulk only */
-
-	Subrbc =	1,	/* reduced blk cmds */
-	Subatapi =	2,	/* cd/dvd using sff-8020i or mmc-2 cmd blks */
-	Subqic =	3,	/* QIC-157 tapes */
-	Subufi =	4,	/* floppy */
-	Sub8070 =	5,	/* removable media, atapi-like */
-	Subscsi =	6,	/* scsi transparent cmd set */
-	Subisd200 =	7,	/* ISD200 ATA */
-	Subdev =	0xff,	/* use device's value */
-};
-
-enum {
-	GET_MAX_LUN_T =	RD2H | Rclass | Rinterface,
-	GET_MAX_LUN =	0xFE,
-	UMS_RESET_T =	RH2D | Rclass | Rinterface,
-	UMS_RESET =	0xFF,
-
-	MaxIOsize	= 256*1024,	/* max. I/O size */
-//	Maxlun		= 256,
-	Maxlun		= 32,
-};
-
-#define PATH(type, n)	((type) | (n)<<8)
-#define TYPE(path)	((uchar)(path))
-#define NUM(path)	((uint)(path)>>8)
-
-enum {
+enum
+{
 	Qdir = 0,
 	Qdir = 0,
 	Qctl,
 	Qctl,
-	Qn,
 	Qraw,
 	Qraw,
 	Qdata,
 	Qdata,
-
-	CMreset = 1,
-
-	Pcmd = 0,
-	Pdata,
-	Pstatus,
-};
-
-static char *subclass[] = {
-	"?",
-	"rbc",
-	"atapi",
-	"qic tape",
-	"ufi floppy",
-	"8070 removable",
-	"scsi transparent",
-	"isd200 ata",
+	Qmax,
 };
 };
 
 
 typedef struct Dirtab Dirtab;
 typedef struct Dirtab Dirtab;
-struct Dirtab {
+struct Dirtab
+{
 	char	*name;
 	char	*name;
 	int	mode;
 	int	mode;
 };
 };
-Dirtab dirtab[] = {
-	".",	DMDIR|0555,
-	"ctl",	0640,
-	nil,	DMDIR|0750,
-	"raw",	0640,
-	"data",	0640,
-};
 
 
-Cmdtab cmdtab[] = {
-	CMreset,	"reset",	1,
-};
-
-/* these are 600 bytes each; ScsiReq is not tiny */
-typedef struct Umsc Umsc;
-struct Umsc {
-	ScsiReq;
-	ulong	blocks;
-	vlong	capacity;
-	uchar 	rawcmd[10];
-	uchar	phase;
-	char	*inq;
-};
-
-typedef struct Ums Ums;
-struct Ums {
-	Umsc	*lun;
-	uchar	maxlun;
-	int	fd2;
-	int	fd;
-	int	setupfd;
-	int	ctlfd;
-	uchar	epin;
-	uchar	epout;
-	char	dev[64];
+static Dirtab dirtab[] =
+{
+	[Qdir]	"/",	DMDIR|0555,
+	[Qctl]	"ctl",	0444,
+	[Qraw]	"raw",	0640,
+	[Qdata]	"data",	0640,
 };
 };
 
 
-int exabyte, force6bytecmds, needmaxlun;
-long starttime;
-long maxiosize = MaxIOsize;
-
-volatile int timedout;
-
-char *owner;
-
-static Ums ums;
-static int freakout;		/* flag: if true, drive freaks out if reset */
-
-extern int debug;
-
-static void umsreset(Ums *umsc, int doinit);
-
 /*
 /*
- * USB transparent SCSI devices
+ * These are used by scuzz scsireq
  */
  */
-typedef struct Cbw Cbw;			/* command block wrapper */
-struct Cbw {
-	char	signature[4];		/* "USBC" */
-	long	tag;
-	long	datalen;
-	uchar	flags;
-	uchar	lun;
-	uchar	len;
-	char	command[16];
-};
+int exabyte, force6bytecmds;
+long maxiosize = MaxIOsize;
 
 
-typedef struct Csw Csw;			/* command status wrapper */
-struct Csw {
-	char	signature[4];		/* "USBS" */
-	long	tag;
-	long	dataresidue;
-	uchar	status;
-};
+static int diskdebug;
 
 
-enum {
-	CbwLen		= 31,
-	CbwDataIn	= 0x80,
-	CbwDataOut	= 0x00,
-	CswLen		= 13,
-	CswOk		= 0,
-	CswFailed	= 1,
-	CswPhaseErr	= 2,
-};
+static int
+getmaxlun(Dev *dev)
+{
+	uchar max;
+	int r;
+
+	max = 0;
+	r = Rd2h|Rclass|Riface;
+	if(usbcmd(dev, r, Getmaxlun, 0, 0, &max, 1) < 0){
+		dprint(2, "disk: %s: getmaxlun failed: %r\n", dev->dir);
+	}else
+		dprint(2, "disk: %s: maxlun %d\n", dev->dir, max);
+	return max;
+}
 
 
-int
-statuscmd(int fd, int type, int req, int value, int index, char *data,
-	int count)
+static int
+umsreset(Ums *ums)
 {
 {
-	char *wp;
-
-	wp = emalloc9p(count + 8);
-	wp[0] = type;
-	wp[1] = req;
-	PUT2(wp + 2, value);
-	PUT2(wp + 4, index);
-	PUT2(wp + 6, count);
-	if(data != nil)
-		memmove(wp + 8, data, count);
-	if(write(fd, wp, count + 8) != count + 8){
-		fprint(2, "%s: statuscmd: %r\n", argv0);
+	int r;
+
+	r = Rh2d|Rclass|Riface;
+	if(usbcmd(ums->dev, r, Umsreset, 0, 0, nil, 0) < 0){
+		fprint(2, "disk: reset: %r\n");
 		return -1;
 		return -1;
 	}
 	}
 	return 0;
 	return 0;
 }
 }
 
 
-int
-statusread(int fd, char *buf, int count)
+static int
+umsrecover(Ums *ums)
 {
 {
-	if(read(fd, buf, count) < 0){
-		fprint(2, "%s: statusread: %r\n", argv0);
+	if(umsreset(ums) < 0)
 		return -1;
 		return -1;
-	}
+	if(unstall(ums->dev, ums->epin, Ein) < 0)
+		dprint(2, "disk: unstall epin: %r\n");
+
+	/* do we need this when epin == epout? */
+	if(unstall(ums->dev, ums->epout, Eout) < 0)
+		dprint(2, "disk: unstall epout: %r\n");
 	return 0;
 	return 0;
 }
 }
 
 
-void
-getmaxlun(Ums *ums)
+static void
+umsfatal(Ums *ums)
 {
 {
-	uchar max;
+	int i;
 
 
-	if(needmaxlun &&
-	    statuscmd(ums->setupfd, GET_MAX_LUN_T, GET_MAX_LUN, 0, 0, nil, 0)
-	    == 0 && statusread(ums->setupfd, (char *)&max, 1) == 0)
-		fprint(2, "%s: maxlun %d\n", argv0, max);	// DEBUG
-	else
-		max = 0;
-	ums->lun = mallocz((max + 1) * sizeof *ums->lun, 1);
-	assert(ums->lun);
-	ums->maxlun = max;
+	devctl(ums->dev, "detach");
+	for(i = 0; i < ums->maxlun; i++)
+		usbfsdel(&ums->lun[i].fs);
 }
 }
 
 
-int
-umsinit(Ums *ums, int epin, int epout)
+static int
+umscapacity(Umsc *lun)
 {
 {
-	uchar data[8], i;
-	char fin[128];
-	Umsc *lun;
+	uchar data[32];
 
 
-	if(ums->ctlfd == -1) {
-		snprint(fin, sizeof fin, "%s/ctl", ums->dev);
-		if((ums->ctlfd = open(fin, OWRITE)) == -1)
+	lun->blocks = 0;
+	lun->capacity = 0;
+	lun->lbsize = 0;
+	if(SRrcapacity(lun, data) < 0 && SRrcapacity(lun, data)  < 0)
+		return -1;
+	lun->blocks = GETBELONG(data);
+	lun->lbsize = GETBELONG(data+4);
+	if(lun->blocks == 0xFFFFFFFF){
+		if(SRrcapacity16(lun, data) < 0){
+			lun->lbsize = 0;
+			lun->blocks = 0;
 			return -1;
 			return -1;
-		if(epin == epout) {
-			if(fprint(ums->ctlfd, "ep %d bulk rw 64 16", epin) < 0)
-				return -1;
-		} else {
-			if(fprint(ums->ctlfd, "ep %d bulk r 64 16", epin) < 0 ||
-			   fprint(ums->ctlfd, "ep %d bulk w 64 16", epout) < 0)
-				return -1;
+		}else{
+			lun->lbsize = GETBELONG(data + 8);
+			lun->blocks = (uvlong)GETBELONG(data)<<32 | GETBELONG(data + 4);
 		}
 		}
-		snprint(fin, sizeof fin, "%s/ep%ddata", ums->dev, epin);
-		if((ums->fd = open(fin, OREAD)) == -1)
-			return -1;
-		snprint(fin, sizeof fin, "%s/ep%ddata", ums->dev, epout);
-		if((ums->fd2 = open(fin, OWRITE)) == -1)
-			return -1;
-		snprint(fin, sizeof fin, "%s/setup", ums->dev);
-		if ((ums->setupfd = open(fin, ORDWR)) == -1)
-			return -1;
 	}
 	}
+	lun->blocks++; /* SRcapacity returns LBA of last block */
+	lun->capacity = (vlong)lun->blocks * lun->lbsize;
+	return 0;
+}
+
+static int
+umsinit(Ums *ums)
+{
+	uchar i;
+	Umsc *lun;
+	int some;
 
 
-	ums->epin = epin;
-	ums->epout = epout;
-	umsreset(ums, 0);
-	getmaxlun(ums);
-	for(i = 0; i <= ums->maxlun; i++) {
+	umsreset(ums);
+	ums->maxlun = getmaxlun(ums->dev);
+	ums->lun = mallocz((ums->maxlun+1) * sizeof(*ums->lun), 1);
+	some = 0;
+	for(i = 0; i <= ums->maxlun; i++){
 		lun = &ums->lun[i];
 		lun = &ums->lun[i];
+		lun->ums = ums;
+		lun->umsc = lun;
 		lun->lun = i;
 		lun->lun = i;
-		lun->umsc = lun;			/* pointer to self */
 		lun->flags = Fopen | Fusb | Frw10;
 		lun->flags = Fopen | Fusb | Frw10;
-		if(SRinquiry(lun) == -1)
-			return -1;
-		lun->inq = smprint("%.48s", (char *)lun->inquiry+8);
-		SRstart(lun, 1);
-		if (SRrcapacity(lun, data) == -1 &&
-		    SRrcapacity(lun, data) == -1) {
-			lun->blocks = 0;
-			lun->capacity = 0;
-			lun->lbsize = 0;
-		} else {
-			lun->lbsize = data[4]<<24|data[5]<<16|data[6]<<8|data[7];
-			lun->blocks = data[0]<<24|data[1]<<16|data[2]<<8|data[3];
-			lun->blocks++; /* SRcapacity returns LBA of last block */
-			lun->capacity = (vlong)lun->blocks * lun->lbsize;
+		if(SRinquiry(lun) < 0 && SRinquiry(lun) < 0)
+			continue;
+		if(lun->inquiry[0] != 0x00){
+			/* not a disk */
+			fprint(2, "%s: lun %d is not a disk (type %#02x)\n",
+				argv0, i, lun->inquiry[0]);
+			continue;
 		}
 		}
+		SRstart(lun, 1);
+		/*
+		 * we ignore the device type reported by inquiry.
+		 * Some devices return a wrong value but would still work.
+		 */
+		some++;
+		lun->inq = smprint("%.48s", (char *)lun->inquiry+8);
+		umscapacity(lun);
+	}
+	if(some == 0){
+		devctl(ums->dev, "detach");
+		return -1;
 	}
 	}
 	return 0;
 	return 0;
 }
 }
 
 
-static void
-unstall(Ums *ums, int ep)
-{
-	if(fprint(ums->ctlfd, "unstall %d", ep & 0xF) < 0)
-		fprint(2, "ctl write failed\n");
-	if(fprint(ums->ctlfd, "data %d 0", ep & 0xF) < 0)
-		fprint(2, "ctl write failed\n");
-
-	statuscmd(ums->setupfd, RH2D | Rstandard | Rendpt, CLEAR_FEATURE, 0,
-		0<<8 | ep, nil, 0);
-}
-
-static void
-umsreset(Ums *umsc, int doinit)
-{
-	if (!freakout)
-		statuscmd(umsc->setupfd, UMS_RESET_T, UMS_RESET, 0, 0, nil, 0);
-
-	unstall(umsc, umsc->epin|0x80);
-	unstall(umsc, umsc->epout);
-	if(doinit && umsinit(&ums, umsc->epin, umsc->epout) < 0)
-		sysfatal("device error");
-}
 
 
+/*
+ * called by SR*() commands provided by scuzz's scsireq
+ */
 long
 long
 umsrequest(Umsc *umsc, ScsiPtr *cmd, ScsiPtr *data, int *status)
 umsrequest(Umsc *umsc, ScsiPtr *cmd, ScsiPtr *data, int *status)
 {
 {
 	Cbw cbw;
 	Cbw cbw;
 	Csw csw;
 	Csw csw;
 	int n;
 	int n;
-	static int seq = 0;
+	Ums *ums;
+
+	ums = umsc->ums;
 
 
 	memcpy(cbw.signature, "USBC", 4);
 	memcpy(cbw.signature, "USBC", 4);
-	cbw.tag = ++seq;
+	cbw.tag = ++ums->seq;
 	cbw.datalen = data->count;
 	cbw.datalen = data->count;
 	cbw.flags = data->write? CbwDataOut: CbwDataIn;
 	cbw.flags = data->write? CbwDataOut: CbwDataIn;
 	cbw.lun = umsc->lun;
 	cbw.lun = umsc->lun;
+	if(cmd->count < 1 || cmd->count > 16)
+		print("%s: umsrequest: bad cmd count: %ld\n", argv0, cmd->count);
+	
 	cbw.len = cmd->count;
 	cbw.len = cmd->count;
+	assert(cmd->count <= sizeof(cbw.command));
 	memcpy(cbw.command, cmd->p, cmd->count);
 	memcpy(cbw.command, cmd->p, cmd->count);
 	memset(cbw.command + cmd->count, 0, sizeof(cbw.command) - cmd->count);
 	memset(cbw.command + cmd->count, 0, sizeof(cbw.command) - cmd->count);
 
 
-	if(debug) {
-		fprint(2, "cmd:");
-		for (n = 0; n < cbw.len; n++)
+	werrstr("");		/* we use %r later even for n == 0 */
+
+	if(diskdebug){
+		fprint(2, "disk: cmd: tag %#lx: ", cbw.tag);
+		for(n = 0; n < cbw.len; n++)
 			fprint(2, " %2.2x", cbw.command[n]&0xFF);
 			fprint(2, " %2.2x", cbw.command[n]&0xFF);
 		fprint(2, " datalen: %ld\n", cbw.datalen);
 		fprint(2, " datalen: %ld\n", cbw.datalen);
 	}
 	}
-	if(write(ums.fd2, &cbw, CbwLen) != CbwLen){
-		fprint(2, "usbscsi: write cmd: %r\n");
-		goto reset;
+	if(write(ums->epout->dfd, &cbw, CbwLen) != CbwLen){
+		fprint(2, "disk: cmd: %r\n");
+		goto Fail;
 	}
 	}
-	if(data->count != 0) {
+	if(data->count != 0){
 		if(data->write)
 		if(data->write)
-			n = write(ums.fd2, data->p, data->count);
-		else
-			n = read(ums.fd, data->p, data->count);
-		if(n == -1){
-			if(debug)
-				fprint(2, "usbscsi: data %sput: %r\n",
-					data->write? "out": "in");
-			if(data->write)
-				unstall(&ums, ums.epout);
-			else
-				unstall(&ums, ums.epin | 0x80);
+			n = write(ums->epout->dfd, data->p, data->count);
+		else{
+			memset(data->p, data->count, 0);
+			n = read(ums->epin->dfd, data->p, data->count);
 		}
 		}
+		if(diskdebug)
+			if(n < 0)
+				fprint(2, "disk: data: %r\n");
+			else
+				fprint(2, "disk: data: %d bytes\n", n);
+		if(n <= 0)
+			if(data->write == 0)
+				unstall(ums->dev, ums->epin, Ein);
 	}
 	}
-	n = read(ums.fd, &csw, CswLen);
-	if(n == -1){
-		unstall(&ums, ums.epin | 0x80);
-		n = read(ums.fd, &csw, CswLen);
+	n = read(ums->epin->dfd, &csw, CswLen);
+	if(n <= 0){
+		/* n == 0 means "stalled" */
+		unstall(ums->dev, ums->epin, Ein);
+		n = read(ums->epin->dfd, &csw, CswLen);
 	}
 	}
 	if(n != CswLen || strncmp(csw.signature, "USBS", 4) != 0){
 	if(n != CswLen || strncmp(csw.signature, "USBS", 4) != 0){
-		fprint(2, "usbscsi: read status: %r\n");
-		goto reset;
+		dprint(2, "disk: read n=%d: status: %r\n", n);
+		goto Fail;
 	}
 	}
-	if(csw.tag != cbw.tag) {
-		fprint(2, "usbscsi: status tag mismatch\n");
-		goto reset;
+	if(csw.tag != cbw.tag){
+		dprint(2, "disk: status tag mismatch\n");
+		goto Fail;
 	}
 	}
 	if(csw.status >= CswPhaseErr){
 	if(csw.status >= CswPhaseErr){
-		fprint(2, "usbscsi: phase error\n");
-		goto reset;
+		dprint(2, "disk: phase error\n");
+		goto Fail;
 	}
 	}
-	if(debug) {
+	if(diskdebug){
 		fprint(2, "status: %2.2ux residue: %ld\n",
 		fprint(2, "status: %2.2ux residue: %ld\n",
 			csw.status, csw.dataresidue);
 			csw.status, csw.dataresidue);
-		if(cbw.command[0] == ScmdRsense) {
+		if(cbw.command[0] == ScmdRsense){
 			fprint(2, "sense data:");
 			fprint(2, "sense data:");
-			for (n = 0; n < data->count - csw.dataresidue; n++)
+			for(n = 0; n < data->count - csw.dataresidue; n++)
 				fprint(2, " %2.2x", data->p[n]);
 				fprint(2, " %2.2x", data->p[n]);
 			fprint(2, "\n");
 			fprint(2, "\n");
 		}
 		}
 	}
 	}
-
-	if(csw.status == CswOk)
+	switch(csw.status){
+	case CswOk:
 		*status = STok;
 		*status = STok;
-	else
+		break;
+	case CswFailed:
 		*status = STcheck;
 		*status = STcheck;
+		break;
+	default:
+		dprint(2, "disk: phase error\n");
+		goto Fail;
+	}
+	ums->nerrs = 0;
 	return data->count - csw.dataresidue;
 	return data->count - csw.dataresidue;
 
 
-reset:
-	umsreset(&ums, 0);
+Fail:
 	*status = STharderr;
 	*status = STharderr;
+	if(ums->nerrs++ > 15){
+		fprint(2, "disk: %s: too many errors: device detached\n", ums->dev->dir);
+		umsfatal(ums);
+	}else
+		umsrecover(ums);
 	return -1;
 	return -1;
 }
 }
 
 
-int
-findendpoints(Device *d, int *epin, int *epout)
-{
-	Endpt *ep;
-	ulong csp;
-	int i, addr, nendpt;
-
-	*epin = *epout = -1;
-	nendpt = 0;
-	if(d->nconf < 1)
-		return -1;
-	for(i=0; i<d->nconf; i++) {
-		if (d->config[i] == nil)
-			d->config[i] = mallocz(sizeof(*d->config[i]),1);
-		loadconfig(d, i);
-	}
-	for(i = 0; i < Nendpt; i++){
-		if((ep = d->ep[i]) == nil)
-			continue;
-		nendpt++;
-		csp = ep->csp;
-		if(!(Class(csp) == CL_STORAGE && (Proto(csp) == 0x50)))
-			continue;
-		if(ep->type == Ebulk) {
-			addr = ep->addr;
-			if (debug)
-				print("findendpoints: bulk; ep->addr %ux\n",
-					ep->addr);
-			if (ep->dir == Eboth || addr&0x80)
-				if(*epin == -1)
-					*epin =  addr&0xF;
-			if (ep->dir == Eboth || !(addr&0x80))
-				if(*epout == -1)
-					*epout = addr&0xF;
-		}
-	}
-	if(nendpt == 0) {
-		if(*epin == -1)
-			*epin = *epout;
-		if(*epout == -1)
-			*epout = *epin;
-	}
-	if (*epin == -1 || *epout == -1)
-		return -1;
-	return 0;
-}
-
-int
-timeoutok(void)
-{
-	if (freakout)
-		return 1;	/* OK; keep trying */
-	else {
-		fprint(2, "%s: no response from device.  unplug and replug "
-			"it and try again with -f\n", argv0);
-		return 0;	/* die */
-	}
-}
-
-int
-notifyf(void *, char *s)
-{
-	if(strcmp(s, "alarm") != 0)
-		return 0;		/* die */
-	if (!timeoutok()) {
-		fprint(2, "%s: timed out\n", argv0);
-		return 0;		/* die */
-	}
-	alarm(120*1000);
-	fprint(2, "%s: resetting alarm\n", argv0);
-	timedout = 1;
-	return 1;			/* keep going */
-}
-
-int
-devokay(int ctlrno, int id)
-{
-	int epin = -1, epout = -1;
-	long time;
-	Device *d;
-	static int beenhere;
-
-	if (!beenhere) {
-		atnotify(notifyf, 1);
-		beenhere = 1;
-	}
-	time = alarm(15*1000);
-	d = opendev(ctlrno, id);
-	if (describedevice(d) < 0) {
-		perror("");
-		closedev(d);
-		alarm(time);
-		return 0;
-	}
-	if (findendpoints(d, &epin, &epout) < 0) {
-		fprint(2, "%s: bad usb configuration for ctlr %d id %d\n",
-			argv0, ctlrno, id);
-		closedev(d);
-		alarm(time);
-		return 0;
-	}
-	closedev(d);
-
-	snprint(ums.dev, sizeof ums.dev, "/dev/usb%d/%d", ctlrno, id);
-	if (umsinit(&ums, epin, epout) < 0) {
-		alarm(time);
-		fprint(2, "%s: initialisation: %r\n", argv0);
-		return 0;
-	}
-	alarm(time);
-	return 1;
-}
-
-static char *
-subclname(int subcl)
-{
-	if ((unsigned)subcl < nelem(subclass))
-		return subclass[subcl];
-	return "**GOK**";		/* traditional */
-}
-
 static int
 static int
-scanstatus(int ctlrno, int id)
+dwalk(Usbfs *fs, Fid *fid, char *name)
 {
 {
-	int winner;
-	ulong csp;
-	char *p, *hex;
-	char buf[64];
-	Biobuf *f;
-
-	/* read port status file */
-	sprint(buf, "/dev/usb%d/%d/status", ctlrno, id);
-	f = Bopen(buf, OREAD);
-	if (f == nil)
-		sysfatal("can't open %s: %r", buf);
-	if (debug)
-		fprint(2, "%s: reading %s\n", argv0, buf);
-	winner = 0;
-	while (!winner && (p = Brdline(f, '\n')) != nil) {
-		p[Blinelen(f)-1] = '\0';
-		if (debug && *p == 'E')			/* Enabled? */
-			fprint(2, "%s: %s\n", argv0, p);
-		for (hex = p; *hex != '\0' && *hex != '0'; hex++)
-			continue;
-		csp = atol(hex);
-
-		if (Class(csp) == CL_STORAGE && Proto(csp) == Protobulk) {
-			if (0)
-				fprint(2, "%s: /dev/usb%d/%d: bulk storage "
-					"of subclass %s\n", argv0, ctlrno, id,
-					subclname(Subclass(csp)));
-			switch (Subclass(csp)) {
-			case Subatapi:
-			case Sub8070:
-			case Subscsi:
-				winner++;
-				break;
-			}
-		}
-	}
-	Bterm(f);
-	return winner;
-}
+	int i;
+	Qid qid;
 
 
-static int
-findums(int *ctlrp, int *idp)
-{
-	int ctlrno, id, winner, devfd, ctlrfd, nctlr, nport;
-	char buf[64];
-	Dir *ctlrs, *cdp, *ports, *pdp;
-
-	*ctlrp = *idp = -1;
-	winner = 0;
-
-	/* walk controllers */
-	devfd = open("/dev", OREAD);
-	if (devfd < 0)
-		sysfatal("can't open /dev: %r");
-	nctlr = dirreadall(devfd, &ctlrs);
-	if (nctlr < 0)
-		sysfatal("can't read /dev: %r");
-	for (cdp = ctlrs; nctlr-- > 0 && !winner; cdp++) {
-		if (strncmp(cdp->name, "usb", 3) != 0)
-			continue;
-		ctlrno = atoi(cdp->name + 3);
-
-		/* walk ports within a controller */
-		snprint(buf, sizeof buf, "/dev/%s", cdp->name);
-		ctlrfd = open(buf, OREAD);
-		if (ctlrfd < 0)
-			sysfatal("can't open %s: %r", buf);
-		nport = dirreadall(ctlrfd, &ports);
-		if (nport < 0)
-			sysfatal("can't read %s: %r", buf);
-		for (pdp = ports; nport-- > 0 && !winner; pdp++) {
-			if (!isdigit(*pdp->name))
-				continue;
-			id = atoi(pdp->name);
-
-			/* read port status file */
-			winner = scanstatus(ctlrno, id);
-			if (winner)
-				if (devokay(ctlrno, id)) {
-					*ctlrp = ctlrno;
-					*idp = id;
-				} else
-					winner = 0;
-		}
-		free(ports);
-		close(ctlrfd);
-	}
-	free(ctlrs);
-	close(devfd);
-	if (!winner)
+	qid = fid->qid;
+	if((qid.type & QTDIR) == 0){
+		werrstr("walk in non-directory");
 		return -1;
 		return -1;
-	else
-		return 0;
-}
-
-void
-rattach(Req *r)
-{
-	r->ofcall.qid.path = PATH(Qdir, 0);
-	r->ofcall.qid.type = dirtab[Qdir].mode >> 24;
-	r->fid->qid = r->ofcall.qid;
-	respond(r, nil);
-}
-
-char*
-rwalk1(Fid *fid, char *name, Qid *qid)
-{
-	int i, n;
-	char buf[32];
-	ulong path;
-
-	path = fid->qid.path;
-	if(!(fid->qid.type & QTDIR))
-		return "walk in non-directory";
+	}
 
 
 	if(strcmp(name, "..") == 0)
 	if(strcmp(name, "..") == 0)
-		switch(TYPE(path)) {
-		case Qn:
-			qid->path = PATH(Qn, NUM(path));
-			qid->type = dirtab[Qn].mode >> 24;
-			return nil;
-		case Qdir:
-			return nil;
-		default:
-			return "bug in rwalk1";
-		}
+		return 0;
 
 
-	for(i = TYPE(path)+1; i < nelem(dirtab); i++) {
-		if(i==Qn){
-			n = atoi(name);
-			snprint(buf, sizeof buf, "%d", n);
-			if(n <= ums.maxlun && strcmp(buf, name) == 0){
-				qid->path = PATH(i, n);
-				qid->type = dirtab[i].mode>>24;
-				return nil;
-			}
-			break;
-		}
-		if(strcmp(name, dirtab[i].name) == 0) {
-			qid->path = PATH(i, NUM(path));
-			qid->type = dirtab[i].mode >> 24;
-			return nil;
+	for(i = 1; i < nelem(dirtab); i++)
+		if(strcmp(name, dirtab[i].name) == 0){
+			qid.path = i | fs->qid;
+			qid.vers = 0;
+			qid.type = dirtab[i].mode >> 24;
+			fid->qid = qid;
+			return 0;
 		}
 		}
-		if(dirtab[i].mode & DMDIR)
-			break;
-	}
-	return "directory entry not found";
+	werrstr(Enotfound);
+	return -1;
 }
 }
 
 
-void
-dostat(int path, Dir *d)
+static void
+dostat(Usbfs *fs, int path, Dir *d)
 {
 {
 	Dirtab *t;
 	Dirtab *t;
+	Umsc *lun;
 
 
-	memset(d, 0, sizeof(*d));
-	d->uid = estrdup9p(owner);
-	d->gid = estrdup9p(owner);
+	t = &dirtab[path];
 	d->qid.path = path;
 	d->qid.path = path;
-	d->atime = d->mtime = starttime;
-	t = &dirtab[TYPE(path)];
-	if(t->name)
-		d->name = estrdup9p(t->name);
-	else {
-		d->name = smprint("%ud", NUM(path));
-		if(d->name == nil)
-			sysfatal("out of memory");
-	}
-	if(TYPE(path) == Qdata)
-		d->length = ums.lun[NUM(path)].capacity;
 	d->qid.type = t->mode >> 24;
 	d->qid.type = t->mode >> 24;
 	d->mode = t->mode;
 	d->mode = t->mode;
+	d->name = t->name;
+	lun = fs->aux;
+	if(path == Qdata)
+		d->length = lun->capacity;
+	else
+		d->length = 0;
 }
 }
 
 
 static int
 static int
-dirgen(int i, Dir *d, void*)
+dirgen(Usbfs *fs, Qid, int i, Dir *d, void*)
 {
 {
-	i += Qdir + 1;
-	if(i <= Qn) {
-		dostat(i, d);
-		return 0;
-	}
-	i -= Qn;
-	if(i <= ums.maxlun) {
-		dostat(PATH(Qn, i), d);
+	i++;	/* skip dir */
+	if(i >= Qmax)
+		return -1;
+	else{
+		dostat(fs, i, d);
+		d->qid.path |= fs->qid;
 		return 0;
 		return 0;
 	}
 	}
-	return -1;
 }
 }
 
 
 static int
 static int
-lungen(int i, Dir *d, void *aux)
+dstat(Usbfs *fs, Qid qid, Dir *d)
 {
 {
-	int *c;
+	int path;
 
 
-	c = aux;
-	i += Qn + 1;
-	if(i <= Qdata){
-		dostat(PATH(i, NUM(*c)), d);
-		return 0;
-	}
-	return -1;
-}
-
-void
-rstat(Req *r)
-{
-	dostat((long)r->fid->qid.path, &r->d);
-	respond(r, nil);
+	path = qid.path & ~fs->qid;
+	dostat(fs, path, d);
+	d->qid.path |= fs->qid;
+	return 0;
 }
 }
 
 
-void
-ropen(Req *r)
+static int
+dopen(Usbfs *fs, Fid *fid, int)
 {
 {
 	ulong path;
 	ulong path;
+	Umsc *lun;
 
 
-	path = r->fid->qid.path;
-	switch(TYPE(path)) {
+	path = fid->qid.path & ~fs->qid;
+	lun = fs->aux;
+	switch(path){
 	case Qraw:
 	case Qraw:
-		ums.lun[NUM(path)].phase = Pcmd;
+		lun->phase = Pcmd;
 		break;
 		break;
 	}
 	}
-	respond(r, nil);
+	return 0;
 }
 }
 
 
-void
-rread(Req *r)
+/*
+ * Upon SRread/SRwrite errors we assume the medium may have changed,
+ * and ask again for the capacity of the media.
+ * BUG: How to proceed to avoid confussing dossrv??
+ */
+static long
+dread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
 {
 {
-	int bno, nb, len, offset, n;
+	long bno, nb, len, off, n;
 	ulong path;
 	ulong path;
-	uchar i;
-	char buf[8192], *p;
+	char buf[1024], *p;
+	char *s;
+	char *e;
 	Umsc *lun;
 	Umsc *lun;
-
-	path = r->fid->qid.path;
-	switch(TYPE(path)) {
+	Ums *ums;
+	Qid q;
+
+	q = fid->qid;
+	path = fid->qid.path & ~fs->qid;
+	ums = fs->dev->aux;
+	lun = fs->aux;
+	qlock(ums);
+	switch(path){
 	case Qdir:
 	case Qdir:
-		dirread9p(r, dirgen, 0);
-		break;
-	case Qn:
-		dirread9p(r, lungen, &path);
+		count = usbdirread(fs, q, data, count, offset, dirgen, nil);
 		break;
 		break;
 	case Qctl:
 	case Qctl:
-		n = 0;
-		for(i = 0; i <= ums.maxlun; i++) {
-			lun = &ums.lun[i];
-			n += snprint(buf + n, sizeof buf - n, "%d: ", i);
-			if(lun->flags & Finqok)
-				n += snprint(buf + n, sizeof buf - n,
-					"inquiry %s ", lun->inq);
-			if(lun->blocks > 0)
-				n += snprint(buf + n, sizeof buf - n,
-					"geometry %ld %ld", lun->blocks,
-					lun->lbsize);
-			n += snprint(buf + n, sizeof buf - n, "\n");
-		}
-		readbuf(r, buf, n);
+		e = buf + sizeof(buf);
+		s = seprint(buf, e, "%s lun %ld: ", fs->dev->dir, lun - &ums->lun[0]);
+		if(lun->flags & Finqok)
+			s = seprint(s, e, "inquiry %s ", lun->inq);
+		if(lun->blocks > 0)
+			s = seprint(s, e, "geometry %llud %ld", lun->blocks,
+				lun->lbsize);
+		s = seprint(s, e, "\n");
+		count = usbreadbuf(data, count, offset, buf, s - buf);
 		break;
 		break;
 	case Qraw:
 	case Qraw:
-		lun = &ums.lun[NUM(path)];
-		if(lun->lbsize <= 0) {
-			respond(r, "no media on this lun");
-			return;
+		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
+			qunlock(ums);
+			return -1;
 		}
 		}
-		switch(lun->phase) {
+		switch(lun->phase){
 		case Pcmd:
 		case Pcmd:
-			respond(r, "phase error");
-			return;
+			qunlock(ums);
+			werrstr("phase error");
+			return -1;
 		case Pdata:
 		case Pdata:
-			lun->data.p = (uchar*)r->ofcall.data;
-			lun->data.count = r->ifcall.count;
+			lun->data.p = (uchar*)data;
+			lun->data.count = count;
 			lun->data.write = 0;
 			lun->data.write = 0;
-			n = umsrequest(lun, &lun->cmd, &lun->data, &lun->status);
+			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
 			lun->phase = Pstatus;
 			lun->phase = Pstatus;
-			if (n == -1) {
-				respond(r, "IO error");
-				return;
+			if(count < 0){
+				lun->lbsize = 0;	/* medium may have changed */
+				qunlock(ums);
+				return -1;
 			}
 			}
-			r->ofcall.count = n;
 			break;
 			break;
 		case Pstatus:
 		case Pstatus:
 			n = snprint(buf, sizeof buf, "%11.0ud ", lun->status);
 			n = snprint(buf, sizeof buf, "%11.0ud ", lun->status);
-			if (r->ifcall.count < n)
-				n = r->ifcall.count;
-			memmove(r->ofcall.data, buf, n);
-			r->ofcall.count = n;
+			count = usbreadbuf(data, count, 0LL, buf, n);
 			lun->phase = Pcmd;
 			lun->phase = Pcmd;
 			break;
 			break;
 		}
 		}
 		break;
 		break;
 	case Qdata:
 	case Qdata:
-		lun = &ums.lun[NUM(path)];
-		if(lun->lbsize <= 0) {
-			respond(r, "no media on this lun");
-			return;
+		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
+			qunlock(ums);
+			return -1;
 		}
 		}
-		bno = r->ifcall.offset / lun->lbsize;
-		nb = (r->ifcall.offset + r->ifcall.count + lun->lbsize - 1)
-			/ lun->lbsize - bno;
+		bno = offset / lun->lbsize;
+		nb = (offset + count + lun->lbsize - 1) / lun->lbsize - bno;
 		if(bno + nb > lun->blocks)
 		if(bno + nb > lun->blocks)
 			nb = lun->blocks - bno;
 			nb = lun->blocks - bno;
-		if(bno >= lun->blocks || nb == 0) {
-			r->ofcall.count = 0;
+		if(bno >= lun->blocks || nb == 0){
+			count = 0;
 			break;
 			break;
 		}
 		}
 		if(nb * lun->lbsize > maxiosize)
 		if(nb * lun->lbsize > maxiosize)
 			nb = maxiosize / lun->lbsize;
 			nb = maxiosize / lun->lbsize;
-		p = malloc(nb * lun->lbsize);
-		if (p == 0) {
-			respond(r, "no mem");
-			return;
-		}
-		lun->offset = r->ifcall.offset / lun->lbsize;
+		p = emallocz(nb * lun->lbsize, 0);	/* could use a static buffer */
+		lun->offset = offset / lun->lbsize;
 		n = SRread(lun, p, nb * lun->lbsize);
 		n = SRread(lun, p, nb * lun->lbsize);
-		if(n == -1) {
+		if(n < 0){
 			free(p);
 			free(p);
-			respond(r, "IO error");
-			return;
+			lun->lbsize = 0;	/* medium may have changed */
+			qunlock(ums);
+			return -1;
 		}
 		}
-		len = r->ifcall.count;
-		offset = r->ifcall.offset % lun->lbsize;
-		if(offset + len > n)
-			len = n - offset;
-		r->ofcall.count = len;
-		memmove(r->ofcall.data, p + offset, len);
+		len = count;
+		off = offset % lun->lbsize;
+		if(off + len > n)
+			len = n - off;
+		count = len;
+		memmove(data, p + off, len);
 		free(p);
 		free(p);
 		break;
 		break;
 	}
 	}
-	respond(r, nil);
+	qunlock(ums);
+	return count;
 }
 }
 
 
-void
-rwrite(Req *r)
+static long
+dwrite(Usbfs *fs, Fid *fid, void *buf, long count, vlong offset)
 {
 {
-	int n, bno, nb, len, offset;
+	int bno, nb, len, off;
 	ulong path;
 	ulong path;
 	char *p;
 	char *p;
-	Cmdbuf *cb;
-	Cmdtab *ct;
+	Ums *ums;
 	Umsc *lun;
 	Umsc *lun;
-
-	n = r->ifcall.count;
-	r->ofcall.count = 0;
-	path = r->fid->qid.path;
-	switch(TYPE(path)) {
-	case Qctl:
-		cb = parsecmd(r->ifcall.data, n);
-		ct = lookupcmd(cb, cmdtab, nelem(cmdtab));
-		if(ct == 0) {
-			respondcmderror(r, cb, "%r");
-			return;
-		}
-		switch(ct->index) {
-		case CMreset:
-			umsreset(&ums, 1);
-		}
-		break;
+	char *data;
+
+	ums = fs->dev->aux;
+	lun = fs->aux;
+	path = fid->qid.path & ~fs->qid;
+	data = buf;
+	qlock(ums);
+	switch(path){
+	default:
+		qunlock(ums);
+		werrstr(Eperm);
+		return -1;
 	case Qraw:
 	case Qraw:
-		lun = &ums.lun[NUM(path)];
-		if(lun->lbsize <= 0) {
-			respond(r, "no media on this lun");
-			return;
+		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
+			qunlock(ums);
+			return -1;
 		}
 		}
-		n = r->ifcall.count;
-		switch(lun->phase) {
+		switch(lun->phase){
 		case Pcmd:
 		case Pcmd:
-			if(n != 6 && n != 10) {
-				respond(r, "bad command length");
-				return;
+			if(count != 6 && count != 10){
+				qunlock(ums);
+				werrstr("bad command length");
+				return -1;
 			}
 			}
-			memmove(lun->rawcmd, r->ifcall.data, n);
+			memmove(lun->rawcmd, data, count);
 			lun->cmd.p = lun->rawcmd;
 			lun->cmd.p = lun->rawcmd;
-			lun->cmd.count = n;
+			lun->cmd.count = count;
 			lun->cmd.write = 1;
 			lun->cmd.write = 1;
 			lun->phase = Pdata;
 			lun->phase = Pdata;
 			break;
 			break;
 		case Pdata:
 		case Pdata:
-			lun->data.p = (uchar*)r->ifcall.data;
-			lun->data.count = n;
+			lun->data.p = (uchar*)data;
+			lun->data.count = count;
 			lun->data.write = 1;
 			lun->data.write = 1;
-			n = umsrequest(lun, &lun->cmd, &lun->data, &lun->status);
+			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
 			lun->phase = Pstatus;
 			lun->phase = Pstatus;
-			if(n == -1) {
-				respond(r, "IO error");
-				return;
+			if(count < 0){
+				lun->lbsize = 0;	/* medium may have changed */
+				qunlock(ums);
+				return -1;
 			}
 			}
 			break;
 			break;
 		case Pstatus:
 		case Pstatus:
 			lun->phase = Pcmd;
 			lun->phase = Pcmd;
-			respond(r, "phase error");
-			return;
+			qunlock(ums);
+			werrstr("phase error");
+			return -1;
 		}
 		}
 		break;
 		break;
 	case Qdata:
 	case Qdata:
-		lun = &ums.lun[NUM(path)];
-		if(lun->lbsize <= 0) {
-			respond(r, "no media on this lun");
-			return;
+		if(lun->lbsize <= 0 && umscapacity(lun) < 0){
+			qunlock(ums);
+			return -1;
 		}
 		}
-		bno = r->ifcall.offset / lun->lbsize;
-		nb = (r->ifcall.offset + r->ifcall.count + lun->lbsize-1)
-			/ lun->lbsize - bno;
+		bno = offset / lun->lbsize;
+		nb = (offset + count + lun->lbsize-1) / lun->lbsize - bno;
 		if(bno + nb > lun->blocks)
 		if(bno + nb > lun->blocks)
 			nb = lun->blocks - bno;
 			nb = lun->blocks - bno;
-		if(bno >= lun->blocks || nb == 0) {
-			r->ofcall.count = 0;
+		if(bno >= lun->blocks || nb == 0){
+			count = 0;
 			break;
 			break;
 		}
 		}
 		if(nb * lun->lbsize > maxiosize)
 		if(nb * lun->lbsize > maxiosize)
 			nb = maxiosize / lun->lbsize;
 			nb = maxiosize / lun->lbsize;
-		p = malloc(nb * lun->lbsize);
-		if(p == 0) {
-			respond(r, "no mem");
-			return;
-		}
-		offset = r->ifcall.offset % lun->lbsize;
-		len = r->ifcall.count;
-		if(offset || (len % lun->lbsize) != 0) {
-			lun->offset = r->ifcall.offset / lun->lbsize;
-			n = SRread(lun, p, nb * lun->lbsize);
-			if(n == -1) {
+		p = emallocz(nb * lun->lbsize, 0);
+		off = offset % lun->lbsize;
+		len = count;
+		if(off || (len % lun->lbsize) != 0){
+			lun->offset = offset / lun->lbsize;
+			count = SRread(lun, p, nb * lun->lbsize);
+			if(count < 0){
 				free(p);
 				free(p);
-				respond(r, "IO error");
-				return;
+				lun->lbsize = 0;	/* medium may have changed */
+				qunlock(ums);
+				return -1;
 			}
 			}
-			if(offset + len > n)
-				len = n - offset;
+			if(off + len > count)
+				len = count - off;
 		}
 		}
-		memmove(p+offset, r->ifcall.data, len);
-		lun->offset = r->ifcall.offset / lun->lbsize;
-		n = SRwrite(lun, p, nb * lun->lbsize);
-		if(n == -1) {
+		memmove(p+off, data, len);
+		lun->offset = offset / lun->lbsize;
+		count = SRwrite(lun, p, nb * lun->lbsize);
+		if(count < 0){
 			free(p);
 			free(p);
-			respond(r, "IO error");
-			return;
+			lun->lbsize = 0;	/* medium may have changed */
+			qunlock(ums);
+			return -1;
 		}
 		}
-		if(offset+len > n)
-			len = n - offset;
-		r->ofcall.count = len;
+		if(off+len > count)
+			len = count - off;
+		count = len;
 		free(p);
 		free(p);
 		break;
 		break;
 	}
 	}
-	r->ofcall.count = n;
-	respond(r, nil);
+	qunlock(ums);
+	return count;
 }
 }
 
 
-Srv usbssrv = {
-	.attach = rattach,
-	.walk1 = rwalk1,
-	.open =	 ropen,
-	.read =	 rread,
-	.write = rwrite,
-	.stat =	 rstat,
-};
+int
+findendpoints(Ums *ums)
+{
+	Ep *ep;
+	Usbdev *ud;
+	ulong csp;
+	ulong sc;
+	int i;
+	int epin, epout;
+
+	epin = epout = -1;
+	ud = ums->dev->usb;
+	for(i = 0; i < nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			continue;
+		csp = ep->iface->csp;
+		sc = Subclass(csp);
+		if(!(Class(csp) == Clstorage && (Proto(csp) == Protobulk)))
+			continue;
+		if(sc != Subatapi && sc != Sub8070 && sc != Subscsi)
+			fprint(2, "disk: subclass %#ulx not supported. trying anyway\n", sc);
+		if(ep->type == Ebulk){
+			if(ep->dir == Eboth || ep->dir == Ein)
+				if(epin == -1)
+					epin =  ep->id;
+			if(ep->dir == Eboth || ep->dir == Eout)
+				if(epout == -1)
+					epout = ep->id;
+		}
+	}
+	dprint(2, "disk: ep ids: in %d out %d\n", epin, epout);
+	if(epin == -1 || epout == -1)
+		return -1;
+	ums->epin = openep(ums->dev, epin);
+	if(ums->epin == nil){
+		fprint(2, "disk: openep %d: %r\n", epin);
+		return -1;
+	}
+	if(epout == epin){
+		incref(ums->epin);
+		ums->epout = ums->epin;
+	}else
+		ums->epout = openep(ums->dev, epout);
+	if(ums->epout == nil){
+		fprint(2, "disk: openep %d: %r\n", epout);
+		closedev(ums->epin);
+		return -1;
+	}
+	if(ums->epin == ums->epout)
+		opendevdata(ums->epin, ORDWR);
+	else{
+		opendevdata(ums->epin, OREAD);
+		opendevdata(ums->epout, OWRITE);
+	}
+	if(ums->epin->dfd < 0 || ums->epout->dfd < 0){
+		fprint(2, "disk: open i/o ep data: %r\n");
+		closedev(ums->epin);
+		closedev(ums->epout);
+		return -1;
+	}
+	dprint(2, "disk: ep in %s out %s\n", ums->epin->dir, ums->epout->dir);
 
 
-void (*dprinter[])(Device *, int, ulong, void *b, int n) = {
-	[STRING] pstring,
-	[DEVICE] pdevice,
-};
+	if(usbdebug > 1 || diskdebug > 2){
+		devctl(ums->epin, "debug 1");
+		devctl(ums->epout, "debug 1");
+		devctl(ums->dev, "debug 1");
+	}
+	return 0;
+}
 
 
-void
+static int
 usage(void)
 usage(void)
 {
 {
-	fprint(2, "usage: %s [-Ddfl] [-m mountpoint] [-s srvname] [ctrno id]\n",
-		argv0);
-	exits("usage");
+	werrstr("usage: usb/disk [-d]");
+	return -1;
 }
 }
 
 
-void
-main(int argc, char **argv)
+static void
+umsdevfree(void *a)
 {
 {
-	int ctlrno, id;
-	char *srvname, *mntname;
+	Ums *ums = a;
+
+	if(ums == nil)
+		return;
+	closedev(ums->epin);
+	closedev(ums->epout);
+	ums->epin = ums->epout = nil;
+	free(ums->lun);
+	free(ums);
+}
 
 
-	mntname = "/n/disk";
-	srvname = nil;
-	ctlrno = 0;
-	id = 1;
+static Usbfs diskfs = {
+	.walk = dwalk,
+	.open =	 dopen,
+	.read =	 dread,
+	.write = dwrite,
+	.stat =	 dstat,
+};
+
+int
+diskmain(Dev *dev, int argc, char **argv)
+{
+	Ums *ums;
+	Umsc *lun;
+	int i;
 
 
 	ARGBEGIN{
 	ARGBEGIN{
 	case 'd':
 	case 'd':
-		debug = Dbginfo;
-		break;
-	case 'f':
-		freakout++;
-		break;
-	case 'l':
-		needmaxlun++;
-		break;
-	case 'm':
-		mntname = EARGF(usage());
-		break;
-	case 's':
-		srvname = EARGF(usage());
-		break;
-	case 'D':
-		++chatty9p;
+		scsidebug(diskdebug);
+		diskdebug++;
 		break;
 		break;
 	default:
 	default:
-		usage();
+		return usage();
 	}ARGEND
 	}ARGEND
+	if(argc != 0)
+		return usage();
+
+	ums = dev->aux = emallocz(sizeof(Ums), 1);
+	ums->maxlun = -1;
+	ums->dev = dev;
+	dev->free = umsdevfree;
+	if(findendpoints(ums) < 0){
+		werrstr("disk: endpoints not found");
+		return -1;
+	}
+	if(umsinit(ums) < 0){
+		dprint(2, "disk: umsinit: %r\n");
+		return -1;
+	}
 
 
-	ums.ctlfd = ums.setupfd = ums.fd = ums.fd2 = -1;
-	ums.maxlun = -1;
-	if (argc == 0) {
-		if (findums(&ctlrno, &id) < 0) {
-			sleep(5*1000);
-			if (findums(&ctlrno, &id) < 0)
-				sysfatal("no usb mass storage device found");
-		}
-	} else if (argc == 2 && isdigit(argv[0][0]) && isdigit(argv[1][0])) {
-		ctlrno = atoi(argv[0]);
-		id = atoi(argv[1]);
-		if (!devokay(ctlrno, id))
-			sysfatal("no usable usb mass storage device at %d/%d",
-				ctlrno, id);
-	} else
-		usage();
-
-	starttime = time(0);
-	owner = getuser();
-
-	postmountsrv(&usbssrv, srvname, mntname, 0);
-	exits(0);
+	for(i = 0; i <= ums->maxlun; i++){
+		lun = &ums->lun[i];
+		lun->fs = diskfs;
+		snprint(lun->fs.name, sizeof(lun->fs.name), "sdU%d.%d", dev->id, i);
+		lun->fs.dev = dev;
+		incref(dev);
+		lun->fs.aux = lun;
+		usbfsadd(&lun->fs);
+	}
+	closedev(dev);
+	return 0;
 }
 }

+ 70 - 0
sys/src/cmd/usb/disk/main.c

@@ -0,0 +1,70 @@
+/*
+ * usb/disk - usb mass storage file server
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include "scsireq.h"
+#include "usb.h"
+#include "usbfs.h"
+#include "ums.h"
+
+enum
+{
+	Arglen = 80,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-Dd] [-m mnt] [-s srv] [dev...]\n", argv0);
+	threadexitsall("usage");
+}
+
+static int csps[] = {
+	CSP(Clstorage,Subatapi,Protobulk),
+	CSP(Clstorage,Sub8070,Protobulk),
+	CSP(Clstorage,Subscsi,Protobulk),
+	0,
+};
+
+void
+threadmain(int argc, char **argv)
+{
+	char args[Arglen];
+	char *as;
+	char *ae;
+	char *srv;
+	char *mnt;
+
+	srv = nil;
+	mnt = "/n/disk";
+
+	quotefmtinstall();
+	ae = args+sizeof(args);
+	as = seprint(args, ae, "disk");
+	ARGBEGIN{
+	case 'D':
+		usbfsdebug++;
+		break;
+	case 'd':
+		usbdebug++;
+		as = seprint(as, ae, " -d");
+		break;
+	case 'm':
+		mnt = EARGF(usage());
+		break;
+	case 's':
+		srv = EARGF(usage());
+	default:
+		usage();
+	}ARGEND
+
+	rfork(RFNOTEG);
+	threadsetgrp(threadid());
+	fmtinstall('U', Ufmt);
+	usbfsinit(srv, mnt, &usbdirfs, MBEFORE);
+	startdevs(args, argv, argc, matchdevcsp, csps, diskmain);
+	threadexits(nil);
+}

+ 30 - 10
sys/src/cmd/usb/disk/mkfile

@@ -1,21 +1,41 @@
 </$objtype/mkfile
 </$objtype/mkfile
 
 
 TARG=disk
 TARG=disk
-LIBDIR=../lib
-HFILES = /sys/src/cmd/scuzz/scsireq.h $LIBDIR/usb.h
-OFILES = disk.$O scsireq.$O
-LIB=$LIBDIR/usb.a$O
+OFILES=\
+	main.$O
+
+LIBDOFILES=\
+	disk.$O\
+	scsireq.$O\
+	scsierrs.$O\
+
+HFILES =\
+	scsireq.h\
+	../lib/usb.h\
+	../lib/usbfs.h\
+	ums.h\
+
+LIBD=../lib/usbdev.a$O
+LIBU=../lib/usb.a$O
+LIB=\
+	$LIBD\
+	$LIBU\
 
 
 BIN=/$objtype/bin/usb
 BIN=/$objtype/bin/usb
 
 
 </sys/src/cmd/mkone
 </sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
+CLEANFILES=scsierrs.c
 
 
-CFLAGS=-I$LIBDIR -I/sys/src/cmd/scuzz $CFLAGS
-
-$LIB:
-	cd $LIBDIR
+$LIBU:
+	cd ../lib
 	mk install
 	mk install
 	mk clean
 	mk clean
 
 
-scsireq.$O: /sys/src/cmd/scuzz/scsireq.c
-	$CC $CFLAGS /sys/src/cmd/scuzz/scsireq.c
+$LIBD:V: $LIBDOFILES
+	ar vu $LIBD $newprereq
+	rm $newprereq
+
+scsierrs.c: /sys/lib/scsicodes mkscsierrs
+	mkscsierrs >scsierrs.c
+

+ 32 - 0
sys/src/cmd/usb/disk/mkscsierrs

@@ -0,0 +1,32 @@
+#!/bin/rc
+
+cat <<EOF
+#include <u.h>
+#include <libc.h>
+
+typedef struct Err Err;
+struct Err
+{
+	int n;
+	char *s;
+};
+
+static Err scsierrs[] = {
+EOF
+
+grep '^[0-9a-c][0-9a-c][0-9a-c][0-9a-c][ 	]' /sys/lib/scsicodes |
+	sed -e 's/^(....) (.*)/	{0x\1,	"\2"},\n/'
+cat <<EOF
+};
+
+char*
+scsierrmsg(int n)
+{
+	int i;
+
+	for(i = 0; i < nelem(scsierrs); i++)
+		if(scsierrs[i].n == n)
+			return scsierrs[i].s;
+	return "scsi error";
+}
+EOF

+ 949 - 0
sys/src/cmd/usb/disk/scsireq.c

@@ -0,0 +1,949 @@
+/*
+ * This is /sys/src/cmd/scuzz/scsireq.c
+ * changed to add more debug support, to keep
+ * disk compiling without a scuzz that includes these changes.
+ * Also, this includes minor tweaks for usb:
+ *	we set req.lun/unit to rp->lun/unit in SRreqsense
+ *	we set the rp->sense[0] bit Sd0valid in SRreqsense
+ * This does not use libdisk to retrieve the scsi error to make
+ * user we see the diagnostics if we boot with debug enabled.
+ * 
+ */
+
+#include <u.h>
+#include <libc.h>
+/*
+ * BUGS:
+ *	no luns
+ *	and incomplete in many other ways
+ */
+#include "scsireq.h"
+
+enum {
+	Debug = 0,
+};
+
+/*
+ * exabyte tape drives, at least old ones like the 8200 and 8505,
+ * are dumb: you have to read the exact block size on the tape,
+ * they don't take 10-byte SCSI commands, and various other fine points.
+ */
+extern int exabyte, force6bytecmds;
+
+static int debug = Debug;
+
+static char *scmdnames[256] = {
+[ScmdTur]	"Tur",
+[ScmdRewind]	"Rewind",
+[ScmdRsense]	"Rsense",
+[ScmdFormat]	"Format",
+[ScmdRblimits]	"Rblimits",
+[ScmdRead]	"Read",
+[ScmdWrite]	"Write",
+[ScmdSeek]	"Seek",
+[ScmdFmark]	"Fmark",
+[ScmdSpace]	"Space",
+[ScmdInq]	"Inq",
+[ScmdMselect6]	"Mselect6",
+[ScmdMselect10]	"Mselect10",
+[ScmdMsense6]	"Msense6",
+[ScmdMsense10]	"Msense10",
+[ScmdStart]	"Start",
+[ScmdRcapacity]	"Rcapacity",
+[ScmdRcapacity16]	"Rcap16",
+[ScmdExtread]	"Extread",
+[ScmdExtwrite]	"Extwrite",
+[ScmdExtseek]	"Extseek",
+
+[ScmdSynccache]	"Synccache",
+[ScmdRTOC]	"RTOC",
+[ScmdRdiscinfo]	"Rdiscinfo",
+[ScmdRtrackinfo]	"Rtrackinfo",
+[ScmdReserve]	"Reserve",
+[ScmdBlank]	"Blank",
+
+[ScmdCDpause]	"CDpause",
+[ScmdCDstop]	"CDstop",
+[ScmdCDplay]	"CDplay",
+[ScmdCDload]	"CDload",
+[ScmdCDscan]	"CDscan",
+[ScmdCDstatus]	"CDstatus",
+[Scmdgetconf]	"getconf",
+};
+
+long
+SRready(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRrewind(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRewind;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	if(SRrequest(rp) >= 0){
+		rp->offset = 0;
+		return 0;
+	}
+	return -1;
+}
+
+long
+SRreqsense(ScsiReq *rp)
+{
+	uchar cmd[6];
+	ScsiReq req;
+	long status;
+
+	if(rp->status == Status_SD){
+		rp->status = STok;
+		return 0;
+	}
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRsense;
+	cmd[4] = sizeof(req.sense);
+	memset(&req, 0, sizeof(req));
+	if(rp->flags&Fusb)
+		req.flags |= Fusb;
+	req.lun = rp->lun;
+	req.unit = rp->unit;
+	req.fd = rp->fd;
+	req.umsc = rp->umsc;
+	req.cmd.p = cmd;
+	req.cmd.count = sizeof cmd;
+	req.data.p = rp->sense;
+	req.data.count = sizeof(rp->sense);
+	req.data.write = 0;
+	status = SRrequest(&req);
+	rp->status = req.status;
+	if(status != -1)
+		rp->sense[0] |= Sd0valid;
+	return status;
+}
+
+long
+SRformat(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdFormat;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 6;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRrblimits(ScsiReq *rp, uchar *list)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRblimits;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = 6;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+static int
+dirdevrw(ScsiReq *rp, uchar *cmd, long nbytes)
+{
+	long n;
+
+	n = nbytes / rp->lbsize;
+	if(rp->offset <= Max24off && n <= 256 && (rp->flags & Frw10) == 0){
+		PUTBE24(cmd+1, rp->offset);
+		cmd[4] = n;
+		cmd[5] = 0;
+		return 6;
+	}
+	cmd[0] |= ScmdExtread;
+	cmd[1] = 0;
+	PUTBELONG(cmd+2, rp->offset);
+	cmd[6] = 0;
+	cmd[7] = n>>8;
+	cmd[8] = n;
+	cmd[9] = 0;
+	return 10;
+}
+
+static int
+seqdevrw(ScsiReq *rp, uchar *cmd, long nbytes)
+{
+	long n;
+
+	/* don't set Cmd1sili; we want the ILI bit instead of a fatal error */
+	cmd[1] = rp->flags&Fbfixed? Cmd1fixed: 0;
+	n = nbytes / rp->lbsize;
+	PUTBE24(cmd+2, n);
+	cmd[5] = 0;
+	return 6;
+}
+
+long
+SRread(ScsiReq *rp, void *buf, long nbytes)
+{
+	uchar cmd[10];
+	long n;
+
+	if((nbytes % rp->lbsize) || nbytes > maxiosize){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+
+	/* set up scsi read cmd */
+	cmd[0] = ScmdRead;
+	if(rp->flags & Fseqdev)
+		rp->cmd.count = seqdevrw(rp, cmd, nbytes);
+	else
+		rp->cmd.count = dirdevrw(rp, cmd, nbytes);
+	rp->cmd.p = cmd;
+	rp->data.p = buf;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+
+	/* issue it */
+	n = SRrequest(rp);
+	if(n != -1){			/* it worked? */
+		rp->offset += n / rp->lbsize;
+		return n;
+	}
+
+	/* request failed; maybe we just read a short record? */
+	if (exabyte) {
+		fprint(2, "read error\n");
+		rp->status = STcheck;
+		return n;
+	}
+	if(rp->status != Status_SD || !(rp->sense[0] & Sd0valid))
+		return -1;
+	/* compute # of bytes not read */
+	n = GETBELONG(rp->sense+3) * rp->lbsize;
+	if(!(rp->flags & Fseqdev))
+		return -1;
+
+	/* device is a tape or something similar */
+	if (rp->sense[2] == Sd2filemark || rp->sense[2] == 0x08 ||
+	    rp->sense[2] & Sd2ili && n > 0)
+		rp->data.count = nbytes - n;
+	else
+		return -1;
+	n = rp->data.count;
+	if (!rp->readblock++ || debug)
+		fprint(2, "SRread: tape data count %ld%s\n", n,
+			(rp->sense[2] & Sd2ili? " with ILI": ""));
+	rp->status = STok;
+	rp->offset += n / rp->lbsize;
+	return n;
+}
+
+long
+SRwrite(ScsiReq *rp, void *buf, long nbytes)
+{
+	uchar cmd[10];
+	long n;
+
+	if((nbytes % rp->lbsize) || nbytes > maxiosize){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+
+	/* set up scsi write cmd */
+	cmd[0] = ScmdWrite;
+	if(rp->flags & Fseqdev)
+		rp->cmd.count = seqdevrw(rp, cmd, nbytes);
+	else
+		rp->cmd.count = dirdevrw(rp, cmd, nbytes);
+	rp->cmd.p = cmd;
+	rp->data.p = buf;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+
+	/* issue it */
+	if((n = SRrequest(rp)) == -1){
+		if (exabyte) {
+			fprint(2, "write error\n");
+			rp->status = STcheck;
+			return n;
+		}
+		if(rp->status != Status_SD || rp->sense[2] != Sd2eom)
+			return -1;
+		if(rp->sense[0] & Sd0valid){
+			n -= GETBELONG(rp->sense+3) * rp->lbsize;
+			rp->data.count = nbytes - n;
+		}
+		else
+			rp->data.count = nbytes;
+		n = rp->data.count;
+	}
+	rp->offset += n / rp->lbsize;
+	return n;
+}
+
+long
+SRseek(ScsiReq *rp, long offset, int type)
+{
+	uchar cmd[10];
+
+	switch(type){
+
+	case 0:
+		break;
+
+	case 1:
+		offset += rp->offset;
+		if(offset >= 0)
+			break;
+		/*FALLTHROUGH*/
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(cmd, 0, sizeof cmd);
+	if(offset <= Max24off && (rp->flags & Frw10) == 0){
+		cmd[0] = ScmdSeek;
+		PUTBE24(cmd+1, offset & Max24off);
+		rp->cmd.count = 6;
+	}else{
+		cmd[0] = ScmdExtseek;
+		PUTBELONG(cmd+2, offset);
+		rp->cmd.count = 10;
+	}
+	rp->cmd.p = cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	SRrequest(rp);
+	if(rp->status == STok)
+		return rp->offset = offset;
+	return -1;
+}
+
+long
+SRfilemark(ScsiReq *rp, ulong howmany)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdFmark;
+	PUTBE24(cmd+2, howmany);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRspace(ScsiReq *rp, uchar code, long howmany)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdSpace;
+	cmd[1] = code;
+	PUTBE24(cmd+2, howmany);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	/*
+	 * what about rp->offset?
+	 */
+	return SRrequest(rp);
+}
+
+long
+SRinquiry(ScsiReq *rp)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdInq;
+	cmd[4] = sizeof rp->inquiry;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	memset(rp->inquiry, 0, sizeof rp->inquiry);
+	rp->data.p = rp->inquiry;
+	rp->data.count = sizeof rp->inquiry;
+	rp->data.write = 0;
+	if(SRrequest(rp) >= 0){
+		rp->flags |= Finqok;
+		return 0;
+	}
+	rp->flags &= ~Finqok;
+	return -1;
+}
+
+long
+SRmodeselect6(ScsiReq *rp, uchar *list, long nbytes)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMselect6;
+	if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
+		cmd[1] = 0x10;
+	cmd[4] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRmodeselect10(ScsiReq *rp, uchar *list, long nbytes)
+{
+	uchar cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
+		cmd[1] = 0x10;
+	cmd[0] = ScmdMselect10;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRmodesense6(ScsiReq *rp, uchar page, uchar *list, long nbytes)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMsense6;
+	cmd[2] = page;
+	cmd[4] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRmodesense10(ScsiReq *rp, uchar page, uchar *list, long nbytes)
+{
+	uchar cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMsense10;
+	cmd[2] = page;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRstart(ScsiReq *rp, uchar code)
+{
+	uchar cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdStart;
+	cmd[4] = code;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+long
+SRrcapacity(ScsiReq *rp, uchar *data)
+{
+	uchar cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRcapacity;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = data;
+	rp->data.count = 8;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+long
+SRrcapacity16(ScsiReq *rp, uchar *data)
+{
+	uchar cmd[16];
+	uint i;
+
+	i = 32;
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRcapacity16;
+	cmd[1] = 0x10;
+	cmd[10] = i>>24;
+	cmd[11] = i>>16;
+	cmd[12] = i>>8;
+	cmd[13] = i;
+
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = data;
+	rp->data.count = i;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+void
+scsidebug(int d)
+{
+	debug = d;
+	if(debug)
+		fprint(2, "scsidebug on\n");
+}
+
+static long
+request(int fd, ScsiPtr *cmd, ScsiPtr *data, int *status)
+{
+	long n, r;
+	char buf[16];
+
+	/* this was an experiment but it seems to be a good idea */
+	*status = STok;
+
+	/* send SCSI command */
+	if(write(fd, cmd->p, cmd->count) != cmd->count){
+		fprint(2, "scsireq: write cmd: %r\n");
+		*status = Status_SW;
+		return -1;
+	}
+
+	/* read or write actual data */
+	werrstr("");
+	if(data->write)
+		n = write(fd, data->p, data->count);
+	else {
+		n = read(fd, data->p, data->count);
+		if (n < 0)
+			memset(data->p, 0, data->count);
+		else if (n < data->count)
+			memset(data->p + n, 0, data->count - n);
+	}
+	if (n != data->count && n <= 0) {
+		if (debug)
+			fprint(2,
+	"request: tried to %s %ld bytes of data for cmd 0x%x but got %r\n",
+				(data->write? "write": "read"),
+				data->count, cmd->p[0]);
+	} else if (n != data->count && (data->write || debug))
+		fprint(2, "request: %s %ld of %ld bytes of actual data\n",
+			(data->write? "wrote": "read"), n, data->count);
+
+	/* read status */
+	buf[0] = '\0';
+	r = read(fd, buf, sizeof buf-1);
+	if(exabyte && r <= 0 || !exabyte && r < 0){
+		fprint(2, "scsireq: read status: %r\n");
+		*status = Status_SW;
+		return -1;
+	}
+	if (r >= 0)
+		buf[r] = '\0';
+	*status = atoi(buf);
+	if(n < 0 && (exabyte || *status != STcheck))
+		fprint(2, "scsireq: status 0x%2.2uX: data transfer: %r\n",
+			*status);
+	return n;
+}
+
+static char*
+seprintcmd(char *s, char* e, char *cmd, int count, int args)
+{
+	uint c;
+
+	if(count < 6)
+		return seprint(s, e, "<short cmd>");
+	c = cmd[0];
+	if(scmdnames[c] != nil)
+		s = seprint(s, e, "%s", scmdnames[c]);
+	else
+		s = seprint(s, e, "cmd:%#02uX", c);
+	if(args != 0)
+		switch(c){
+		case ScmdRsense:
+		case ScmdInq:
+		case ScmdMselect6:
+		case ScmdMsense6:
+			s = seprint(s, e, " sz %d", cmd[4]);
+			break;
+		case ScmdSpace:
+			s = seprint(s, e, " code %d", cmd[1]);
+			break;
+		case ScmdStart:
+			s = seprint(s, e, " code %d", cmd[4]);
+			break;
+	
+		}
+	return s;
+}
+
+static char*
+seprintdata(char *s, char *se, uchar *p, int count)
+{
+	int i;
+
+	if(count == 0)
+		return s;
+	for(i = 0; i < 20 && i < count; i++)
+		s = seprint(s, se, " %02x", p[i]);
+	return s;
+}
+
+static void
+SRdumpReq(ScsiReq *rp)
+{
+	char buf[128];
+	char *s;
+	char *se;
+
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "lun %d ", rp->lun);
+	s = seprintcmd(s, se, (char*)rp->cmd.p, rp->cmd.count, 1);
+	s = seprint(s, se, " [%ld]", rp->data.count);
+	if(rp->cmd.write)
+		seprintdata(s, se, rp->data.p, rp->data.count);
+	fprint(2, "scsi⇒ %s\n", buf);
+}
+
+static void
+SRdumpRep(ScsiReq *rp)
+{
+	char buf[128];
+	char *s;
+	char *se;
+
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "lun %d ", rp->lun);
+	s = seprintcmd(s, se, (char*)rp->cmd.p, rp->cmd.count, 0);
+	switch(rp->status){
+	case STok:
+		s = seprint(s, se, " good [%ld] ", rp->data.count);
+		if(rp->cmd.write == 0)
+			s = seprintdata(s, se, rp->data.p, rp->data.count);
+		break;
+	case STnomem:
+		s = seprint(s, se, " buffer allocation failed");
+		break;
+	case STharderr:
+		s = seprint(s, se, " controller error");
+		break;
+	case STtimeout:
+		s = seprint(s, se, " bus timeout");
+		break;
+	case STcheck:
+		s = seprint(s, se, " check condition");
+		break;
+	case STcondmet:
+		s = seprint(s, se, " condition met/good");
+		break;
+	case STbusy:
+		s = seprint(s, se, " busy");
+		break;
+	case STintok:
+		s = seprint(s, se, " intermediate/good");
+		break;
+	case STintcondmet:
+		s = seprint(s, se, " intermediate/condition met/good");
+		break;
+	case STresconf:
+		s = seprint(s, se, " reservation conflict");
+		break;
+	case STterminated:
+		s = seprint(s, se, " command terminated");
+		break;
+	case STqfull:
+		s = seprint(s, se, " queue full");
+		break;
+	default:
+		s = seprint(s, se, " sts=%#x", rp->status);
+	}
+	USED(s);
+	fprint(2, "scsi← %s\n", buf);
+}
+
+static char*
+scsierr(ScsiReq *rp)
+{
+	int ec;
+
+	switch(rp->status){
+	case 0:
+		return "";
+	case Status_SD:
+		ec = (rp->sense[12] << 8) | rp->sense[13];
+		return scsierrmsg(ec);
+	case Status_SW:
+		return "software error";
+	case Status_BADARG:
+		return "bad argument";
+	case Status_RO:
+		return "device is read only";
+	default:
+		return "unknown";
+	}
+}
+
+static void
+SRdumpErr(ScsiReq *rp)
+{
+	char buf[128];
+	char *se;
+
+	se = buf+sizeof(buf);
+	seprintcmd(buf, se, (char*)rp->cmd.p, rp->cmd.count, 0);
+	print("\t%s status: %s\n", buf, scsierr(rp));
+}
+
+long
+SRrequest(ScsiReq *rp)
+{
+	long n;
+	int status;
+
+retry:
+	if(debug)
+		SRdumpReq(rp);
+	if(rp->flags&Fusb)
+		n = umsrequest(rp->umsc, &rp->cmd, &rp->data, &status);
+	else
+		n = request(rp->fd, &rp->cmd, &rp->data, &status);
+	rp->status = status;
+	if(status == STok)
+		rp->data.count = n;
+	if(debug)
+		SRdumpRep(rp);
+	switch(status){
+	case STok:
+		break;
+	case STcheck:
+		if(rp->cmd.p[0] != ScmdRsense && SRreqsense(rp) != -1)
+			rp->status = Status_SD;
+		if(debug || exabyte)
+			SRdumpErr(rp);
+		werrstr("%s", scsierr(rp));
+		return -1;
+	case STbusy:
+		sleep(1000);
+		goto retry;
+	default:
+		if(debug || exabyte)
+			SRdumpErr(rp);
+		werrstr("%s", scsierr(rp));
+		return -1;
+	}
+	return n;
+}
+
+int
+SRclose(ScsiReq *rp)
+{
+	if((rp->flags & Fopen) == 0){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	close(rp->fd);
+	rp->flags = 0;
+	return 0;
+}
+
+static int
+dirdevopen(ScsiReq *rp)
+{
+	ulong blocks;
+	uchar data[8];
+
+	if(SRstart(rp, 1) == -1 || SRrcapacity(rp, data) == -1)
+		return -1;
+	rp->lbsize = GETBELONG(data+4);
+	blocks =     GETBELONG(data);
+	if(blocks == 0xffffffff){
+		if(SRrcapacity16(rp, data) == -1)
+			return -1;
+		rp->lbsize = GETBELONG(data + 8);
+		blocks = (vlong)GETBELONG(data)<<32 | GETBELONG(data + 4);
+	}
+	/* some newer dev's don't support 6-byte commands */
+	if(blocks > Max24off && !force6bytecmds)
+		rp->flags |= Frw10;
+	return 0;
+}
+
+static int
+seqdevopen(ScsiReq *rp)
+{
+	uchar mode[16], limits[6];
+
+	if(SRrblimits(rp, limits) == -1)
+		return -1;
+	if(limits[1] == 0 && limits[2] == limits[4] && limits[3] == limits[5]){
+		rp->flags |= Fbfixed;
+		rp->lbsize = limits[4]<<8 | limits[5];
+		return 0;
+	}
+	/*
+	 * On some older hardware the optional 10-byte
+	 * modeselect command isn't implemented.
+	 */
+	if (force6bytecmds)
+		rp->flags |= Fmode6;
+	if(!(rp->flags & Fmode6)){
+		/* try 10-byte command first */
+		memset(mode, 0, sizeof mode);
+		mode[3] = 0x10;		/* device-specific param. */
+		mode[7] = 8;		/* block descriptor length */
+		/*
+		 * exabytes can't handle this, and
+		 * modeselect(10) is optional.
+		 */
+		if(SRmodeselect10(rp, mode, sizeof mode) != -1){
+			rp->lbsize = 1;
+			return 0;	/* success */
+		}
+		/* can't do 10-byte commands, back off to 6-byte ones */
+		rp->flags |= Fmode6;
+	}
+
+	/* 6-byte command */
+	memset(mode, 0, sizeof mode);
+	mode[2] = 0x10;		/* device-specific param. */
+	mode[3] = 8;		/* block descriptor length */
+	/*
+	 * bsd sez exabytes need this bit (NBE: no busy enable) in
+	 * vendor-specific page (0), but so far we haven't needed it.
+	mode[12] |= 8;
+	 */
+	if(SRmodeselect6(rp, mode, 4+8) == -1)
+		return -1;
+	rp->lbsize = 1;
+	return 0;
+}
+
+static int
+wormdevopen(ScsiReq *rp)
+{
+	long status;
+	uchar list[MaxDirData];
+
+	if (SRstart(rp, 1) == -1 ||
+	    (status = SRmodesense10(rp, Allmodepages, list, sizeof list)) == -1)
+		return -1;
+	/* nbytes = list[0]<<8 | list[1]; */
+
+	/* # of bytes of block descriptors of 8 bytes each; not even 1? */
+	if((list[6]<<8 | list[7]) < 8)
+		rp->lbsize = 2048;
+	else
+		/* last 3 bytes of block 0 descriptor */
+		rp->lbsize = GETBE24(list+13);
+	return status;
+}
+
+int
+SRopenraw(ScsiReq *rp, char *unit)
+{
+	char name[128];
+
+	if(rp->flags & Fopen){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(rp, 0, sizeof *rp);
+	rp->unit = unit;
+
+	sprint(name, "%s/raw", unit);
+
+	if((rp->fd = open(name, ORDWR)) == -1){
+		rp->status = STtimeout;
+		return -1;
+	}
+	rp->flags = Fopen;
+	return 0;
+}
+
+int
+SRopen(ScsiReq *rp, char *unit)
+{
+	if(SRopenraw(rp, unit) == -1)
+		return -1;
+	SRready(rp);
+	if(SRinquiry(rp) >= 0){
+		switch(rp->inquiry[0]){
+
+		default:
+			fprint(2, "unknown device type 0x%.2x\n", rp->inquiry[0]);
+			rp->status = Status_SW;
+			break;
+
+		case 0x00:	/* Direct access (disk) */
+		case 0x05:	/* CD-ROM */
+		case 0x07:	/* rewriteable MO */
+			if(dirdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case 0x01:	/* Sequential eg: tape */
+			rp->flags |= Fseqdev;
+			if(seqdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case 0x02:	/* Printer */
+			rp->flags |= Fprintdev;
+			return 0;
+
+		case 0x04:	/* Worm */
+			rp->flags |= Fwormdev;
+			if(wormdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case 0x08:	/* medium-changer */
+			rp->flags |= Fchanger;
+			return 0;
+		}
+	}
+	SRclose(rp);
+	return -1;
+}

+ 223 - 0
sys/src/cmd/usb/disk/scsireq.h

@@ -0,0 +1,223 @@
+/*
+ * This is /sys/src/cmd/scuzz/scsireq.c
+ * changed to add more debug support, to keep
+ * disk compiling without a scuzz that includes these changes.
+ */
+/* this file is also included by usb/disk and cdfs */
+typedef struct Umsc Umsc;
+#pragma incomplete Umsc
+
+enum {					/* fundamental constants/defaults */
+	NTargetID	= 8,		/* number of target IDs */
+	CtlrID		= 7,		/* default controller target ID */
+	MaxDirData	= 255,		/* max. direct data returned */
+	LBsize		= 512,		/* default logical-block size */
+};
+
+typedef struct {
+	uchar	*p;
+	long	count;
+	uchar	write;
+} ScsiPtr;
+
+typedef struct {
+	int	flags;
+	char	*unit;			/* unit directory */
+	int	lun;
+	ulong	lbsize;
+	ulong	offset;			/* in blocks of lbsize bytes */
+	int	fd;
+	Umsc	*umsc;			/* lun */
+	ScsiPtr	cmd;
+	ScsiPtr	data;
+	int	status;			/* returned status */
+	uchar	sense[MaxDirData];	/* returned sense data */
+	uchar	inquiry[MaxDirData];	/* returned inquiry data */
+	int	readblock;		/* flag: read a block since open */
+} ScsiReq;
+
+enum {					/* software flags */
+	Fopen		= 0x0001,	/* open */
+	Fseqdev		= 0x0002,	/* sequential-access device */
+	Fwritten	= 0x0004,	/* device written */
+	Fronly		= 0x0008,	/* device is read-only */
+	Fwormdev	= 0x0010,	/* write-once read-multiple device */
+	Fprintdev	= 0x0020,	/* printer */
+	Fbfixed		= 0x0040,	/* fixed block size */
+	Fchanger	= 0x0080,	/* medium-changer device */
+	Finqok		= 0x0100,	/* inquiry data is OK */
+	Fmode6		= 0x0200,	/* use 6-byte modeselect */
+	Frw10		= 0x0400,	/* use 10-byte read/write */
+	Fusb		= 0x0800,	/* USB transparent scsi */
+};
+
+enum {
+	STnomem		=-4,		/* buffer allocation failed */
+	STharderr	=-3,		/* controller error of some kind */
+	STtimeout	=-2,		/* bus timeout */
+	STok		= 0,		/* good */
+	STcheck		= 0x02,		/* check condition */
+	STcondmet	= 0x04,		/* condition met/good */
+	STbusy		= 0x08,		/* busy */
+	STintok		= 0x10,		/* intermediate/good */
+	STintcondmet	= 0x14,		/* intermediate/condition met/good */
+	STresconf	= 0x18,		/* reservation conflict */
+	STterminated	= 0x22,		/* command terminated */
+	STqfull		= 0x28,		/* queue full */
+};
+
+enum {					/* status */
+	Status_SD	= 0x80,		/* sense-data available */
+	Status_SW	= 0x83,		/* internal software error */
+	Status_BADARG	= 0x84,		/* bad argument to request */
+	Status_RO	= 0x85,		/* device is read-only */
+};
+
+enum {					/* SCSI command codes */
+	ScmdTur		= 0x00,		/* test unit ready */
+	ScmdRewind	= 0x01,		/* rezero/rewind */
+	ScmdRsense	= 0x03,		/* request sense */
+	ScmdFormat	= 0x04,		/* format unit */
+	ScmdRblimits	= 0x05,		/* read block limits */
+	ScmdRead	= 0x08,		/* read */
+	ScmdWrite	= 0x0A,		/* write */
+	ScmdSeek	= 0x0B,		/* seek */
+	ScmdFmark	= 0x10,		/* write filemarks */
+	ScmdSpace	= 0x11,		/* space forward/backward */
+	ScmdInq		= 0x12,		/* inquiry */
+	ScmdMselect6	= 0x15,		/* mode select */
+	ScmdMselect10	= 0x55,		/* mode select */
+	ScmdMsense6	= 0x1A,		/* mode sense */
+	ScmdMsense10	= 0x5A,		/* mode sense */
+	ScmdStart	= 0x1B,		/* start/stop unit */
+	ScmdRcapacity	= 0x25,		/* read capacity */
+	ScmdRcapacity16	= 0x9e,		/* long read capacity */
+	ScmdExtread	= 0x28,		/* extended read */
+	ScmdExtwrite	= 0x2A,		/* extended write */
+	ScmdExtseek	= 0x2B,		/* extended seek */
+
+	ScmdSynccache	= 0x35,		/* flush cache */
+	ScmdRTOC	= 0x43,		/* read TOC data */
+	ScmdRdiscinfo	= 0x51,		/* read disc information */
+	ScmdRtrackinfo	= 0x52,		/* read track information */
+	ScmdReserve	= 0x53,		/* reserve track */
+	ScmdBlank	= 0xA1,		/* blank *-RW media */
+
+	ScmdCDpause	= 0x4B,		/* pause/resume */
+	ScmdCDstop	= 0x4E,		/* stop play/scan */
+	ScmdCDplay	= 0xA5,		/* play audio */
+	ScmdCDload	= 0xA6,		/* load/unload */
+	ScmdCDscan	= 0xBA,		/* fast forward/reverse */
+	ScmdCDstatus	= 0xBD,		/* mechanism status */
+	Scmdgetconf	= 0x46,		/* get configuration */
+
+	ScmdEInitialise	= 0x07,		/* initialise element status */
+	ScmdMMove	= 0xA5,		/* move medium */
+	ScmdEStatus	= 0xB8,		/* read element status */
+	ScmdMExchange	= 0xA6,		/* exchange medium */
+	ScmdEposition	= 0x2B,		/* position to element */
+
+	ScmdReadDVD	= 0xAD,		/* read dvd structure */
+	ScmdReportKey	= 0xA4,		/* read dvd key */
+	ScmdSendKey	= 0xA3,		/* write dvd key */
+
+	ScmdClosetracksess= 0x5B,
+	ScmdRead12	= 0xA8,
+	ScmdSetcdspeed	= 0xBB,
+	ScmdReadcd	= 0xBE,
+
+	/* vendor-specific */
+	ScmdFwaddr	= 0xE2,		/* first writeable address */
+	ScmdTreserve	= 0xE4,		/* reserve track */
+	ScmdTinfo	= 0xE5,		/* read track info */
+	ScmdTwrite	= 0xE6,		/* write track */
+	ScmdMload	= 0xE7,		/* medium load/unload */
+	ScmdFixation	= 0xE9,		/* fixation */
+};
+
+enum {
+	/* sense data byte 0 */
+	Sd0valid	= 0x80,		/* valid sense data present */
+
+	/* sense data byte 2 */
+	/* incorrect-length indicator, difference in bytes 3—6 */
+	Sd2ili		= 0x20,
+	Sd2eom		= 0x40,		/* end of medium (tape) */
+	Sd2filemark	= 0x80,		/* at a filemark (tape) */
+
+	/* command byte 1 */
+	Cmd1fixed	= 1,		/* use fixed-length blocks */
+	Cmd1sili	= 2,		/* don't set Sd2ili */
+
+	/* limit of block #s in 24-bit ccbs */
+	Max24off	= (1<<21) - 1,	/* 2⁲ⁱ - 1 */
+
+	/* mode pages */
+	Allmodepages = 0x3F,
+};
+
+/* p arguments should be of type uchar* */
+#define GETBELONG(p) ((ulong)(p)[0]<<24 | (ulong)(p)[1]<<16 | (p)[2]<<8 | (p)[3])
+#define PUTBELONG(p, ul) ((p)[0] = (ul)>>24, (p)[1] = (ul)>>16, \
+			  (p)[2] = (ul)>>8,  (p)[3] = (ul))
+#define GETBE24(p)	((ulong)(p)[0]<<16 | (p)[1]<<8 | (p)[2])
+#define PUTBE24(p, ul)	((p)[0] = (ul)>>16, (p)[1] = (ul)>>8, (p)[2] = (ul))
+
+extern long maxiosize;
+
+long	SRready(ScsiReq*);
+long	SRrewind(ScsiReq*);
+long	SRreqsense(ScsiReq*);
+long	SRformat(ScsiReq*);
+long	SRrblimits(ScsiReq*, uchar*);
+long	SRread(ScsiReq*, void*, long);
+long	SRwrite(ScsiReq*, void*, long);
+long	SRseek(ScsiReq*, long, int);
+long	SRfilemark(ScsiReq*, ulong);
+long	SRspace(ScsiReq*, uchar, long);
+long	SRinquiry(ScsiReq*);
+long	SRmodeselect6(ScsiReq*, uchar*, long);
+long	SRmodeselect10(ScsiReq*, uchar*, long);
+long	SRmodesense6(ScsiReq*, uchar, uchar*, long);
+long	SRmodesense10(ScsiReq*, uchar, uchar*, long);
+long	SRstart(ScsiReq*, uchar);
+long	SRrcapacity(ScsiReq*, uchar*);
+long	SRrcapacity16(ScsiReq*, uchar*);
+
+long	SRblank(ScsiReq*, uchar, uchar);	/* MMC CD-R/CD-RW commands */
+long	SRsynccache(ScsiReq*);
+long	SRTOC(ScsiReq*, void*, int, uchar, uchar);
+long	SRrdiscinfo(ScsiReq*, void*, int);
+long	SRrtrackinfo(ScsiReq*, void*, int, int);
+
+long	SRcdpause(ScsiReq*, int);		/* MMC CD audio commands */
+long	SRcdstop(ScsiReq*);
+long	SRcdload(ScsiReq*, int, int);
+long	SRcdplay(ScsiReq*, int, long, long);
+long	SRcdstatus(ScsiReq*, uchar*, int);
+long	SRgetconf(ScsiReq*, uchar*, int);
+
+/*	old CD-R/CD-RW commands */
+long	SRfwaddr(ScsiReq*, uchar, uchar, uchar, uchar*);
+long	SRtreserve(ScsiReq*, long);
+long	SRtinfo(ScsiReq*, uchar, uchar*);
+long	SRwtrack(ScsiReq*, void*, long, uchar, uchar);
+long	SRmload(ScsiReq*, uchar);
+long	SRfixation(ScsiReq*, uchar);
+
+long	SReinitialise(ScsiReq*);		/* CHANGER commands */
+long	SRestatus(ScsiReq*, uchar, uchar*, int);
+long	SRmmove(ScsiReq*, int, int, int, int);
+
+long	SRrequest(ScsiReq*);
+int	SRclose(ScsiReq*);
+int	SRopenraw(ScsiReq*, char*);
+int	SRopen(ScsiReq*, char*);
+
+void	makesense(ScsiReq*);
+
+long	umsrequest(struct Umsc*, ScsiPtr*, ScsiPtr*, int*);
+
+void	scsidebug(int);
+
+char*	scsierrmsg(int n);

+ 96 - 0
sys/src/cmd/usb/disk/ums.h

@@ -0,0 +1,96 @@
+/*
+ * mass storage transport protocols and subclasses,
+ * from usb mass storage class specification overview rev 1.2
+ */
+
+typedef struct Umsc Umsc;
+typedef struct Ums Ums;
+typedef struct Cbw Cbw;			/* command block wrapper */
+typedef struct Csw Csw;			/* command status wrapper */
+
+enum
+{
+	Protocbi =	0,	/* control/bulk/interrupt; mainly floppies */
+	Protocb =	1,	/*   "  with no interrupt; mainly floppies */
+	Protobulk =	0x50,	/* bulk only */
+
+	Subrbc =	1,	/* reduced blk cmds */
+	Subatapi =	2,	/* cd/dvd using sff-8020i or mmc-2 cmd blks */
+	Subqic 	=	3,	/* QIC-157 tapes */
+	Subufi =	4,	/* floppy */
+	Sub8070 =	5,	/* removable media, atapi-like */
+	Subscsi =	6,	/* scsi transparent cmd set */
+	Subisd200 =	7,	/* ISD200 ATA */
+	Subdev =	0xff,	/* use device's value */
+
+	Umsreset =	0xFF,
+	Getmaxlun =	0xFE,
+
+	MaxIOsize	= 256*1024,	/* max. I/O size */
+//	Maxlun		= 256,
+	Maxlun		= 32,
+
+	CMreset = 1,
+
+	Pcmd = 0,
+	Pdata,
+	Pstatus,
+
+	CbwLen		= 31,
+	CbwDataIn	= 0x80,
+	CbwDataOut	= 0x00,
+	CswLen		= 13,
+	CswOk		= 0,
+	CswFailed	= 1,
+	CswPhaseErr	= 2,
+};
+
+/* these are 600 bytes each; ScsiReq is not tiny */
+struct Umsc
+{
+	ScsiReq;
+	uvlong	blocks;
+	uvlong	capacity;
+	uchar 	rawcmd[10];
+	uchar	phase;
+	char	*inq;
+	Ums	*ums;
+	Usbfs	fs;
+};
+
+struct Ums
+{
+	QLock;
+	Dev	*dev;
+	Dev	*epin;
+	Dev	*epout;
+	Umsc	*lun;
+	uchar	maxlun;
+	int	seq;
+	int	nerrs;
+};
+
+/*
+ * USB transparent SCSI devices
+ */
+struct Cbw
+{
+	char	signature[4];		/* "USBC" */
+	long	tag;
+	long	datalen;
+	uchar	flags;
+	uchar	lun;
+	uchar	len;
+	char	command[16];
+};
+
+struct Csw
+{
+	char	signature[4];		/* "USBS" */
+	long	tag;
+	long	dataresidue;
+	uchar	status;
+};
+
+
+int	diskmain(Dev*, int, char**);

+ 484 - 0
sys/src/cmd/usb/ether/asix.c

@@ -0,0 +1,484 @@
+/*
+ * Asix USB ether adapters
+ * I got no documentation for it, thus the bits
+ * come from other systems; it's likely this is
+ * doing more than needed in some places and
+ * less than required in others.
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "ether.h"
+
+enum
+{
+
+	/* Asix commands */
+	Cswmii		= 0x06,		/* set sw mii */
+	Crmii		= 0x07,		/* read mii reg */
+	Cwmii		= 0x08,		/* write mii reg */
+	Chwmii		= 0x0a,		/* set hw mii */
+	Creeprom	= 0x0b,		/* read eeprom */
+	Cwdis		= 0x0e,		/* write disable */
+	Cwena		= 0x0d,		/* write enable */
+	Crrxctl		= 0x0f,		/* read rx ctl */
+	Cwrxctl		= 0x10,		/* write rx ctl */
+	Cwipg		= 0x12,		/* write ipg */
+	Crmac		= 0x13,		/* read mac addr */
+	Crphy		= 0x19,		/* read phy id */
+	Cwmedium		= 0x1b,		/* write medium mode */
+	Crgpio		= 0x1e,		/* read gpio */
+	Cwgpio		= 0x1f,		/* write gpios */
+	Creset		= 0x20,		/* reset */
+	Cwphy		= 0x22,		/* select phy */
+
+	/* reset codes */
+	Rclear		= 0x00,
+	Rprte		= 0x04,
+	Rprl		= 0x08,
+	Riprl		= 0x20,
+	Rippd		= 0x40,
+
+	Gpiogpo1en	= 0x04,	/* gpio1 enable */,
+	Gpiogpo1		= 0x08,	/* gpio1 value */
+	Gpiogpo2en	= 0x10,	/* gpio2 enable */
+	Gpiogpo2		= 0x20,	/* gpio2 value */
+	Gpiorse		= 0x80,	/* gpio reload serial eeprom */
+
+	Pmask		= 0x1F,
+	Pembed		= 0x10,			/* embedded phy */
+
+	Mfd		= 0x002,		/* media */
+	Mac		= 0x004,
+	Mrfc		= 0x010,
+	Mtfc		= 0x020,
+	Mjfe		= 0x040,
+	Mre		= 0x100,
+	Mps		= 0x200,
+	Mall772		= Mfd|Mrfc|Mtfc|Mps|Mac|Mre,
+	Mall178		= Mps|Mfd|Mac|Mrfc|Mtfc|Mjfe|Mre,
+
+	Ipgdflt		= 0x15|0x0c|0x12,	/* default ipg0, 1, 2 */
+	Rxctlso		= 0x80,
+	Rxctlab		= 0x08,
+	Rxctlsep	= 0x04,
+	Rxctlamall	= 0x02,			/* all multicast */
+	Rxctlprom	= 0x01,			/* promiscuous */
+
+	/* MII */
+	Miibmcr			= 0x00,		/* basic mode ctrl reg. */
+		Bmcrreset	= 0x8000,	/* reset */
+		Bmcranena	= 0x1000,	/* auto neg. enable */
+		Bmcrar		= 0x0200,	/* announce restart */
+
+	Miiad			= 0x04,		/* advertise reg. */
+		Adcsma		= 0x0001,
+		Ad1000f		= 0x0200,
+		Ad1000h		= 0x0100,
+		Ad10h		= 0x0020,
+		Ad10f		= 0x0040,
+		Ad100h		= 0x0080,
+		Ad100f		= 0x0100,
+		Adpause		= 0x0400,
+		Adall		= Ad10h|Ad10f|Ad100h|Ad100f,
+
+	Miimctl			= 0x14,		/* marvell ctl */
+		Mtxdly		= 0x02,
+		Mrxdly		= 0x80,
+		Mtxrxdly	= 0x82,
+
+	Miic1000			= 0x09,
+
+};
+
+static int
+asixset(Dev *d, int c, int v)
+{
+	int r;
+	int ec;
+
+	r = Rh2d|Rvendor|Rdev;
+	ec = usbcmd(d, r, c, v, 0, nil, 0);
+	if(ec < 0)
+		deprint(2, "%s: asixset %x %x: %r\n", argv0, c, v);
+	return ec;
+}
+
+static int
+asixget(Dev *d, int c, uchar *buf, int l)
+{
+	int r;
+	int ec;
+
+	r = Rd2h|Rvendor|Rdev;
+	ec = usbcmd(d, r, c, 0, 0, buf, l);
+	if(ec < 0)
+		deprint(2, "%s: asixget %x: %r\n", argv0, c);
+	return ec;
+}
+
+static int
+getgpio(Dev *d)
+{
+	uchar c;
+
+	if(asixget(d, Crgpio, &c, 1) < 0)
+		return -1;
+	return c;
+}
+
+static int
+getphy(Dev *d)
+{
+	uchar buf[2];
+
+	if(asixget(d, Crphy, buf, sizeof(buf)) < 0)
+		return -1;
+	deprint(2, "%s: phy addr %#ux\n", argv0, buf[1]);
+	return buf[1];
+}
+
+static int
+getrxctl(Dev *d)
+{
+	uchar buf[2];
+	int r;
+
+	memset(buf, 0, sizeof(buf));
+	if(asixget(d, Crrxctl, buf, sizeof(buf)) < 0)
+		return -1;
+	r = GET2(buf);
+	deprint(2, "%s: rxctl %#x\n", argv0, r);
+	return r;
+}
+
+static int
+getmac(Dev *d, uchar buf[])
+{
+	if(asixget(d, Crmac, buf, Eaddrlen) < 0)
+		return -1;
+	return Eaddrlen;
+}
+
+static int
+miiread(Dev *d, int phy, int reg)
+{
+	int r;
+	uchar v[2];
+
+	r = Rd2h|Rvendor|Rdev;
+	if(usbcmd(d, r, Crmii, phy, reg, v, 2) < 0){
+		dprint(2, "%s: miiwrite: %r\n", argv0);
+		return -1;
+	}
+	r = GET2(v);
+	if(r == 0xFFFF)
+		return -1;
+	return r;
+}
+
+
+static int
+miiwrite(Dev *d, int phy, int reg, int val)
+{
+	int r;
+	uchar v[2];
+
+	if(asixset(d, Cswmii, 0) < 0)
+		return -1;
+	r = Rh2d|Rvendor|Rdev;
+	PUT2(v, val);
+	if(usbcmd(d, r, Cwmii, phy, reg, v, 2) < 0){
+		deprint(2, "%s: miiwrite: %#x %#x %r\n", argv0, reg, val);
+		return -1;
+	}
+	if(asixset(d, Chwmii, 0) < 0)
+		return -1;
+	return 0;
+}
+
+static int
+eepromread(Dev *d, int i)
+{
+	int r;
+	int ec;
+	uchar buf[2];
+
+	r = Rd2h|Rvendor|Rdev;
+	ec = usbcmd(d, r, Creeprom, i, 0, buf, sizeof(buf));
+	if(ec < 0)
+		deprint(2, "%s: eepromread %d: %r\n", argv0, i);
+	ec = GET2(buf);
+	deprint(2, "%s: eeprom %#x = %#x\n", argv0, i, ec);
+	if(ec == 0xFFFF)
+		ec = -1;
+	return ec;
+}
+
+/*
+ * No doc. we are doing what Linux does as closely
+ * as we can.
+ */
+static int
+ctlrinit(Ether *ether)
+{
+	Dev *d;
+	int i;
+	int bmcr;
+	int gpio;
+	int ee17;
+	int rc;
+
+	d = ether->dev;
+	switch(ether->cid){
+	default:
+		fprint(2, "%s: card known but not implemented\n", argv0);
+		return -1;
+
+	case A88178:
+		deprint(2, "%s: setting up A88178\n", argv0);
+		gpio = getgpio(d);
+		if(gpio < 0)
+			return -1;
+		deprint(2, "%s: gpio sts %#x\n", argv0, gpio);
+		asixset(d, Cwena, 0);
+		ee17 = eepromread(d, 0x0017);
+		asixset(d, Cwdis, 0);
+		asixset(d, Cwgpio, Gpiorse|Gpiogpo1|Gpiogpo1en);
+		if((ee17 >> 8) != 1){
+			asixset(d, Cwgpio, 0x003c);
+			asixset(d, Cwgpio, 0x001c);
+			asixset(d, Cwgpio, 0x003c);
+		}else{
+			asixset(d, Cwgpio, Gpiogpo1en);
+			asixset(d, Cwgpio, Gpiogpo1|Gpiogpo1en);
+		}
+		asixset(d, Creset, Rclear);
+		sleep(150);
+		asixset(d, Creset, Rippd|Rprl);
+		sleep(150);
+		asixset(d, Cwrxctl, 0);
+		if(getmac(d, ether->addr) < 0)
+			return -1;
+		ether->phy = getphy(d);
+		if(ee17 < 0 || (ee17 & 0x7) == 0){
+			miiwrite(d, ether->phy, Miimctl, Mtxrxdly);
+			sleep(60);
+		}
+		miiwrite(d, ether->phy, Miibmcr, Bmcrreset|Bmcranena);
+		miiwrite(d, ether->phy, Miiad, Adall|Adcsma|Adpause);
+		miiwrite(d, ether->phy, Miic1000, Ad1000f);
+		bmcr = miiread(d, ether->phy, Miibmcr);
+		if((bmcr & Bmcranena) != 0){
+			bmcr |= Bmcrar;
+			miiwrite(d, ether->phy, Miibmcr, bmcr);
+		}
+		asixset(d, Cwmedium, Mall178);
+		asixset(d, Cwrxctl, Rxctlso|Rxctlab);
+		break;
+
+	case A88772:
+		deprint(2, "%s: setting up A88772\n", argv0);
+		if(asixset(d, Cwgpio, Gpiorse|Gpiogpo2|Gpiogpo2en) < 0)
+			return -1;
+		ether->phy = getphy(d);
+		dprint(2, "%s: phy %#x\n", argv0, ether->phy);
+		if((ether->phy & Pmask) == Pembed){
+			/* embedded 10/100 ethernet */
+			rc = asixset(d, Cwphy, 1);
+		}else
+			rc = asixset(d, Cwphy, 0);
+		if(rc < 0)
+			return -1;
+		if(asixset(d, Creset, Rippd|Rprl) < 0)
+			return -1;
+		sleep(150);
+		if((ether->phy & Pmask) == Pembed)
+			rc = asixset(d, Creset, Riprl);
+		else
+			rc = asixset(d, Creset, Rprte);
+		if(rc < 0)
+			return -1;
+		sleep(150);
+		rc = getrxctl(d);
+		deprint(2, "%s: rxctl is %#x\n", argv0, rc);
+		if(asixset(d, Cwrxctl, 0) < 0)
+			return -1;
+		if(getmac(d, ether->addr) < 0)
+			return -1;
+
+
+		if(asixset(d, Creset, Rprl) < 0)
+			return -1;
+		sleep(150);
+		if(asixset(d, Creset, Riprl|Rprl) < 0)
+			return -1;
+		sleep(150);
+
+		miiwrite(d, ether->phy, Miibmcr, Bmcrreset);
+		miiwrite(d, ether->phy, Miiad, Adall|Adcsma);
+		bmcr = miiread(d, ether->phy, Miibmcr);
+		if((bmcr & Bmcranena) != 0){
+			bmcr |= Bmcrar;
+			miiwrite(d, ether->phy, Miibmcr, bmcr);
+		}
+		if(asixset(d, Cwmedium, Mall772) < 0)
+			return -1;
+		if(asixset(d, Cwipg, Ipgdflt) < 0)
+			return -1;
+		if(asixset(d, Cwrxctl, Rxctlso|Rxctlab) < 0)
+			return -1;
+		deprint(2, "%s: final rxctl: %#x\n", argv0, getrxctl(d));
+		break;
+	}
+
+	if(etherdebug){
+		fprint(2, "%s: ether: phy %#x addr ", argv0, ether->phy);
+		for(i = 0; i < sizeof(ether->addr); i++)
+			fprint(2, "%02x", ether->addr[i]);
+		fprint(2, "\n");
+	}
+	return 0;
+}
+
+
+static long
+asixbread(Ether *e, Buf *bp)
+{
+	ulong nr;
+	ulong hd;
+	Buf *rbp;
+
+	rbp = e->aux;
+	if(rbp == nil || rbp->ndata < 4){
+		rbp->rp = rbp->data;
+		rbp->ndata = read(e->epin->dfd, rbp->rp, sizeof(bp->data));
+		if(rbp->ndata < 0)
+			return -1;
+	}
+	if(rbp->ndata < 4){
+		werrstr("short frame");
+		deprint(2, "%s: asixbread got %d bytes\n", argv0, rbp->ndata);
+		rbp->ndata = 0;
+		return 0;
+	}
+	hd = GET4(rbp->rp);
+	nr = hd & 0xFFFF;
+	hd = (hd>>16) & 0xFFFF;
+	if(nr != (~hd & 0xFFFF)){
+		if(0)deprint(2, "%s: asixread: bad header %#ulx %#ulx\n",
+			argv0, nr, (~hd & 0xFFFF));
+		werrstr("bad usb packet header");
+		rbp->ndata = 0;
+		return 0;
+	}
+	rbp->rp += 4;
+	if(nr < 6 || nr > Epktlen){
+		if(nr < 6)
+			werrstr("short frame");
+		else
+			werrstr("long frame");
+		deprint(2, "%s: asixbread %r (%ld)\n", argv0, nr);
+		rbp->ndata = 0;
+		return 0;
+	}
+	bp->rp = bp->data + Hdrsize;
+	memmove(bp->rp, rbp->rp, nr);
+	bp->ndata = nr;
+	rbp->rp += 4 + nr;
+	rbp->ndata -= (4 + nr);
+	return bp->ndata;
+}
+
+static long
+asixbwrite(Ether *e, Buf *bp)
+{
+	ulong len;
+	long n;
+
+	deprint(2, "%s: asixbwrite %d bytes\n", argv0, bp->ndata);
+	assert(bp->rp - bp->data >= Hdrsize);
+	bp->ndata &= 0xFFFF;
+	len = (0xFFFF0000 & ~(bp->ndata<<16))  | bp->ndata;
+	bp->rp -= 4;
+	PUT4(bp->rp, len);
+	bp->ndata += 4;
+	if((bp->ndata % e->epout->maxpkt) == 0){
+		PUT4(bp->rp+bp->ndata, 0xFFFF0000);
+		bp->ndata += 4;
+	}
+	n = write(e->epout->dfd, bp->rp, bp->ndata);
+	deprint(2, "%s: asixbwrite wrote %ld bytes\n", argv0, n);
+	if(n <= 0)
+		return n;
+	return n;
+}
+
+static int
+asixpromiscuous(Ether *e, int on)
+{
+	int rxctl;
+
+	deprint(2, "%s: aixprompiscuous %d\n", argv0, on);
+	rxctl = getrxctl(e->dev);
+	if(on != 0)
+		rxctl |= Rxctlprom;
+	else
+		rxctl &= ~Rxctlprom;
+	return asixset(e->dev, Cwrxctl, rxctl);
+}
+
+static int
+asixmulticast(Ether *e, uchar *addr, int on)
+{
+	int rxctl;
+
+	USED(addr);
+	USED(on);
+	/* BUG: should write multicast filter */
+	rxctl = getrxctl(e->dev);
+	if(e->nmcasts != 0)
+		rxctl |= Rxctlamall;
+	else
+		rxctl &= ~Rxctlamall;
+	deprint(2, "%s: asixmulticast %d\n", argv0, e->nmcasts);
+	return asixset(e->dev, Cwrxctl, rxctl);
+}
+
+static void
+asixfree(Ether *ether)
+{
+	deprint(2, "%s: aixfree %#p\n", argv0, ether);
+	free(ether->aux);
+	ether->aux = nil;
+}
+
+int
+asixreset(Ether *ether)
+{
+	Cinfo *ip;
+	Dev *dev;
+
+	dev = ether->dev;
+	for(ip = cinfo; ip->vid != 0; ip++)
+		if(ip->vid == dev->usb->vid && ip->did == dev->usb->did){
+			ether->cid = ip->cid;
+			if(ctlrinit(ether) < 0){
+				deprint(2, "%s: init failed: %r\n", argv0);
+				return -1;
+			}
+			deprint(2, "%s: asix reset done\n", argv0);
+			ether->aux = emallocz(sizeof(Buf), 1);
+			ether->bread = asixbread;
+			ether->bwrite = asixbwrite;
+			ether->free = asixfree;
+			ether->promiscuous = asixpromiscuous;
+			ether->multicast = asixmulticast;
+			ether->mbps = 100;	/* BUG */
+			return 0;
+		}
+	return -1;
+}

+ 60 - 0
sys/src/cmd/usb/ether/cdc.c

@@ -0,0 +1,60 @@
+/*
+ * Standard usb ethernet communications device.
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "ether.h"
+
+static int
+okclass(Iface *iface)
+{
+	return Class(iface->csp) == Clcomms && Subclass(iface->csp) == Scether;
+}
+
+static int
+getmac(Ether *ether)
+{
+	int i;
+	Usbdev *ud;
+	uchar *b;
+	Desc *dd;
+	char *mac;
+
+	ud = ether->dev->usb;
+
+	for(i = 0; i < nelem(ud->ddesc); i++)
+		if((dd = ud->ddesc[i]) != nil && okclass(dd->iface)){
+			b = (uchar*)&dd->data;
+			if(b[1] == Dfunction && b[2] == Fnether){
+				mac = loaddevstr(ether->dev, b[3]);
+				if(mac != nil && strlen(mac) != 12){
+					free(mac);
+					mac = nil;
+				}
+				if(mac != nil){
+					parseaddr(ether->addr, mac);
+					free(mac);
+					return 0;
+				}
+			}
+		}
+	return -1;
+}
+
+int
+cdcreset(Ether *ether)
+{
+	/*
+	 * Assume that all communication devices are going to
+	 * be std. ethernet communication devices. Specific controllers
+	 * must have been probed first.
+	 * NB: This ignores unions.
+	 */
+	if(ether->dev->usb->class == Clcomms)
+		return getmac(ether);
+	return -1;
+}

+ 993 - 0
sys/src/cmd/usb/ether/clether.c

@@ -0,0 +1,993 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <bio.h>
+#include <auth.h>
+#include <fcall.h>
+#include <9p.h>
+
+#include "usb.h"
+
+typedef struct Tab Tab;
+typedef struct Qbuf Qbuf;
+typedef struct Dq Dq;
+typedef struct Conn Conn;
+typedef struct Ehdr Ehdr;
+typedef struct Stats Stats;
+
+enum
+{
+	SC_ACM = 2,
+	SC_ETHER = 6,
+
+	FUNCTION = 0x24,
+	FN_HEADER = 0,
+	FN_UNION = 6,
+	FN_ETHER = 15,
+};
+
+enum
+{
+	Qroot,
+	Qiface,
+	Qclone,
+	Qstats,
+	Qaddr,
+	Qndir,
+	Qctl,
+	Qdata,
+	Qtype,
+	Qmax,
+};
+
+struct Tab
+{
+	char *name;
+	ulong mode;
+};
+
+Tab tab[] =
+{
+	"/",		DMDIR|0555,
+	"etherU",	DMDIR|0555,	/* calling it *ether* makes snoopy(8) happy */
+	"clone",	0666,
+	"stats",	0666,
+	"addr",	0444,
+	"%ud",	DMDIR|0555,
+	"ctl",		0666,
+	"data",	0666,
+	"type",	0444,
+};
+
+struct Qbuf
+{
+	Qbuf		*next;
+	int		ndata;
+	uchar	data[];
+};
+
+struct Dq
+{
+	QLock	l;
+	Dq		*next;
+	Req		*r;
+	Req		**rt;
+	Qbuf		*q;
+	Qbuf		**qt;
+
+	int		nb;
+};
+
+struct Conn
+{
+	QLock	l;
+	int		used;
+	int		type;
+	int		prom;
+	Dq		*dq;
+};
+
+struct Ehdr
+{
+	uchar	d[6];
+	uchar	s[6];
+	uchar	type[2];
+};
+
+struct Stats
+{
+	int		in;
+	int		out;
+};
+
+Conn conn[32];
+int nconn = 0;
+
+int debug;
+ulong time0;
+int usbfdin = -1;
+int usbfdout = -1;
+
+Stats stats;
+
+uchar macaddr[6];
+int iunion[8][2];
+int niunion;
+int maxpacket = 64;
+
+char *uname;
+
+#define PATH(type, n)		((type)|((n)<<8))
+#define TYPE(path)			(((uint)(path) & 0x000000FF)>>0)
+#define NUM(path)			(((uint)(path) & 0xFFFFFF00)>>8)
+#define NUMCONN(c)		(((long)(c)-(long)&conn[0])/sizeof(conn[0]))
+
+static int
+receivepacket(void *buf, int len);
+
+static void
+fillstat(Dir *d, uvlong path)
+{
+	Tab *t;
+
+	memset(d, 0, sizeof(*d));
+	d->uid = estrdup9p(uname);
+	d->gid = estrdup9p(uname);
+	d->qid.path = path;
+	d->atime = d->mtime = time0;
+	t = &tab[TYPE(path)];
+	d->name = smprint(t->name, NUM(path));
+	d->qid.type = t->mode>>24;
+	d->mode = t->mode;
+}
+
+static void
+fsattach(Req *r)
+{
+	if(r->ifcall.aname && r->ifcall.aname[0]){
+		respond(r, "invalid attach specifier");
+		return;
+	}
+	if(uname == nil)
+		uname = estrdup9p(r->ifcall.uname);
+	r->fid->qid.path = PATH(Qroot, 0);
+	r->fid->qid.type = QTDIR;
+	r->fid->qid.vers = 0;
+	r->ofcall.qid = r->fid->qid;
+	respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+	fillstat(&r->d, r->fid->qid.path);
+	respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void*)
+{
+	i += Qroot+1;
+	if(i == Qiface){
+		fillstat(d, i);
+		return 0;
+	}
+	return -1;
+}
+
+static int
+ifacegen(int i, Dir *d, void*)
+{
+	i += Qiface+1;
+	if(i < Qndir){
+		fillstat(d, i);
+		return 0;
+	}
+	i -= Qndir;
+	if(i < nconn){
+		fillstat(d, PATH(Qndir, i));
+		return 0;
+	}
+	return -1;
+}
+
+static int
+ndirgen(int i, Dir *d, void *aux)
+{
+	i += Qndir+1;
+	if(i < Qmax){
+		fillstat(d, PATH(i, NUMCONN(aux)));
+		return 0;
+	}
+	return -1;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+	int i, n;
+	char buf[32];
+	ulong path;
+
+	path = fid->qid.path;
+	if(!(fid->qid.type&QTDIR))
+		return "walk in non-directory";
+
+	if(strcmp(name, "..") == 0){
+		switch(TYPE(path)){
+		case Qroot:
+			return nil;
+		case Qiface:
+			qid->path = PATH(Qroot, 0);
+			qid->type = tab[Qroot].mode>>24;
+			return nil;
+		case Qndir:
+			qid->path = PATH(Qiface, 0);
+			qid->type = tab[Qiface].mode>>24;
+			return nil;
+		default:
+			return "bug in fswalk1";
+		}
+	}
+
+	for(i = TYPE(path)+1; i<nelem(tab); i++){
+		if(i==Qndir){
+			n = atoi(name);
+			snprint(buf, sizeof buf, "%d", n);
+			if(n < nconn && strcmp(buf, name) == 0){
+				qid->path = PATH(i, n);
+				qid->type = tab[i].mode>>24;
+				return nil;
+			}
+			break;
+		}
+		if(strcmp(name, tab[i].name) == 0){
+			qid->path = PATH(i, NUM(path));
+			qid->type = tab[i].mode>>24;
+			return nil;
+		}
+		if(tab[i].mode&DMDIR)
+			break;
+	}
+	return "directory entry not found";
+}
+
+static void
+matchrq(Dq *d)
+{
+	Req *r;
+	Qbuf *b;
+
+	while(r = d->r){
+		int n;
+
+		if((b = d->q) == nil)
+			break;
+		if((d->q = b->next) == nil)
+			d->qt = &d->q;
+		if((d->r = (Req*)r->aux) == nil)
+			d->rt = &d->r;
+
+		n = r->ifcall.count;
+		if(n > b->ndata)
+			n = b->ndata;
+		memmove(r->ofcall.data, b->data, n);
+		free(b);
+		r->ofcall.count = n;
+		respond(r, nil);
+	}
+}
+
+static void
+readconndata(Req *r)
+{
+	Dq *d;
+
+	d = r->fid->aux;
+	qlock(&d->l);
+	if(d->q==nil && d->nb){
+		qunlock(&d->l);
+		r->ofcall.count = 0;
+		respond(r, nil);
+		return;
+	}
+	// enqueue request
+	r->aux = nil;
+	*d->rt = r;
+	d->rt = (Req**)&r->aux;
+	matchrq(d);
+	qunlock(&d->l);
+}
+
+static void
+writeconndata(Req *r)
+{
+	char e[ERRMAX];
+	Dq *d;
+	void *p;
+	int n;
+
+	d = r->fid->aux;
+	p = r->ifcall.data;
+	n = r->ifcall.count;
+
+	if((n == 11) && memcmp(p, "nonblocking", n)==0){
+		d->nb = 1;
+		goto out;
+	}
+
+	n = write(usbfdout, p, n);
+	if(n < 0){
+		rerrstr(e, sizeof(e));
+		respond(r, e);
+		return;
+	}
+	/*
+	 * this may not work with all CDC devices. the
+	 * linux driver sends one more random byte
+	 * instead of a zero byte transaction. maybe we
+	 * should do the same?
+	 */
+	if(n % maxpacket == 0)
+		write(usbfdout, "", 0);
+
+	if(receivepacket(p, n) == 0)
+		stats.out++;
+out:
+	r->ofcall.count = n;
+	respond(r, nil);
+}
+
+static char*
+mac2str(uchar *m)
+{
+	int i;
+	char *t = "0123456789abcdef";
+	static char buf[13];
+	buf[13] = 0;
+	for(i=0; i<6; i++){
+		buf[i*2] = t[m[i]>>4];
+		buf[i*2+1] = t[m[i]&0xF];
+	}
+	return buf;
+}
+
+static int
+str2mac(uchar *m, char *s)
+{
+	int i;
+
+	if(strlen(s) != 12)
+		return -1;
+
+	for(i=0; i<12; i++){
+		uchar v;
+
+		if(s[i] >= 'A' && s[i] <= 'F'){
+			v = 10 + s[i] - 'A';
+		} else if(s[i] >= 'a' && s[i] <= 'f'){
+			v = 10 + s[i] - 'a';
+		} else if(s[i] >= '0' && s[i] <= '9'){
+			v = s[i] - '0';
+		} else {
+			v = 0;
+		}
+		if(i&1){
+			m[i/2] |= v;
+		} else {
+			m[i/2] = v<<4;
+		}
+	}
+	return 0;
+}
+
+static void
+fsread(Req *r)
+{
+	char buf[200];
+	char e[ERRMAX];
+	ulong path;
+
+	path = r->fid->qid.path;
+
+	switch(TYPE(path)){
+	default:
+		snprint(e, sizeof e, "bug in fsread path=%lux", path);
+		respond(r, e);
+		break;
+
+	case Qroot:
+		dirread9p(r, rootgen, nil);
+		respond(r, nil);
+		break;
+
+	case Qiface:
+		dirread9p(r, ifacegen, nil);
+		respond(r, nil);
+		break;
+
+	case Qstats:
+		snprint(buf, sizeof(buf),
+			"in: %d\n"
+			"out: %d\n"
+			"mbps: %d\n"
+			"addr: %s\n",
+			stats.in, stats.out, 10, mac2str(macaddr));
+		readstr(r, buf);
+		respond(r, nil);
+		break;
+
+	case Qaddr:
+		readstr(r, mac2str(macaddr));
+		respond(r, nil);
+		break;
+
+	case Qndir:
+		dirread9p(r, ndirgen, &conn[NUM(path)]);
+		respond(r, nil);
+		break;
+
+	case Qctl:
+		snprint(buf, sizeof(buf), "%11d ", NUM(path));
+		readstr(r, buf);
+		respond(r, nil);
+		break;
+
+	case Qtype:
+		snprint(buf, sizeof(buf), "%11d ", conn[NUM(path)].type);
+		readstr(r, buf);
+		respond(r, nil);
+		break;
+
+	case Qdata:
+		readconndata(r);
+		break;
+	}
+}
+
+static void
+fswrite(Req *r)
+{
+	char e[ERRMAX];
+	ulong path;
+	char *p;
+	int n;
+
+	path = r->fid->qid.path;
+	switch(TYPE(path)){
+	case Qctl:
+		n = r->ifcall.count;
+		p = (char*)r->ifcall.data;
+		if((n == 11) && memcmp(p, "promiscuous", 11)==0)
+			conn[NUM(path)].prom = 1;
+		if((n > 8) && memcmp(p, "connect ", 8)==0){
+			char x[12];
+
+			if(n - 8 >= sizeof(x)){
+				respond(r, "invalid control msg");
+				return;
+			}
+
+			p += 8;
+			memcpy(x, p, n-8);
+			x[n-8] = 0;
+
+			conn[NUM(path)].type = atoi(p);
+		}
+		r->ofcall.count = n;
+		respond(r, nil);
+		break;
+	case Qdata:
+		writeconndata(r);
+		break;
+	default:
+		snprint(e, sizeof e, "bug in fswrite path=%lux", path);
+		respond(r, e);
+	}
+}
+
+static void
+fsopen(Req *r)
+{
+	static int need[4] = { 4, 2, 6, 1 };
+	ulong path;
+	int i, n;
+	Tab *t;
+	Dq *d;
+	Conn *c;
+
+
+	/*
+	 * lib9p already handles the blatantly obvious.
+	 * we just have to enforce the permissions we have set.
+	 */
+	path = r->fid->qid.path;
+	t = &tab[TYPE(path)];
+	n = need[r->ifcall.mode&3];
+	if((n&t->mode) != n){
+		respond(r, "permission denied");
+		return;
+	}
+
+	d = nil;
+	r->fid->aux = nil;
+
+	switch(TYPE(path)){
+	case Qclone:
+		for(i=0; i<nelem(conn); i++){
+			if(conn[i].used)
+				continue;
+			if(i >= nconn)
+				nconn = i+1;
+			path = PATH(Qctl, i);
+			goto CaseConn;
+		}
+		respond(r, "out of connections");
+		return;
+	case Qdata:
+		d = emalloc9p(sizeof(*d));
+		memset(d, 0, sizeof(*d));
+		d->qt = &d->q;
+		d->rt = &d->r;
+		r->fid->aux = d;
+	case Qndir:
+	case Qctl:
+	case Qtype:
+	CaseConn:
+		c = &conn[NUM(path)];
+		qlock(&c->l);
+		if(c->used++ == 0){
+			c->type = 0;
+			c->prom = 0;
+		}
+		if(d != nil){
+			d->next = c->dq;
+			c->dq = d;
+		}
+		qunlock(&c->l);
+		break;
+	}
+
+	r->fid->qid.path = path;
+	r->ofcall.qid.path = path;
+	respond(r, nil);
+}
+
+static void
+fsflush(Req *r)
+{
+	Req *o, **p;
+	Fid *f;
+	Dq *d;
+
+	o = r->oldreq;
+	f = o->fid;
+	if(TYPE(f->qid.path) == Qdata){
+		d = f->aux;
+		qlock(&d->l);
+		for(p=&d->r; *p; p=(Req**)&((*p)->aux)){
+			if(*p == o){
+				if((*p = (Req*)o->aux) == nil)
+					d->rt = p;
+				r->oldreq = nil;
+				respond(o, "interrupted");
+				break;
+			}
+		}
+		qunlock(&d->l);
+	}
+	respond(r, nil);
+}
+
+
+static void
+fsdestroyfid(Fid *fid)
+{
+	Conn *c;
+	Qbuf *b;
+	Dq **x, *d;
+
+	if(TYPE(fid->qid.path) >= Qndir){
+		c = &conn[NUM(fid->qid.path)];
+		qlock(&c->l);
+		if(d = fid->aux){
+			fid->aux = nil;
+			for(x=&c->dq; *x; x=&((*x)->next)){
+				if(*x == d){
+					*x = d->next;
+					break;
+				}
+			}
+			qlock(&d->l);
+			while(b = d->q){
+				d->q = b->next;
+				free(b);
+			}
+			qunlock(&d->l);
+		}
+		if(TYPE(fid->qid.path) == Qctl)
+			c->prom = 0;
+		c->used--;
+		qunlock(&c->l);
+	}
+}
+
+static char*
+finddevice(int *ctrlno, int *id)
+{
+	static char buf[80];
+	int fd;
+	int i, j;
+
+	for(i=0; i<16; i++){
+		for(j=0; j<128; j++){
+			int csp;
+			char line[80];
+			char *p;
+			int n;
+
+			snprint(buf, sizeof(buf), "/dev/usb%d/%d/status", i, j);
+			fd = open(buf, OREAD);
+			buf[strlen(buf)-7] = 0;
+
+			if(fd < 0)
+				break;
+			n = read(fd, line, sizeof(line)-1);
+			close(fd);
+			if(n <= 0)
+				continue;
+			line[n] = 0;
+			p = line;
+
+			if(strncmp(p, "Enabled ", 8) == 0)
+				p += 8;
+
+			csp = atol(p);
+			
+			if(Class(csp) != Clcomms)
+				continue;
+			switch(Subclass(csp)){
+			default:
+				continue;
+			case SC_ACM:
+			case SC_ETHER:
+				break;
+			}
+			if(debug)
+				fprint(2, "found matching device %s\n", buf);
+			if(ctrlno)
+				*ctrlno = i;
+			if(id)
+				*id = j;
+			return buf;
+		}
+	}
+	return nil;
+}
+
+static char*
+usbgetstr(Dev *d, int i, int lang)
+{
+	byte b[200];
+	byte *rb;
+	char *s;
+	Rune r;
+	int l;
+	int n;
+
+	setupreq(d->ep[0], RD2H|Rdevice, GET_DESCRIPTOR, STRING<<8|i, lang, sizeof(b));
+	if((n = setupreply(d->ep[0],  b, sizeof(b))) < 0)
+		return nil;
+	if(n <= 2)
+		return nil;
+	if(n & 1)
+		return nil;
+	s = malloc(n*UTFmax+1);
+	n = (n - 2)/2;
+	rb = (byte*)b + 2;
+	for(l=0; --n >= 0; rb += 2){
+		r = GET2(rb);
+		l += runetochar(s+l, &r);
+	}
+	s[l] = 0;
+	return s;
+}
+
+static void
+etherfunc(Device *d, int, ulong csp, void *bb, int nb)
+{
+	int class, subclass;
+	uchar *b = bb;
+	char *s;
+
+	class = Class(csp);
+	subclass = Subclass(csp);
+
+	if(class != CL_COMMS || subclass != SC_ETHER)
+		return;
+	switch(b[2]){
+	case FN_HEADER:
+		pcs_raw("header: ", bb, nb);
+		break;
+	case FN_ETHER:
+		pcs_raw("ether: ", bb, nb);
+		if(s = usbgetstr(d, b[3], 0)){
+			str2mac(macaddr, s);
+			free(s);
+		}
+		break;
+	case FN_UNION:
+		pcs_raw("union: ", bb, nb);
+		if(niunion < nelem(iunion)){
+			iunion[niunion][0] = b[3];
+			iunion[niunion][1] = b[4];
+			niunion++;
+		}
+		break;
+	default:
+		pcs_raw("unknown: ", bb, nb);
+	}
+}
+
+int
+findendpoints(Device *d, int *epin, int *epout)
+{
+	int i, j, k;
+
+	*epin = *epout = -1;
+	niunion = 0;
+	memset(macaddr, 0, 6);
+
+	for(i=0; i<d->nconf; i++){
+		if (d->config[i] == nil)
+			d->config[i] = mallocz(sizeof(*d->config[0]), 1);
+		loadconfig(d, i);
+	}
+	if(niunion <= 0)
+		return -1;
+	for(i=0; i<nelem(d->ep); i++){
+		Endpt *ep;
+
+		if((ep = d->ep[i]) == nil)
+			continue;
+		if(ep->type != Ebulk)
+			continue;
+		if(ep->iface == nil)
+			continue;
+		if(Class(ep->iface->csp) != CL_DATA)
+			continue;
+		for(j=0; j<niunion; j++){
+			if(iunion[j][1] != ep->iface->interface)
+				continue;
+			if(ep->conf == nil)
+				continue;
+			for(k=0; k<ep->conf->nif; k++){
+				if(iunion[j][0] != ep->conf->iface[k]->interface)
+					continue;
+				if(Class(ep->conf->iface[k]->csp) != CL_COMMS)
+					continue;
+				if(Subclass(ep->conf->iface[k]->csp) != SC_ETHER)
+					continue;
+				if(ep->addr & 0x80){
+					if(*epin == -1)
+						*epin = ep->addr&0xF;
+				} else {
+					if(*epout == -1)
+						*epout = ep->addr&0xF;
+				}
+				if (*epin != -1 && *epout != -1){
+					maxpacket = ep->maxpkt;
+					for(i=0; i<2; i++){
+						if(ep->iface->dalt[i] == nil)
+							continue;
+						setupreq(d->ep[0], RH2D|Rinterface, SET_INTERFACE, i, ep->iface->interface, 0);
+						break;
+					}
+					return 0;
+				}
+				break;
+			}
+			break;
+		}
+	}
+	return -1;
+}
+
+
+static int
+inote(void *, char *msg)
+{
+	if(strstr(msg, "interrupt"))
+		return 1;
+	return 0;
+}
+
+static int
+receivepacket(void *buf, int len)
+{
+	int i;
+	int t;
+	Ehdr *h;
+
+	if(len < sizeof(*h))
+		return -1;
+
+	h = (Ehdr*)buf;
+	t = (h->type[0]<<8)|h->type[1];
+
+	for(i=0; i<nconn; i++){
+		Qbuf *b;
+		Conn *c;
+		Dq *d;
+
+		c = &conn[i];
+		qlock(&c->l);
+		if(!c->used)
+			goto next;
+		if(c->type > 0)
+			if(c->type != t)
+				goto next;
+		if(!c->prom && !(h->d[0]&1))
+			if(memcmp(h->d, macaddr, 6))
+				goto next;
+		for(d=c->dq; d; d=d->next){
+			int n;
+
+			n = len;
+			if(c->type == -2 && n > 64)
+				n = 64;
+
+			b = emalloc9p(sizeof(*b) + n);
+			b->ndata = n;
+			memcpy(b->data, buf, n);
+
+			qlock(&d->l);
+			// enqueue buffer
+			b->next = nil;
+			*d->qt = b;
+			d->qt = &b->next;
+			matchrq(d);
+			qunlock(&d->l);
+		}
+next:
+		qunlock(&c->l);
+	}
+	return 0;
+}
+
+static void
+usbreadproc(void *)
+{
+	char err[ERRMAX];
+	uchar buf[4*1024];
+	atnotify(inote, 1);
+
+	threadsetname("usbreadproc");
+
+	for(;;){
+		int n;
+
+		n = read(usbfdin, buf, sizeof(buf));
+		if(n < 0){
+			rerrstr(err, sizeof(err));
+			if(strstr(err, "interrupted"))
+				continue;
+			fprint(2, "usbreadproc: %s\n", err);
+			threadexitsall(err);
+		}
+		if(n == 0)
+			continue;
+		if(receivepacket(buf, n) == 0)
+			stats.in++;
+	}
+}
+
+void (*dprinter[])(Device *, int, ulong, void *b, int n) = {
+	[STRING] pstring,
+	[DEVICE] pdevice,
+	[FUNCTION] etherfunc,
+};
+
+Srv fs = 
+{
+.attach=		fsattach,
+.destroyfid=	fsdestroyfid,
+.walk1=		fswalk1,
+.open=		fsopen,
+.read=		fsread,
+.write=		fswrite,
+.stat=		fsstat,
+.flush=		fsflush,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-dD] [-m mtpt] [-s srv] [ctrlno n]\n", argv0);
+	exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char *srv, *mtpt, *r;
+	char s[64];
+	int ctrlno, id, epin, epout;
+	Device *d;
+
+	srv = nil;
+	mtpt = "/net";
+
+	ARGBEGIN {
+	case 'd':
+		debug = 1;
+		break;
+	case 'D':
+		chatty9p++;
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc != 0 && argc != 2)
+		usage();
+
+	if(argc == 2){
+		ctrlno = atoi(argv[0]);
+		id = atoi(argv[1]);
+		r = smprint("/dev/usb%d/%d", ctrlno, id);
+	} else {
+		r = finddevice(&ctrlno, &id);
+		if(r == nil){
+			fprint(2, "no device found\n");
+			return;
+		}
+	}
+
+	if(debug)
+		fprint(2, "using %d %d %s\n", ctrlno, id, r);
+
+	if((d = opendev(ctrlno, id)) == nil){
+		fprint(2, "opendev failed: %r\n");
+		exits("opendev");
+	}
+	if(describedevice(d) < 0){
+		fprint(2, "describedevice failed: %r\n");
+		exits("describedevice");
+	}
+	if(findendpoints(d, &epin, &epout)  < 0){
+		fprint(2, "no endpoints found!\n");
+		exits("findendpoints");
+	}
+
+	if(debug)
+		fprint(2, "endpoints in %d, out %d, maxpacket %d\n", epin, epout, maxpacket);
+
+	fprint(d->ctl, "ep %d bulk r %d 24", epin, maxpacket);
+	fprint(d->ctl, "ep %d bulk w %d 24", epout, maxpacket);
+	closedev(d);
+
+	sprint(s, "%s/ep%ddata", r, epin);
+	if((usbfdin = open(s, OREAD)) < 0){
+		fprint(2, "cant open receiving endpoint: %r\n");
+		exits("open");
+	}
+
+	sprint(s, "%s/ep%ddata", r, epout);
+	if((usbfdout = open(s, OWRITE)) < 0){
+		fprint(2, "cant open transmitting endpoint: %r\n");
+		exits("open");
+	}
+
+	proccreate(usbreadproc, nil, 8*1024);
+
+	atnotify(inote, 1);
+	time0 = time(0);
+	threadpostmountsrv(&fs, srv, mtpt, MBEFORE);
+}

+ 1139 - 0
sys/src/cmd/usb/ether/ether.c

@@ -0,0 +1,1139 @@
+/*
+ * usb/ether - usb ethernet adapter.
+ * BUG: This should use /dev/etherfile to
+ * use the kernel ether device code.
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "ether.h"
+
+typedef struct Dirtab Dirtab;
+
+enum
+{
+	/* Qids. Maintain order (relative to dirtabs structs) */
+	Qroot	= 0,
+	Qclone,
+	Qaddr,
+	Qifstats,
+	Qstats,
+	Qndir,
+	Qndata,
+	Qnctl,
+	Qnifstats,
+	Qnstats,
+	Qntype,
+	Qmax,
+};
+
+struct Dirtab
+{
+	char	*name;
+	int	qid;
+	int	mode;
+};
+
+typedef int (*Resetf)(Ether*);
+
+/*
+ * Controllers by vid/vid. Used to locate
+ * specific adapters that do not implement cdc ethernet
+ * Keep null terminated.
+ */
+Cinfo cinfo[] =
+{
+	/* Asix controllers.
+	 * Only A88178 and A881772 are implemented.
+	 * Others are easy to add by borrowing code
+	 * from other systems.
+	 */
+	{0x077b, 0x2226, A8817x},
+	{0x0b95, 0x1720, A8817x},
+	{0x0557, 0x2009, A8817x},
+	{0x0411, 0x003d, A8817x},
+	{0x0411, 0x006e, A88178},
+	{0x6189, 0x182d, A8817x},
+	{0x07aa, 0x0017, A8817x},
+	{0x1189, 0x0893, A8817x},
+	{0x1631, 0x6200, A8817x},
+	{0x04f1, 0x3008, A8817x},
+	{0x0b95, 0x1780, A88178},	/* Geoff */
+	{0x13b1, 0x0018, A88772},
+	{0x1557, 0x7720, A88772},
+	{0x07d1, 0x3c05, A88772},
+	{0x2001, 0x3c05, A88772},
+	{0x1737, 0x0039, A88178},
+	{0x050d, 0x5055, A88178},
+	{0x05ac, 0x1402, A88772},	/* Apple */
+	{0x0b95, 0x772a, A88772},
+	{0x14ea, 0xab11, A88178},
+	{0x0db0, 0xa877, A88772},
+	{0, 0, 0},
+};
+
+/*
+ * Each etherU%d is the root of our file system,
+ * which is added to the usb root directory. We only
+ * have to concern ourselfs with each /etherU%d subtree.
+ *
+ * NB: Maintain order in dirtabs, relative to the Qids enum.
+ */
+
+static Dirtab rootdirtab[] =
+{
+	"/",		Qroot,		DMDIR|0555,	/* etherU%d */
+	"clone",	Qclone,		0666,
+	"addr",		Qaddr,		0444,
+	"ifstats",	Qifstats,	0444,
+	"stats",	Qstats,		0444,
+	/* one dir per connection here */
+	nil, 0, 0,
+};
+
+static Dirtab conndirtab[] =
+{
+	"%d",		Qndir,		DMDIR|0555,
+	"data",		Qndata,		0666,
+	"ctl",		Qnctl,		0666,
+	"ifstats",	Qnifstats,	0444,
+	"stats",	Qnstats,	0444,
+	"type",		Qntype,		0444,
+	nil, 0,
+};
+
+int etherdebug;
+
+Resetf ethers[] =
+{
+	asixreset,
+	cdcreset,	/* keep last */
+};
+
+static int
+qtype(vlong q)
+{
+	return q&0xFF;
+}
+
+static int
+qnum(vlong q)
+{
+	return (q >> 8) & 0xFFFFFF;
+}
+
+static uvlong
+mkqid(int n, int t)
+{
+	uvlong q;
+
+	q =  (n&0xFFFFFF) << 8 | t&0xFF;
+	return q;
+}
+
+static void
+freebuf(Ether *e, Buf *bp)
+{
+	if(0)deprint(2, "%s: freebuf %#p\n", argv0, bp);
+	if(bp != nil){
+		qlock(e);
+		e->nbufs--;
+		qunlock(e);
+		sendp(e->bc, bp);
+	}
+}
+
+static Buf*
+allocbuf(Ether *e)
+{
+	Buf *bp;
+
+	bp = nbrecvp(e->bc);
+	if(bp == nil){
+		qlock(e);
+		if(e->nabufs < Nconns){
+			bp = emallocz(sizeof(Buf), 1);
+			e->nabufs++;
+			setmalloctag(bp, getcallerpc(&e));
+			deprint(2, "%s: %d buffers\n", argv0, e->nabufs);
+		}
+		qunlock(e);
+	}
+	if(bp == nil)
+		bp = recvp(e->bc);
+	bp->rp = bp->data + Hdrsize;
+	bp->ndata = 0;
+	if(0)deprint(2, "%s: allocbuf %#p\n", argv0, bp);
+	qlock(e);
+	e->nbufs++;
+	qunlock(e);
+	return bp;
+}
+
+static Conn*
+newconn(Ether *e)
+{
+	int i;
+	Conn *c;
+
+	qlock(e);
+	for(i = 0; i < nelem(e->conns); i++){
+		c = e->conns[i];
+		if(c == nil || c->ref == 0){
+			if(c == nil){
+				c = emallocz(sizeof(Conn), 1);
+				c->rc = chancreate(sizeof(Buf*), 2);
+				c->nb = i;
+			}
+			c->ref = 1;
+			if(i == e->nconns)
+				e->nconns++;
+			e->conns[i] = c;
+			deprint(2, "%s: newconn %d\n", argv0, i);
+			qunlock(e);
+			return c;
+		}
+	}
+	qunlock(e);
+	return nil;
+}
+
+static char*
+seprintaddr(char *s, char *se, uchar *addr)
+{
+	int i;
+
+	for(i = 0; i < Eaddrlen; i++)
+		s = seprint(s, se, "%02x", addr[i]);
+	return s;
+}
+
+void
+dumpframe(char *tag, void *p, int n)
+{
+	Etherpkt *ep;
+	char buf[128];
+	char *s, *se;
+	int i;
+
+	ep = p;
+	if(n < Eaddrlen * 2 + 2){
+		fprint(2, "short packet (%d bytes)\n", n);
+		return;
+	}
+	se = buf+sizeof(buf);
+	s = seprint(buf, se, "%s [%d]: ", tag, n);
+	s = seprintaddr(s, se, ep->s);
+	s = seprint(s, se, " -> ");
+	s = seprintaddr(s, se, ep->d);
+	s = seprint(s, se, " type 0x%02ux%02ux ", ep->type[0], ep->type[1]);
+	n -= Eaddrlen * 2 + 2;
+	for(i = 0; i < n && i < 16; i++)
+		s = seprint(s, se, "%02x", ep->data[i]);
+	if(n >= 16)
+		fprint(2, "%s...\n", buf);
+	else
+		fprint(2, "%s\n", buf);
+}
+
+static char*
+seprintstats(char *s, char *se, Ether *e)
+{
+	qlock(e);
+	s = seprint(s, se, "in: %ld\n", e->nin);
+	s = seprint(s, se, "out: %ld\n", e->nout);
+	s = seprint(s, se, "input errs: %ld\n", e->nierrs);
+	s = seprint(s, se, "output errs: %ld\n", e->noerrs);
+	s = seprint(s, se, "mbps: %d\n", e->mbps);
+	s = seprint(s, se, "prom: %ld\n", e->prom.ref);
+	s = seprint(s, se, "addr: ");
+	s = seprintaddr(s, se, e->addr);
+	s = seprint(s, se, "\n");
+	qunlock(e);
+	return s;
+}
+
+static char*
+seprintifstats(char *s, char *se, Ether *e)
+{
+	int i;
+	Conn *c;
+
+	qlock(e);
+	s = seprint(s, se, "ctlr id: %#x\n", e->cid);
+	s = seprint(s, se, "phy: %#x\n", e->phy);
+	s = seprint(s, se, "exiting: %#x\n", e->exiting);
+	s = seprint(s, se, "conns: %d\n", e->nconns);
+	s = seprint(s, se, "allocated bufs: %d\n", e->nabufs);
+	s = seprint(s, se, "used bufs: %d\n", e->nbufs);
+	for(i = 0; i < nelem(e->conns); i++){
+		c = e->conns[i];
+		if(c == nil)
+			continue;
+		if(c->ref == 0)
+			s = seprint(s, se, "c[%d]: free\n", i);
+		else{
+			s = seprint(s, se, "c[%d]: refs %ld t %#x h %d p %d\n",
+				c->nb, c->ref, c->type, c->headersonly, c->prom);
+		}
+	}
+	qunlock(e);
+	return s;
+}
+
+static void
+etherdump(Ether *e)
+{
+	char buf[256];
+
+	if(etherdebug == 0)
+		return;
+	seprintifstats(buf, buf+sizeof(buf), e);
+	fprint(2, "%s: ether %#p:\n%s\n", argv0, e, buf);
+}
+
+static Conn*
+getconn(Ether *e, int i, int idleok)
+{
+	Conn *c;
+
+	qlock(e);
+	if(i < 0 || i >= e->nconns)
+		c = nil;
+	else{
+		c = e->conns[i];
+		if(idleok == 0 && c != nil && c->ref == 0)
+			c = nil;
+	}
+	qunlock(e);
+	return c;
+}
+
+static void
+filldir(Usbfs *fs, Dir *d, Dirtab *tab, int cn)
+{
+	d->qid.path = mkqid(cn, tab->qid);
+	d->qid.path |= fs->qid;
+	d->mode = tab->mode;
+	if((d->mode & DMDIR) != 0)
+		d->qid.type = QTDIR;
+	else
+		d->qid.type = QTFILE;
+	if(tab->qid == Qndir)
+		sprint(d->name, "%d", cn);
+	else
+		d->name = tab->name;
+}
+
+static int
+rootdirgen(Usbfs *fs, Qid, int i, Dir *d, void *)
+{
+	Ether *e;
+	Dirtab *tab;
+	int cn;
+
+	e = fs->aux;
+	i++;				/* skip root */
+	cn = 0;
+	if(i < nelem(rootdirtab) - 1)	/* null terminated */
+		tab = &rootdirtab[i];
+	else{
+		cn = i - nelem(rootdirtab) + 1;
+		if(cn < e->nconns)
+			tab = &conndirtab[0];
+		else
+			return -1;
+	}
+	filldir(fs, d, tab, cn);
+	return 0;
+	
+}
+
+static int
+conndirgen(Usbfs *fs, Qid q, int i, Dir *d, void *)
+{
+	Dirtab *tab;
+
+	i++;				/* skip root */
+	if(i < nelem(conndirtab) - 1)	/* null terminated */
+		tab = &conndirtab[i];
+	else
+		return -1;
+	filldir(fs, d, tab, qnum(q.path));
+	return 0;
+}
+
+static int
+fswalk(Usbfs *fs, Fid *fid, char *name)
+{
+	Ether *e;
+	int i;
+	Qid qid;
+	char *es;
+	Dirtab *tab;
+	int cn;
+
+	e = fs->aux;
+	qid = fid->qid;
+	qid.path &= ~fs->qid;
+	if((qid.type & QTDIR) == 0){
+		werrstr("walk in non-directory");
+		return -1;
+	}
+
+	if(strcmp(name, "..") == 0){
+		/* must be /etherU%d; i.e. our root dir. */
+		fid->qid.path = mkqid(0, Qroot) | fs->qid;
+		fid->qid.vers = 0;
+		fid->qid.type = QTDIR;
+		return 0;
+	}
+	switch(qtype(qid.path)){
+	case Qroot:
+		if(name[0] >= '0' && name[0] <= '9'){
+			es = name;
+			cn = strtoul(name, &es, 10);
+			if(cn >= e->nconns || *es != 0){
+				werrstr(Enotfound);
+				return -1;
+			}
+			fid->qid.path = mkqid(cn, Qndir) | fs->qid;
+			fid->qid.vers = 0;
+			return 0;
+		}
+		/* fall */
+	case Qndir:
+		if(qtype(qid.path) == Qroot)
+			tab = rootdirtab;
+		else
+			tab = conndirtab;
+		cn = qnum(qid.path);
+		for(i = 0; tab[i].name != nil; tab++)
+			if(strcmp(tab[i].name, name) == 0){
+				fid->qid.path = mkqid(cn, tab[i].qid)|fs->qid;
+				fid->qid.vers = 0;
+				if((tab[i].mode & DMDIR) != 0)
+					fid->qid.type = QTDIR;
+				else
+					fid->qid.type = QTFILE;
+				return 0;
+			}
+		break;
+	default:
+		sysfatal("usb: ether: fswalk bug");
+	}
+	return -1;
+}
+
+static Dirtab*
+qdirtab(vlong q)
+{
+	int qt;
+	Dirtab *tab;
+	int i;
+
+	qt = qtype(q);
+	if(qt < nelem(rootdirtab) - 1){	/* null terminated */
+		tab = rootdirtab;
+		i = qt;
+	}else{
+		tab = conndirtab;
+		i = qt - (nelem(rootdirtab) - 1);
+		assert(i < nelem(conndirtab) - 1);
+	}
+	return &tab[i];
+}
+
+static int
+fsstat(Usbfs *fs, Qid qid, Dir *d)
+{
+	filldir(fs, d, qdirtab(qid.path), qnum(qid.path));
+	return 0;
+}
+
+static int
+fsopen(Usbfs *fs, Fid *fid, int omode)
+{
+	Ether *e;
+	int qt;
+	Dirtab *tab;
+	Conn *c;
+	vlong qid;
+
+	qid = fid->qid.path & ~fs->qid;
+	e = fs->aux;
+	qt = qtype(qid);
+	tab = qdirtab(qid);
+	omode &= 3;
+	if(omode != OREAD && (tab->mode&0222) == 0){
+		werrstr(Eperm);
+		return -1;
+	}
+	switch(qt){
+	case Qclone:
+		c = newconn(e);
+		if(c == nil){
+			werrstr("no more connections");
+			return -1;
+		}
+		fid->qid.type = QTFILE;
+		fid->qid.path = mkqid(c->nb, Qnctl)|fs->qid;
+		fid->qid.vers = 0;
+		break;
+	case Qndata:
+	case Qnctl:
+	case Qnifstats:
+	case Qnstats:
+	case Qntype:
+		c = getconn(e, qnum(qid), 1);
+		if(c == nil)
+			sysfatal("usb: ether: fsopen bug");
+		incref(c);
+		break;
+	}
+	etherdump(e);
+	return 0;
+}
+
+static int
+prom(Ether *e, int set)
+{
+	if(e->promiscuous != nil)
+		return e->promiscuous(e, set);
+	return 0;
+}
+
+static void
+fsclunk(Usbfs *fs, Fid *fid)
+{
+	Ether *e;
+	int qt;
+	Conn *c;
+	vlong qid;
+	Buf *bp;
+
+	e = fs->aux;
+	qid = fid->qid.path & ~fs->qid;
+	qt = qtype(qid);
+	switch(qt){
+	case Qndata:
+	case Qnctl:
+	case Qnifstats:
+	case Qnstats:
+	case Qntype:
+		if(fid->omode != ONONE){
+			c = getconn(e, qnum(qid), 0);
+			if(c == nil)
+				sysfatal("usb: ether: fsopen bug");
+			if(decref(c) == 0){
+				while((bp = nbrecvp(c->rc)) != nil)
+					freebuf(e, bp);
+				qlock(e);
+				if(c->prom != 0)
+					if(decref(&e->prom) == 0)
+						prom(e, 0);
+				c->prom = c->type = 0;
+				qunlock(e);
+			}
+		}
+		break;
+	}
+	etherdump(e);
+}
+
+int
+parseaddr(uchar *m, char *s)
+{
+	int i;
+	int n;
+	uchar v;
+
+	if(strlen(s) < 12)
+		return -1;
+	if(strlen(s) > 12 && strlen(s) < 17)
+		return -1;
+	for(i = n = 0; i < strlen(s); i++){
+		if(s[i] == ':')
+			continue;
+		if(s[i] >= 'A' && s[i] <= 'F')
+			v = 10 + s[i] - 'A';
+		else if(s[i] >= 'a' && s[i] <= 'f')
+			v = 10 + s[i] - 'a';
+		else if(s[i] >= '0' && s[i] <= '9')
+			v = s[i] - '0';
+		else
+			return -1;
+		if(n&1)
+			m[n/2] |= v;
+		else
+			m[n/2] = v<<4;
+		n++;
+	}
+	return 0;
+}
+
+static long
+fsread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
+{
+	Qid q;
+	Buf *bp;
+	Ether *e;
+	int qt;
+	int cn;
+	char buf[128];
+	char *s;
+	char *se;
+	Conn *c;
+
+	q = fid->qid;
+	q.path &= ~fs->qid;
+	e = fs->aux;
+	s = buf;
+	se = buf+sizeof(buf);
+	qt = qtype(q.path);
+	cn = qnum(q.path);
+	switch(qt){
+	case Qroot:
+		count = usbdirread(fs, q, data, count, offset, rootdirgen, nil);
+		break;
+	case Qaddr:
+		s = seprintaddr(s, se, e->addr);
+		count = usbreadbuf(data, count, offset, buf, s - buf);
+		break;
+	case Qnifstats:
+		/* BUG */
+	case Qifstats:
+		s = seprintifstats(s, se, e);
+		if(e->seprintstats != nil)
+			s = e->seprintstats(s, se, e);
+		count = usbreadbuf(data, count, offset, buf, s - buf);
+		break;
+	case Qnstats:
+		/* BUG */
+	case Qstats:
+		s = seprintstats(s, se, e);
+		count = usbreadbuf(data, count, offset, buf, s - buf);
+		break;
+
+	case Qndir:
+		count = usbdirread(fs, q, data, count, offset, conndirgen, nil);
+		break;
+	case Qndata:
+		c = getconn(e, cn, 0);
+		if(c == nil){
+			werrstr(Eio);
+			return -1;
+		}
+		bp = recvp(c->rc);
+		if(bp == nil)
+			return -1;
+		if(etherdebug > 1)
+			dumpframe("etherin", bp->rp, bp->ndata);
+		count = usbreadbuf(data, count, 0LL, bp->rp, bp->ndata);
+		freebuf(e, bp);
+		break;
+	case Qnctl:
+		s = seprint(s, se, "%11d ", cn);
+		count = usbreadbuf(data, count, offset, buf, s - buf);
+		break;
+	case Qntype:
+		c = getconn(e, cn, 0);
+		if(c == nil)
+			s = seprint(s, se, "%11d ", 0);
+		else
+			s = seprint(s, se, "%11d ", c->type);
+		count = usbreadbuf(data, count, offset, buf, s - buf);
+		break;
+	default:
+		sysfatal("usb: ether: fsread bug");
+	}
+	return count;
+}
+
+static int
+typeinuse(Ether *e, int t)
+{
+	int i;
+
+	for(i = 0; i < e->nconns; i++)
+		if(e->conns[i]->ref > 0 && e->conns[i]->type == t)
+			return 1;
+	return 0;
+}
+
+static int
+isloopback(Ether *e, Buf *)
+{
+	return e->prom.ref > 0; /* BUG: also loopbacks and broadcasts */
+}
+
+static int
+etherctl(Ether *e, Conn *c, char *buf)
+{
+	uchar addr[Eaddrlen];
+	int t;
+
+	deprint(2, "%s: etherctl: %s\n", argv0, buf);
+	if(strncmp(buf, "connect ", 8) == 0){
+		t = atoi(buf+8);
+		qlock(e);
+		if(typeinuse(e, t)){
+			werrstr("type already in use");
+			qunlock(e);
+			return -1;
+		}
+		c->type = atoi(buf+8);
+		qunlock(e);
+		return 0;
+	}
+	if(strncmp(buf, "nonblocking", 11) == 0){
+		if(buf[11] == '\n' || buf[11] == 0)
+			e->nblock = 1;
+		else
+			e->nblock = atoi(buf + 12);
+		deprint(2, "%s: nblock %d\n", argv0, e->nblock);
+		return 0;
+	}
+	if(strncmp(buf, "promiscuous", 11) == 0){
+		if(c->prom == 0)
+			incref(&e->prom);
+		c->prom = 1;
+		return prom(e, 1);
+	}
+	if(strncmp(buf, "headersonly", 11) == 0){
+		c->headersonly = 1;
+		return 0;
+	}
+	if(!strncmp(buf, "addmulti ", 9) || !strncmp(buf, "remmulti ", 9)){
+		if(parseaddr(addr, buf+9) < 0){
+			werrstr("bad address");
+			return -1;
+		}
+		if(e->multicast == nil)
+			return 0;
+		if(strncmp(buf, "add", 3) == 0){
+			e->nmcasts++;
+			return e->multicast(e, addr, 1);
+		}else{
+			e->nmcasts--;
+			return e->multicast(e, addr, 0);
+		}
+	}
+
+	if(e->ctl != nil)
+		return e->ctl(e, buf);
+	werrstr(Ebadctl);
+	return -1;
+}
+
+static long
+etherbread(Ether *e, Buf *bp)
+{
+	deprint(2, "%s: etherbread\n", argv0);
+	bp->rp = bp->data + Hdrsize;
+	bp->ndata = read(e->epin->dfd, bp->rp, sizeof(bp->data)-Hdrsize);
+	deprint(2, "%s: etherbread got %d bytes\n", argv0, bp->ndata);
+	return bp->ndata;
+}
+
+static long
+etherbwrite(Ether *e, Buf *bp)
+{
+	long n;
+
+	deprint(2, "%s: etherbwrite %d bytes\n", argv0, bp->ndata);
+	n = write(e->epout->dfd, bp->rp, bp->ndata);
+	deprint(2, "%s: etherbwrite wrote %ld bytes\n", argv0, n);
+	if(n <= 0)
+		return n;
+	if((bp->ndata % e->epout->maxpkt) == 0){
+		deprint(2, "%s: short pkt write\n", argv0);
+		write(e->epout->dfd, "", 0);
+	}
+	return n;
+}
+
+static long
+fswrite(Usbfs *fs, Fid *fid, void *data, long count, vlong)
+{
+	Qid q;
+	Ether *e;
+	int qt;
+	int cn;
+	char buf[128];
+	Conn *c;
+	Buf *bp;
+
+	q = fid->qid;
+	q.path &= ~fs->qid;
+	e = fs->aux;
+	qt = qtype(q.path);
+	cn = qnum(q.path);
+	switch(qt){
+	case Qndata:
+		c = getconn(e, cn, 0);
+		if(c == nil){
+			werrstr(Eio);
+			return -1;
+		}
+		bp = allocbuf(e);
+		if(count > sizeof(bp->data)-Hdrsize)
+			count = sizeof(bp->data)-Hdrsize;
+		memmove(bp->rp, data, count);
+		bp->ndata = count;
+		if(etherdebug > 1)
+			dumpframe("etherout", bp->rp, bp->ndata);
+		if(e->nblock == 0)
+			sendp(e->wc, bp);
+		else if(nbsendp(e->wc, bp) < 0){
+			deprint(2, "%s: (out) packet lost\n", argv0);
+			freebuf(e, bp);
+		}
+		break;
+	case Qnctl:
+		c = getconn(e, cn, 0);
+		if(c == nil){
+			werrstr(Eio);
+			return -1;
+		}
+		if(count > sizeof(buf) - 1)
+			count = sizeof(buf) - 1;
+		memmove(buf, data, count);
+		buf[count] = 0;
+		if(etherctl(e, c, buf) < 0)
+			return -1;
+		break;
+	default:
+		sysfatal("usb: ether: fsread bug");
+	}
+	return count;
+}
+
+static int
+openeps(Ether *e, int epin, int epout)
+{
+	e->epin = openep(e->dev, epin);
+	if(e->epin == nil){
+		fprint(2, "ether: in: openep %d: %r\n", epin);
+		return -1;
+	}
+	if(epout == epin){
+		incref(e->epin);
+		e->epout = e->epin;
+	}else
+		e->epout = openep(e->dev, epout);
+	if(e->epout == nil){
+		fprint(2, "ether: out: openep %d: %r\n", epout);
+		closedev(e->epin);
+		return -1;
+	}
+	if(e->epin == e->epout)
+		opendevdata(e->epin, ORDWR);
+	else{
+		opendevdata(e->epin, OREAD);
+		opendevdata(e->epout, OWRITE);
+	}
+	if(e->epin->dfd < 0 || e->epout->dfd < 0){
+		fprint(2, "ether: open i/o ep data: %r\n");
+		closedev(e->epin);
+		closedev(e->epout);
+		return -1;
+	}
+	dprint(2, "ether: ep in %s out %s\n", e->epin->dir, e->epout->dir);
+
+	if(usbdebug > 2 || etherdebug > 2){
+		devctl(e->epin, "debug 1");
+		devctl(e->epout, "debug 1");
+		devctl(e->dev, "debug 1");
+	}
+	return 0;
+}
+
+static int
+usage(void)
+{
+	werrstr("usage: usb/ether [-d]");
+	return -1;
+}
+
+static Usbfs etherfs = {
+	.walk = fswalk,
+	.open =	 fsopen,
+	.read =	 fsread,
+	.write = fswrite,
+	.stat =	 fsstat,
+	.clunk = fsclunk,
+};
+
+static void
+etherfree(Ether *e)
+{
+	int i;
+	Buf *bp;
+
+	if(e->free != nil)
+		e->free(e);
+	closedev(e->epin);
+	closedev(e->epout);
+	if(e->rc == nil){
+		free(e);
+		return;
+	}
+	for(i = 0; i < e->nconns; i++)
+		if(e->conns[i] != nil){
+			while((bp = nbrecvp(e->conns[i]->rc)) != nil)
+				free(bp);
+			chanfree(e->conns[i]->rc);
+			free(e->conns[i]);
+		}
+	while((bp = nbrecvp(e->bc)) != nil)
+		free(bp);
+	chanfree(e->bc);
+	chanfree(e->rc);
+	/* chanfree(e->wc);	released by writeproc */
+	e->epin = e->epout = nil;
+	free(e);
+	
+}
+
+/* must return 1 if c wants bp; 0 if not */
+static int
+cwantsbp(Conn *c, Buf *bp)
+{
+	if(c->ref != 0 && (c->prom != 0 || c->type < 0 || c->type == bp->type))
+		return 1;
+	return 0;
+}
+
+static void
+etherwriteproc(void *a)
+{
+	Ether *e = a;
+	Buf *bp;
+	Channel *wc;
+
+	wc = e->wc;
+	while(e->exiting == 0){
+		bp = recvp(wc);
+		if(bp == nil || e->exiting != 0)
+			break;
+		e->nout++;
+		if(e->bwrite(e, bp) < 0)
+			e->noerrs++;
+		if(isloopback(e, bp))
+			sendp(e->rc, bp); /* send to input queue */
+		else
+			freebuf(e, bp);
+	}
+	while((bp = nbrecvp(wc)) != nil)
+		free(bp);
+	chanfree(wc);
+	deprint(2, "%s: writeproc exiting\n", argv0);
+}
+
+static void
+etherreadproc(void *a)
+{
+	Ether *e = a;
+	int i;
+	int n;
+	Buf *bp;
+	Buf *dbp;
+	int nwants;
+
+	while(e->exiting == 0){
+		bp = nbrecvp(e->rc);
+		if(bp == nil){
+			bp = allocbuf(e);	/* seems to leak bps kept at bc */
+			if(e->bread(e, bp) < 0){
+				freebuf(e, bp);
+				break;
+			}
+			if(bp->ndata == 0){
+				/* may be a short packet; continue */
+				if(0)dprint(2, "%s: read: short\n", argv0);
+				freebuf(e, bp);
+				continue;
+			}
+		}
+		e->nin++;
+		nwants = 0;
+		for(i = 0; i < e->nconns; i++)
+			nwants += cwantsbp(e->conns[i], bp);
+		for(i = 0; nwants > 0 && i < e->nconns; i++)
+			if(cwantsbp(e->conns[i], bp)){
+				n = bp->ndata;
+				if(e->conns[i]->type == -2 && n > 64)
+					n = 64;
+				if(nwants-- == 1){
+					bp->ndata = n;
+					dbp = bp;
+					bp = nil;
+				}else{
+					dbp = allocbuf(e);
+					memmove(dbp->rp, bp->rp, n);
+					dbp->ndata = n;
+				}
+				if(nbsendp(e->conns[i]->rc, dbp) < 0){
+					e->nierrs++;
+					freebuf(e, dbp);
+				}
+			}
+		freebuf(e, bp);
+	}
+	while(e->exiting == 0)	/* give them time... */
+		yield();
+	while((bp = nbrecvp(e->rc)) != nil)
+		free(bp);
+	deprint(2, "%s: writeproc exiting\n", argv0);
+	etherfree(e);
+}
+
+static void
+etherdevfree(void *a)
+{
+	Ether *e = a;
+
+	if(e == nil)
+		return;
+	if(e->free != nil)
+		e->free(e);
+	if(e->rc == nil){
+		/* no readproc; free everything ourselves */
+		etherfree(e);
+		return;
+	}
+	/* ether resources released by etherreadproc
+	 * It will exit its main look for sure, because
+	 * the endpoints must be detached by now.
+	 */
+	close(e->epin->dfd);
+	e->epin->dfd = -1;
+	close(e->epout->dfd);
+	e->epout->dfd = -1;
+	e->exiting = 1;
+}
+
+static void
+setalt(Dev *d, int ifcid, int altid)
+{
+	int r;
+
+	r = Rh2d|Rstd|Riface;
+	if(usbcmd(d, r, Rsetiface, altid, ifcid, nil, 0) < 0)
+		dprint(2, "%s: setalt ifc %d alt %d: %r\n", argv0, ifcid, altid);
+}
+
+static int
+ifaceinit(Ether *e, Iface *ifc, int *ei, int *eo)
+{
+	int i;
+	Ep *ep;
+	int epin;
+	int epout;
+	int altid;
+
+	if(ifc == nil)
+		return -1;
+
+	altid = epin = epout = -1;
+	for(i = 0; (epin < 0 || epout < 0) && i < nelem(ifc->ep); i++)
+		if((ep = ifc->ep[i]) != nil && ep->type == Ebulk){
+			if(ep->dir == Eboth || ep->dir == Ein)
+				if(epin == -1)
+					epin =  ep->id;
+			if(ep->dir == Eboth || ep->dir == Eout)
+				if(epout == -1)
+					epout = ep->id;
+		}
+	if(epin == -1 || epout == -1)
+		return -1;
+	dprint(2, "ether: ep ids: in %d out %d\n", epin, epout);
+	for(i = 0; i < nelem(ifc->altc); i++)
+		if(ifc->altc[i] != nil){
+			altid = ifc->altc[i]->attrib;
+			break;
+		}
+	if(altid != -1)
+		setalt(e->dev, ifc->id, altid);
+
+	*ei = epin;
+	*eo = epout;
+	return 0;
+}
+
+static int
+etherinit(Ether *e, int *ei, int *eo)
+{
+	Usbdev *ud;
+	Conf *c;
+	int i;
+	int j;
+
+	*ei = *eo = -1;
+	ud = e->dev->usb;
+	for(i = 0; i < nelem(ud->conf); i++)
+		if((c = ud->conf[i]) != nil)
+			for(j = 0; j < nelem(c->iface); j++)
+				if(ifaceinit(e,c->iface[j],ei,eo) != -1)
+					return 0;
+	dprint(2, "%s: no valid endpoints", argv0);
+	return -1;
+}
+
+int
+ethermain(Dev *dev, int argc, char **argv)
+{
+	Ether *e;
+	int epin;
+	int epout;
+	int i;
+
+	ARGBEGIN{
+	case 'd':
+		if(etherdebug == 0)
+			fprint(2, "ether debug on\n");
+		etherdebug++;
+		break;
+	default:
+		return usage();
+	}ARGEND
+	if(argc != 0)
+		return usage();
+
+	e = dev->aux = emallocz(sizeof(Ether), 1);
+	e->dev = dev;
+	dev->free = etherdevfree;
+
+	for(i = 0; i < nelem(ethers); i++)
+		if(ethers[i](e) == 0)
+			break;
+	if(i == nelem(ethers))
+		return -1;
+	if(e->init == nil)
+		e->init = etherinit;
+	if(e->init(e, &epin, &epout) < 0)
+		return -1;
+	if(e->bwrite == nil)
+		e->bwrite = etherbwrite;
+	if(e->bread == nil)
+		e->bread = etherbread;
+
+	if(openeps(e, epin, epout) < 0)
+		return -1;
+	e->fs = etherfs;
+	snprint(e->fs.name, sizeof(e->fs.name), "etherU%d", dev->id);
+	e->fs.dev = dev;
+	e->fs.aux = e;
+	e->bc = chancreate(sizeof(Buf*), Nconns);
+	e->rc = chancreate(sizeof(Buf*), Nconns/2);
+	e->wc = chancreate(sizeof(Buf*), Nconns*2);
+	proccreate(etherwriteproc, e, 16*1024);
+	proccreate(etherreadproc, e, 16*1024);
+	deprint(2, "%s: dev ref %ld\n", argv0, dev->ref);
+	usbfsadd(&e->fs);
+	return 0;
+}

+ 115 - 0
sys/src/cmd/usb/ether/ether.h

@@ -0,0 +1,115 @@
+typedef struct Ether Ether;
+typedef struct Etherops Etherops;
+typedef struct Conn Conn;
+typedef struct Cinfo Cinfo;
+typedef struct Buf Buf;
+typedef struct Etherpkt Etherpkt;
+
+enum
+{
+	/* controller ids */
+	Cdc = 0,
+	A8817x,		/* Asis */
+	A88178,
+	A88179,
+	A88772,
+
+	Eaddrlen = 6,
+	Epktlen = 1514,
+	Maxpkt	= 2000,	/* no jumbo packets here */
+	Nconns	= 8,	/* max number of connections */
+	Nbufs	= 8,	/* max number of buffers */
+	Scether = 6,	/* ethernet cdc subclass */
+	Fnheader = 0,	/* Functions */
+	Fnunion = 6,
+	Fnether = 15,
+};
+
+struct Buf
+{
+	int	type;
+	int	ndata;
+	uchar*	rp;
+	uchar	data[Hdrsize+Maxpkt];
+};
+
+struct Conn
+{
+	Ref;			/* one per file in use */
+	int	nb;
+	int	type;
+	int	headersonly;
+	int	prom;
+	Channel*rc;		/* [2] of Buf* */
+};
+
+struct Etherops
+{
+	int	(*init)(Ether*, int *epin, int *epout);
+	long	(*bread)(Ether*, Buf*);
+	long	(*bwrite)(Ether*, Buf*);
+	int	(*ctl)(Ether*, char*);
+	int	(*promiscuous)(Ether*, int);
+	int	(*multicast)(Ether*, uchar*, int);
+	char*	(*seprintstats)(char*, char*, Ether*);
+	void	(*free)(Ether*);
+	void*	aux;
+};
+
+struct Ether
+{
+	QLock;
+	QLock	wlck;			/* write one at a time */
+	int	epinid;			/* epin address */
+	int	epoutid;			/* epout address */
+	Dev*	dev;
+	Dev*	epin;
+	Dev*	epout;
+	int	cid;			/* ctlr id */
+	int	phy;			/* phy id */
+	Ref	prom;			/* nb. of promiscuous conns */
+	int	exiting;			/* shutting it down */
+	uchar	addr[Eaddrlen];		/* mac */
+	int	nconns;			/* nb. of entries used in... */
+	Conn*	conns[Nconns];		/* connections */
+	int	nabufs;			/* nb. of allocated buffers */
+	int	nbufs;			/* nb. of buffers in use */
+	int	nblock;			/* nonblocking (output)? */
+	long	nin;
+	long	nout;
+	long	nierrs;
+	long	noerrs;
+	int	mbps;
+	int	nmcasts;
+	Channel*rc;			/* read channel (of Buf*) */
+	Channel*wc;			/* write channel (of Buf*) */
+	Channel*bc;			/* free buf. chan. (of Buf*) */
+	Etherops;
+	Usbfs	fs;
+};
+
+struct Cinfo
+{
+	int vid;		/* usb vendor id */
+	int did;		/* usb device/product id */
+	int cid;		/* controller id assigned by us */
+};
+
+struct Etherpkt
+{
+	uchar d[Eaddrlen];
+	uchar s[Eaddrlen];
+	uchar type[2];
+	uchar data[1500];
+};
+
+int	ethermain(Dev *dev, int argc, char **argv);
+int	asixreset(Ether*);
+int	cdcreset(Ether*);
+int	parseaddr(uchar *m, char *s);
+void	dumpframe(char *tag, void *p, int n);
+
+extern Cinfo cinfo[];
+extern int etherdebug;
+
+#define	deprint	if(etherdebug)fprint

+ 89 - 0
sys/src/cmd/usb/ether/main.c

@@ -0,0 +1,89 @@
+/*
+ * usb/ether - usb ethernet adapter.
+ * BUG: This should use /dev/etherfile to
+ * use the kernel ether device code.
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "ether.h"
+
+enum
+{
+	Arglen = 80,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-Dd] [-m mnt] [-s srv] [dev...]\n", argv0);
+	threadexitsall("usage");
+}
+
+/*
+ * Ether devices may be weird.
+ * Be optimistic and try to use any communication
+ * device or one of the `vendor specific class' devices
+ * that we know are ethernets.
+ */
+static int
+matchether(char *info, void*)
+{
+	Cinfo *ip;
+	char buf[50];
+
+	/*
+	 * I have an ether reporting comms.0.0
+	 */
+	if(strstr(info, "comms") != nil)
+		return 0;
+	for(ip = cinfo; ip->vid != 0; ip++){
+		snprint(buf, sizeof(buf), "vid %#06x did %#06x", ip->vid, ip->did);
+		if(strstr(info, buf) != nil)
+			return 0;
+	}
+	return -1;
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char args[Arglen];
+	char *as;
+	char *ae;
+	char *srv;
+	char *mnt;
+
+	srv = nil;
+	mnt = "/net";
+
+	quotefmtinstall();
+	ae = args+sizeof(args);
+	as = seprint(args, ae, "ether");
+	ARGBEGIN{
+	case 'D':
+		usbfsdebug++;
+		break;
+	case 'd':
+		usbdebug++;
+		as = seprint(as, ae, " -d");
+		break;
+	case 'm':
+		mnt = EARGF(usage());
+		break;
+	case 's':
+		srv = EARGF(usage());
+	default:
+		usage();
+	}ARGEND
+
+	rfork(RFNOTEG);
+	threadsetgrp(threadid());
+	fmtinstall('U', Ufmt);
+	usbfsinit(srv, mnt, &usbdirfs, MAFTER|MCREATE);
+	startdevs(args, argv, argc, matchether, nil, ethermain);
+	threadexits(nil);
+}

+ 25 - 0
sys/src/cmd/usb/ether/mkfile

@@ -0,0 +1,25 @@
+</$objtype/mkfile
+
+TARG=ether
+LIB=../lib/usb.a$O
+
+HFILES=\
+	ether.h\
+	../lib/usb.h\
+	../lib/usbfs.h\
+
+OFILES=\
+	main.$O\
+	ether.$O\
+	asix.$O\
+	cdc.$O\
+
+BIN=/$objtype/bin/usb
+</sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
+
+$LIB:
+	cd ../lib
+	mk install
+	mk clean
+

+ 30 - 27
sys/src/cmd/usb/kb/hid.h

@@ -2,41 +2,44 @@
  * USB keyboard/mouse constants
  * USB keyboard/mouse constants
  */
  */
 enum {
 enum {
+
+	Stack = 32 * 1024,
+
 	/* HID class subclass protocol ids */
 	/* HID class subclass protocol ids */
 	PtrCSP		= 0x020103,	/* mouse.boot.hid */
 	PtrCSP		= 0x020103,	/* mouse.boot.hid */
 	KbdCSP		= 0x010103,	/* keyboard.boot.hid */
 	KbdCSP		= 0x010103,	/* keyboard.boot.hid */
 
 
 	/* Requests */
 	/* Requests */
-	SET_PROTO	= 0x0b,
+	Getproto	= 0x03,
+	Setproto	= 0x0b,
 
 
 	/* protocols for SET_PROTO request */
 	/* protocols for SET_PROTO request */
-	BOOT_PROTO	= 0,
-	REPORT_PROTO	= 1,
+	Bootproto	= 0,
+	Reportproto	= 1,
 };
 };
 
 
 enum {
 enum {
 	/* keyboard modifier bits */
 	/* keyboard modifier bits */
-	Mlctrl=		0,
-	Mlshift=	1,
-	Mlalt=		2,
-	Mlgui=		3,
-	Mrctrl=		4,
-	Mrshift=	5,
-	Mralt=		6,
-	Mrgui=		7,
+	Mlctrl		= 0,
+	Mlshift		= 1,
+	Mlalt		= 2,
+	Mlgui		= 3,
+	Mrctrl		= 4,
+	Mrshift		= 5,
+	Mralt		= 6,
+	Mrgui		= 7,
 
 
 	/* masks for byte[0] */
 	/* masks for byte[0] */
-	Mctrl=		1<<Mlctrl | 1<<Mrctrl,
-	Mshift=		1<<Mlshift | 1<<Mrshift,
-	Malt=		1<<Mlalt | 1<<Mralt,
-	Mcompose=	1<<Mlalt,
-	Maltgr=		1<<Mralt,
-	Mgui=		1<<Mlgui | 1<<Mrgui,
+	Mctrl		= 1<<Mlctrl | 1<<Mrctrl,
+	Mshift		= 1<<Mlshift | 1<<Mrshift,
+	Malt		= 1<<Mlalt | 1<<Mralt,
+	Mcompose	= 1<<Mlalt,
+	Maltgr		= 1<<Mralt,
+	Mgui		= 1<<Mlgui | 1<<Mrgui,
 
 
 	MaxAcc = 3,			/* max. ptr acceleration */
 	MaxAcc = 3,			/* max. ptr acceleration */
 	PtrMask= 0xf,			/* 4 buttons: should allow for more. */
 	PtrMask= 0xf,			/* 4 buttons: should allow for more. */
 
 
-	Awakemsg=0xdeaddead,
 };
 };
 
 
 /*
 /*
@@ -44,14 +47,14 @@ enum {
  */
  */
 enum {
 enum {
 	/* Scan codes (see kbd.c) */
 	/* Scan codes (see kbd.c) */
-	SCesc1=		0xe0,		/* first of a 2-character sequence */
-	SCesc2=		0xe1,
-	SClshift=	0x2a,
-	SCrshift=	0x36,
-	SCctrl=		0x1d,
-	SCcompose=	0x38,
-	Keyup=		0x80,		/* flag bit */
-	Keymask=	0x7f,		/* regular scan code bits */
+	SCesc1		= 0xe0,		/* first of a 2-character sequence */
+	SCesc2		= 0xe1,
+	SClshift		= 0x2a,
+	SCrshift		= 0x36,
+	SCctrl		= 0x1d,
+	SCcompose	= 0x38,
+	Keyup		= 0x80,		/* flag bit */
+	Keymask		= 0x7f,		/* regular scan code bits */
 };
 };
 
 
-extern int hdebug;
+int kbmain(Dev *d, int argc, char*argv[]);

+ 301 - 370
sys/src/cmd/usb/kb/kb.c

@@ -16,6 +16,34 @@
 #include "usb.h"
 #include "usb.h"
 #include "hid.h"
 #include "hid.h"
 
 
+enum
+{
+	Awakemsg=0xdeaddead,
+	Diemsg = 0xbeefbeef,
+};
+
+typedef struct KDev KDev;
+typedef struct Kin Kin;
+
+struct KDev
+{
+	Dev*	dev;		/* usb device*/
+	Dev*	ep;		/* endpoint to get events */
+	Kin*	in;		/* used to send events to kernel */
+	Channel*repeatc;	/* only for keyboard */
+	int	accel;		/* only for mouse */
+};
+
+/*
+ * Kbdin and mousein files must be shared among all instances.
+ */
+struct Kin
+{
+	int	ref;
+	int	fd;
+	char*	name;
+};
+
 /*
 /*
  * Map for the logitech bluetooth mouse with 8 buttons and wheels.
  * Map for the logitech bluetooth mouse with 8 buttons and wheels.
  *	{ ptr ->mouse}
  *	{ ptr ->mouse}
@@ -34,7 +62,7 @@
  * key code to scan code; for the page table used by
  * key code to scan code; for the page table used by
  * the logitech bluetooth keyboard.
  * the logitech bluetooth keyboard.
  */
  */
-char sctab[256] = 
+static char sctab[256] = 
 {
 {
 [0x00]	0x0,	0x0,	0x0,	0x0,	0x1e,	0x30,	0x2e,	0x20,
 [0x00]	0x0,	0x0,	0x0,	0x0,	0x1e,	0x30,	0x2e,	0x20,
 [0x08]	0x12,	0x21,	0x22,	0x23,	0x17,	0x24,	0x25,	0x26,
 [0x08]	0x12,	0x21,	0x22,	0x23,	0x17,	0x24,	0x25,	0x26,
@@ -70,61 +98,45 @@ char sctab[256] =
 [0xf8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
 [0xf8]	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
 };
 };
 
 
-typedef struct Dev Dev;
-struct Dev {
-	char*	name;		/* debug */
-	void	(*proc)(void*);	/* handler process */
-	int	csp;		/* the csp we want */
-	int	enabled;	/* can we start it? */
-	int	ctlrno;		/* controller number, for probing*/
-	int	id;		/* device id, for probing*/
-	int	ep;		/* endpoint used to get events */
-	int	msz;		/* message size */
-	int	fd;		/* to the endpoint data file */
-	Device*	dev;		/* usb device*/
+static QLock inlck;
+static Kin kbdin =
+{
+	.ref = 0,
+	.name = "#Ι/kbin",
+	.fd = -1,
 };
 };
-
-
-Dev	kf, pf;			/* kbd and pointer drivers*/
-Channel *repeatc;		/* channel to request key repeating*/
-
-void (*dprinter[])(Device *, int, ulong, void *b, int n) = {
-	[STRING] pstring,
-	[DEVICE] pdevice,
-	[HID] phid,
+static Kin ptrin =
+{
+	.ref = 0,
+	.name = "#m/mousein",
+	.fd = -1,
 };
 };
 
 
-int	accel;
-int	hdebug;
-int	dryrun;
-int	verbose;
-
-int mainstacksize = 32*1024;
-
-int
-robusthandler(void*, char *s)
-{
-	if(hdebug)
-		fprint(2, "inthandler: %s\n", s);
-	return (s && (strstr(s, "interrupted")|| strstr(s, "hangup")));
-}
+static int kbdebug;
 
 
-long
-robustread(int fd, void *buf, long sz)
+static void
+kbfatal(KDev *kd, char *sts)
 {
 {
-	long r;
-	char err[ERRMAX];
-
-	do {
-		r = read(fd , buf, sz);
-		if(r < 0)
-			rerrstr(err, sizeof err);
-	} while(r < 0 && robusthandler(nil, err));
-	return r;
+	Dev *dev;
+
+	if(sts != nil)
+		fprint(2, "kd: %s: fatal: %s\n", kd->ep->dir, sts);
+	dev = kd->dev;
+	devctl(dev, "detach");
+	if(kd->repeatc != nil)
+		sendul(kd->repeatc, Diemsg);
+	closedev(kd->ep);
+	kd->ep = nil;
+	kd->dev = nil;
+	closedev(dev);
+	/*
+	 * free(kd); done by closedev.
+	 */
+	threadexits(sts);
 }
 }
 
 
 static int
 static int
-scale(int x)
+scale(KDev *f, int x)
 {
 {
 	int sign = 1;
 	int sign = 1;
 
 
@@ -139,10 +151,10 @@ scale(int x)
 	case 3:
 	case 3:
 		break;
 		break;
 	case 4:
 	case 4:
-		x = 6 + (accel>>2);
+		x = 6 + (f->accel>>2);
 		break;
 		break;
 	case 5:
 	case 5:
-		x = 9 + (accel>>1);
+		x = 9 + (f->accel>>1);
 		break;
 		break;
 	default:
 	default:
 		x *= MaxAcc;
 		x *= MaxAcc;
@@ -151,44 +163,54 @@ scale(int x)
 	return sign*x;
 	return sign*x;
 }
 }
 
 
-void
+/*
+ * ps2 mouse is processed mostly at interrupt time.
+ * for usb we do what we can.
+ */
+static void
+sethipri(void)
+{
+	char fn[30];
+	int fd;
+
+	snprint(fn, sizeof(fn), "/proc/%d/ctl", getpid());
+	fd = open(fn, OWRITE);
+	if(fd < 0)
+		return;
+	fprint(fd, "pri 13");
+	close(fd);
+}
+
+static void
 ptrwork(void* a)
 ptrwork(void* a)
 {
 {
-	int x, y, b, c, mfd, ptrfd;
-	char	buf[32];
-	Dev*	f = a;
 	static char maptab[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7};
 	static char maptab[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7};
+	int x, y, b, c, ptrfd;
+	int	mfd;
+	char	buf[32];
+	char	mbuf[80];
+	KDev*	f = a;
+	int	hipri;
 
 
-	ptrfd = f->fd;
-	if(ptrfd < 0)
-		return;
-	mfd = -1;
-	if(f->msz < 3 || f->msz > sizeof buf)
-		sysfatal("bug: ptrwork: bad mouse maxpkt");
-	if(!dryrun){
-		mfd = open("#m/mousein", OWRITE);
-		if(mfd < 0)
-			sysfatal("%s: mousein: %r", f->name);
-	}
+	hipri = 0;
+	ptrfd = f->ep->dfd;
+	mfd = f->in->fd;
+
+	if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
+		kbfatal(f, "weird maxpkt");
 	for(;;){
 	for(;;){
 		memset(buf, 0, sizeof buf);
 		memset(buf, 0, sizeof buf);
-		c = robustread(ptrfd, buf, f->msz);
-		if(c == 0)
-			fprint(2, "%s: %s: eof\n", argv0, f->name);
+		c = read(ptrfd, buf, f->ep->maxpkt);
 		if(c < 0)
 		if(c < 0)
-			fprint(2, "%s: %s: read: %r\n", argv0, f->name);
-		if(c <= 0){
-			if(!dryrun)
-				close(mfd);
-			close(f->fd);
-			threadexits("read");
-		}
+			fprint(2, "kb: %s: read: %r\n", f->ep->dir);
+		if(c <= 0)
+			kbfatal(f, nil);
 		if(c < 3)
 		if(c < 3)
 			continue;
 			continue;
-		if(accel) {
-			x = scale(buf[1]);
-			y = scale(buf[2]);
-		} else {
+		if(f->accel){
+			x = scale(f, buf[1]);
+			y = scale(f, buf[2]);
+		}else{
 			x = buf[1];
 			x = buf[1];
 			y = buf[2];
 			y = buf[2];
 		}
 		}
@@ -197,27 +219,28 @@ ptrwork(void* a)
 			b |= 0x08;
 			b |= 0x08;
 		if(c > 3 && buf[3] == -1)	/* down */
 		if(c > 3 && buf[3] == -1)	/* down */
 			b |= 0x10;
 			b |= 0x10;
-		if(hdebug)
-			fprint(2, "%s: %s: m%11d %11d %11d\n",
-				argv0, f->name, x, y, b);
-		if(!dryrun)
-			if(fprint(mfd, "m%11d %11d %11d", x, y, b) < 0){
-				fprint(2, "%s: #m/mousein: write: %r", argv0);
-				close(mfd);
-				close(f->fd);
-				threadexits("write");
-			}
+		if(kbdebug)
+			fprint(2, "%s: ptr: m%11d %11d %11d\n", argv0, x, y, b);
+		seprint(mbuf, mbuf+sizeof(mbuf), "m%11d %11d %11d", x, y,b);
+		if(write(mfd, mbuf, strlen(mbuf)) < 0){
+			fprint(2, "%s: #m/mousein: write: %r", argv0);
+			kbfatal(f, "mousein");
+		}
+		if(hipri == 0){
+			sethipri();
+			hipri = 1;
+		}
 	}
 	}
 }
 }
 
 
 static void
 static void
-stoprepeat(void)
+stoprepeat(KDev *f)
 {
 {
-	sendul(repeatc, Awakemsg);
+	sendul(f->repeatc, Awakemsg);
 }
 }
 
 
 static void
 static void
-startrepeat(uchar esc1, uchar sc)
+startrepeat(KDev *f, uchar esc1, uchar sc)
 {
 {
 	ulong c;
 	ulong c;
 
 
@@ -225,76 +248,84 @@ startrepeat(uchar esc1, uchar sc)
 		c = SCesc1 << 8 | (sc & 0xff);
 		c = SCesc1 << 8 | (sc & 0xff);
 	else
 	else
 		c = sc;
 		c = sc;
-	sendul(repeatc, c);
+	sendul(f->repeatc, c);
 }
 }
 
 
-static int kbinfd = -1;
-
 static void
 static void
-putscan(uchar esc, uchar sc)
+putscan(int kbinfd, uchar esc, uchar sc)
 {
 {
-	static uchar s[2] = {SCesc1, 0};
+	uchar s[2] = {SCesc1, 0};
 
 
 	if(sc == 0x41){
 	if(sc == 0x41){
-		hdebug = 1;
+		kbdebug++;
 		return;
 		return;
 	}
 	}
 	if(sc == 0x42){
 	if(sc == 0x42){
-		hdebug = 0;
+		kbdebug = 0;
 		return;
 		return;
 	}
 	}
-	if(kbinfd < 0 && !dryrun)
-		kbinfd = open("#Ι/kbin", OWRITE);
-	if(kbinfd < 0 && !dryrun)
-		sysfatal("/dev/kbin: %r");
-	if(hdebug)
+	if(kbdebug)
 		fprint(2, "sc: %x %x\n", (esc? SCesc1: 0), sc);
 		fprint(2, "sc: %x %x\n", (esc? SCesc1: 0), sc);
-	if(!dryrun){
-		s[1] = sc;
-		if(esc && sc != 0)
-			write(kbinfd, s, 2);
-		else if(sc != 0)
-			write(kbinfd, s+1, 1);
-	}
+	s[1] = sc;
+	if(esc && sc != 0)
+		write(kbinfd, s, 2);
+	else if(sc != 0)
+		write(kbinfd, s+1, 1);
 }
 }
 
 
 static void
 static void
-repeatproc(void*)
+repeatproc(void* a)
 {
 {
-	ulong l;
+	KDev *f;
+	Channel *repeatc;
+	int kbdinfd;
+	ulong l, t, i;
 	uchar esc1, sc;
 	uchar esc1, sc;
 
 
-	assert(sizeof(int) <= sizeof(void*));
-
+	/*
+	 * too many jumps here.
+	 * Rewrite instead of debug, if needed.
+	 */
+	f = a;
+	repeatc = f->repeatc;
+	kbdinfd = f->in->fd;
+	l = Awakemsg;
+Repeat:
+	if(l == Diemsg)
+		goto Abort;
+	while(l == Awakemsg)
+		l = recvul(repeatc);
+	if(l == Diemsg)
+		goto Abort;
+	esc1 = l >> 8;
+	sc = l;
+	t = 160;
 	for(;;){
 	for(;;){
-		l = recvul(repeatc);		/*  wait for work*/
-		if(l == 0xdeaddead)
-			continue;
-		esc1 = l >> 8;
-		sc = l;
-		for(;;){
-			putscan(esc1, sc);
-			sleep(80);
-			l = nbrecvul(repeatc);
-			if(l != 0){		/*  stop repeating*/
-				if(l != Awakemsg)
-					fprint(2, "kb: race: should be awake mesg\n");
-				break;
-			}
+		for(i = 0; i < t; i += 5){
+			if(l = nbrecvul(repeatc))
+				goto Repeat;
+			sleep(5);
 		}
 		}
+		putscan(kbdinfd, esc1, sc);
+		t = 30;
 	}
 	}
+Abort:
+	chanfree(repeatc);
+	threadexits("aborted");
+
 }
 }
 
 
 
 
 #define hasesc1(sc)	(((sc) > 0x47) || ((sc) == 0x38))
 #define hasesc1(sc)	(((sc) > 0x47) || ((sc) == 0x38))
 
 
 static void
 static void
-putmod(uchar mods, uchar omods, uchar mask, uchar esc, uchar sc)
+putmod(int fd, uchar mods, uchar omods, uchar mask, uchar esc, uchar sc)
 {
 {
+	/* BUG: Should be a single write */
 	if((mods&mask) && !(omods&mask))
 	if((mods&mask) && !(omods&mask))
-		putscan(esc, sc);
+		putscan(fd, esc, sc);
 	if(!(mods&mask) && (omods&mask))
 	if(!(mods&mask) && (omods&mask))
-		putscan(esc, Keyup|sc);
+		putscan(fd, esc, Keyup|sc);
 }
 }
 
 
 /*
 /*
@@ -305,42 +336,19 @@ putmod(uchar mods, uchar omods, uchar mask, uchar esc, uchar sc)
  * The aim is to allow future addition of other keycode pages
  * The aim is to allow future addition of other keycode pages
  * for other keyboards.
  * for other keyboards.
  */
  */
-static void
-putkeys(uchar buf[], uchar obuf[], int n)
+static uchar
+putkeys(KDev *f, uchar buf[], uchar obuf[], int n, uchar dk)
 {
 {
 	int i, j;
 	int i, j;
-	uchar sc;
-	static int repeating = 0, times = 0;
-	static uchar last = 0;
-
-	putmod(buf[0], obuf[0], Mctrl, 0, SCctrl);
-	putmod(buf[0], obuf[0], (1<<Mlshift), 0, SClshift);
-	putmod(buf[0], obuf[0], (1<<Mrshift), 0, SCrshift);
-	putmod(buf[0], obuf[0], Mcompose, 0, SCcompose);
-	putmod(buf[0], obuf[0], Maltgr, 1, SCcompose);
+	uchar uk;
+	int fd;
 
 
-	/*
-	 * If we get three times the same (single) key, we start
-	 * repeating. Otherwise, we stop repeating and
-	 * perform normal processing.
-	 */
-	if(buf[2] != 0 && buf[3] == 0 && buf[2] == last){
-		if(repeating)
-			return;	/* already being done */
-		times++;
-		if(times >= 2){
-			repeating = 1;
-			sc = sctab[buf[2]];
-			startrepeat(hasesc1(sc), sc);
-			return;
-		}
-	} else
-		times = 0;
-	last = buf[2];
-	if(repeating){
-		repeating = 0;
-		stoprepeat();
-	}
+	fd = f->in->fd;
+	putmod(fd, buf[0], obuf[0], Mctrl, 0, SCctrl);
+	putmod(fd, buf[0], obuf[0], (1<<Mlshift), 0, SClshift);
+	putmod(fd, buf[0], obuf[0], (1<<Mrshift), 0, SCrshift);
+	putmod(fd, buf[0], obuf[0], Mcompose, 0, SCcompose);
+	putmod(fd, buf[0], obuf[0], Maltgr, 1, SCcompose);
 
 
 	/* Report key downs */
 	/* Report key downs */
 	for(i = 2; i < n; i++){
 	for(i = 2; i < n; i++){
@@ -348,21 +356,28 @@ putkeys(uchar buf[], uchar obuf[], int n)
 			if(buf[i] == obuf[j])
 			if(buf[i] == obuf[j])
 			 	break;
 			 	break;
 		if(j == n && buf[i] != 0){
 		if(j == n && buf[i] != 0){
-			sc = sctab[buf[i]];
-			putscan(hasesc1(sc), sc);
+			dk = sctab[buf[i]];
+			putscan(fd, hasesc1(dk), dk);
+			startrepeat(f, hasesc1(dk), dk);
 		}
 		}
 	}
 	}
 
 
 	/* Report key ups */
 	/* Report key ups */
+	uk = 0;
 	for(i = 2; i < n; i++){
 	for(i = 2; i < n; i++){
 		for(j = 2; j < n; j++)
 		for(j = 2; j < n; j++)
 			if(obuf[i] == buf[j])
 			if(obuf[i] == buf[j])
 				break;
 				break;
 		if(j == n && obuf[i] != 0){
 		if(j == n && obuf[i] != 0){
-			sc = sctab[obuf[i]];
-			putscan(hasesc1(sc), sc|Keyup);
+			uk = sctab[obuf[i]];
+			putscan(fd, hasesc1(uk), uk|Keyup);
 		}
 		}
 	}
 	}
+	if(uk && (dk == 0 || dk == uk)){
+		stoprepeat(f);
+		dk = 0;
+	}
+	return dk;
 }
 }
 
 
 static int
 static int
@@ -376,262 +391,178 @@ kbdbusy(uchar* buf, int n)
 	return 1;
 	return 1;
 }
 }
 
 
-void
+static void
 kbdwork(void *a)
 kbdwork(void *a)
 {
 {
 	int c, i, kbdfd;
 	int c, i, kbdfd;
 	uchar buf[64], lbuf[64];
 	uchar buf[64], lbuf[64];
-	Dev *f = a;
+	KDev *f = a;
+	uchar dk;
 
 
-	kbdfd = f->fd;
-	if(kbdfd < 0)
-		return;
-	if(f->msz < 3 || f->msz > sizeof buf)
-		sysfatal("bug: ptrwork: bad kbd maxpkt");
-	repeatc = chancreate(sizeof(ulong), 0);
-	if(repeatc == nil)
-		sysfatal("repeat chan: %r");
-	proccreate(repeatproc, nil, 32*1024);
+	kbdfd = f->ep->dfd;
+
+	if(f->ep->maxpkt < 3 || f->ep->maxpkt > sizeof buf)
+		kbfatal(f, "weird maxpkt");
+
+	f->repeatc = chancreate(sizeof(ulong), 0);
+	if(f->repeatc == nil)
+		kbfatal(f, "chancreate failed");
+
+	proccreate(repeatproc, f, Stack);
 	memset(lbuf, 0, sizeof lbuf);
 	memset(lbuf, 0, sizeof lbuf);
-	for(;;) {
+	dk = 0;
+	for(;;){
 		memset(buf, 0, sizeof buf);
 		memset(buf, 0, sizeof buf);
-		c = robustread(kbdfd, buf, f->msz);
-		if(c == 0)
-			fprint(2, "%s: %s: eof\n", argv0, f->name);
-		else if(c < 0)
-			fprint(2, "%s: %s: read: %r\n", argv0, f->name);
-		if(c <= 0){
-			if(!dryrun)
-				close(kbinfd);
-			close(f->fd);
-			threadexits("read");
-		}
+		c = read(kbdfd, buf, f->ep->maxpkt);
+		if(c < 0)
+			fprint(2, "%s: %s: read: %r\n", argv0, f->ep->dir);
+		if(c <= 0)
+			kbfatal(f, "eof");
 		if(c < 3)
 		if(c < 3)
 			continue;
 			continue;
 		if(kbdbusy(buf + 2, c - 2))
 		if(kbdbusy(buf + 2, c - 2))
 			continue;
 			continue;
-		if(hdebug > 1){
+		if(usbdebug > 1 || kbdebug > 1){
 			fprint(2, "kbd mod %x: ", buf[0]);
 			fprint(2, "kbd mod %x: ", buf[0]);
 			for(i = 2; i < c; i++)
 			for(i = 2; i < c; i++)
 				fprint(2, "kc %x ", buf[i]);
 				fprint(2, "kc %x ", buf[i]);
 			fprint(2, "\n");
 			fprint(2, "\n");
 		}
 		}
-		putkeys(buf, lbuf, f->msz);
+		dk = putkeys(f, buf, lbuf, f->ep->maxpkt, dk);
 		memmove(lbuf, buf, c);
 		memmove(lbuf, buf, c);
 	}
 	}
 }
 }
 
 
 static int
 static int
-probeif(Dev* f, int ctlrno, int i, int kcsp, int csp, int, int)
+setbootproto(KDev* f, int eid)
 {
 {
-	int found, n, sfd;
-	char buf[256], cspstr[50], kcspstr[50];
-
-	seprint(cspstr, cspstr + sizeof cspstr, "0x%0.06x", csp);
-	seprint(buf, buf + sizeof buf, "/dev/usb%d/%d/status", ctlrno, i);
-	sfd = open(buf, OREAD);
-	if(sfd < 0)
-		return -1;
-	n = read(sfd, buf, sizeof buf - 1);
-	if(n <= 0){
-		close(sfd);
-		return -1;
-	}
-	buf[n] = 0;
-	close(sfd);
-	/*
-	 * Mouse + keyboard combos may report the interface as Kbdcsp,
-	 * because it's the endpoint the one with the right csp.
-	 */
-	sprint(cspstr, "Enabled 0x%0.06x", csp);
-	found = (strncmp(buf, cspstr, strlen(cspstr)) == 0);
-	if(!found){
-		sprint(cspstr, " 0x%0.06x", csp);
-		sprint(kcspstr, "Enabled 0x%0.06x", kcsp);
-		if(strncmp(buf, kcspstr, strlen(kcspstr)) == 0 &&
-		   strstr(buf, cspstr) != nil)
-			found = 1;
-	}
-	if(found){
-		f->ctlrno = ctlrno;
-		f->id = i;
-		f->enabled = 1;
-		if(hdebug)
-			fprint(2, "%s: csp 0x%x at /dev/usb%d/%d\n",
-				f->name, csp, f->ctlrno, f->id);
-		return 0;
-	}
-	if(hdebug)
-		fprint(2, "%s: not found %s\n", f->name, cspstr);
-	return -1;
+	int r, id;
 
 
-}
-
-static int
-probedev(Dev* f, int kcsp, int csp, int vid, int did)
-{
-	int c, i;
-
-	for(c = 0; c < 16; c++)
-		if(f->ctlrno == 0 || c == f->ctlrno)
-			for(i = 1; i < 128; i++)
-				if(f->id == 0 || i == f->id)
-				if(probeif(f, c, i, kcsp, csp, vid, did) != -1){
-					f->csp = csp;
-					return 0;
-				}
-	f->enabled = 0;
-	if(hdebug || verbose)
-		fprint(2, "%s: csp 0x%x: not found\n", f->name, csp);
-	return -1;
+	r = Rh2d|Rclass|Riface;
+	id = f->dev->usb->ep[eid]->iface->id;
+	return usbcmd(f->dev, r, Setproto, Bootproto, id, nil, 0);
 }
 }
 
 
 static void
 static void
-initdev(Dev* f)
+freekdev(void *a)
 {
 {
-	int i;
-
-	f->dev = opendev(f->ctlrno, f->id);
-	if(f->dev == nil || describedevice(f->dev) < 0) {
-		fprint(2, "init failed: %s: %r\n", f->name);
-		f->enabled = 0;
-		f->ctlrno = f->id = -1;
-		return;
-	}
-	memset(f->dev->config, 0, sizeof f->dev->config);
-	for(i = 0; i < f->dev->nconf; i++){
-		f->dev->config[i] = mallocz(sizeof *f->dev->config[i], 1);
-		loadconfig(f->dev, i);
-	}
-	if(verbose || hdebug){
-		fprint(2, "%s found: ctlrno=%d id=%d\n",
-			f->name, f->ctlrno, f->id);
-//		printdevice(f->dev);	// TODO
+	KDev *kd;
+
+	kd = a;
+	if(kd->in != nil){
+		qlock(&inlck);
+		if(--kd->in->ref == 0){
+			close(kd->in->fd);
+			kd->in->fd = -1;
+		}
+		qunlock(&inlck);
 	}
 	}
-}
-
-static int
-setbootproto(Dev* f)
-{
-	int r, id;
-	Endpt* ep;
-
-	ep = f->dev->ep[0];
-	r = RH2D | Rclass | Rinterface;
-	id = f->dev->ep[f->ep]->iface->interface;
-	return setupreq(ep, r, SET_PROTO, BOOT_PROTO, id, 0);
+	free(kd);
 }
 }
 
 
 static void
 static void
-startdev(Dev* f)
+kbstart(Dev *d, Ep *ep, Kin *in, void (*f)(void*), int accel)
 {
 {
-	int i;
-	char buf[128];
-	Endpt* ep;
-
-	f->ep = -1;
-	ep = nil;
-	for(i = 0; i < Nendpt; i++)
-		if((ep = f->dev->ep[i]) != nil &&
-		    ep->csp == f->csp && ep->type == Eintr && ep->dir == Ein){
-			f->ep = i;
-			f->msz = ep->maxpkt;
-			break;
+	KDev *kd;
+
+	qlock(&inlck);
+	if(in->fd < 0){
+		in->fd = open(in->name, OWRITE);
+		if(in->fd < 0){
+			fprint(2, "kb: %s: %r\n", in->name);
+			qunlock(&inlck);
+			return;
+		}
+	}
+	qunlock(&inlck);
+	kd = d->aux;
+	if(kd == nil){
+		kd = d->aux = emallocz(sizeof(KDev), 1);
+		d->free = freekdev;
+		kd->in = in;
+		qlock(&inlck);
+		kd->in->ref++;
+		qunlock(&inlck);
+		kd->dev = d;
+		if(setbootproto(kd, ep->id) < 0){
+			fprint(2, "kb: %s: bootproto: %r\n", d->dir);
+			return;
 		}
 		}
-	if(ep == nil){
-		fprint(2, "%s: %s: bug: no endpoint\n", argv0, f->name);
-		return;
 	}
 	}
-	sprint(buf, "ep %d 10 r %d", f->ep, f->msz);
-	if(hdebug)
-		fprint(2, "%s: %s: ep %d: ctl %s\n", argv0, f->name, f->ep, buf);
-	if(write(f->dev->ctl, buf, strlen(buf)) != strlen(buf)){
-		fprint(2, "%s: %s: startdev: %r\n", argv0, f->name);
+	kd->accel = accel;
+	kd->ep = openep(d, ep->id);
+	if(kd->ep == nil){
+		fprint(2, "kb: %s: openep %d: %r\n", d->dir, ep->id);
 		return;
 		return;
 	}
 	}
-	sprint(buf, "/dev/usb%d/%d/ep%ddata", f->ctlrno, f->id, f->ep);
-	f->fd = open(buf, OREAD);
-	if(f->fd < 0){
-		fprint(2, "%s: opening %s: %s: %r", argv0, f->name, buf);
+	if(opendevdata(kd->ep, OREAD) < 0){
+		fprint(2, "kb: %s: opendevdata: %r\n", kd->ep->dir);
+		closedev(kd->ep);
+		kd->ep = nil;
 		return;
 		return;
 	}
 	}
-	if(setbootproto(f) < 0)
-		fprint(2, "%s: %s: setbootproto: %r\n", argv0, f->name);
-	if(hdebug)
-		fprint(2, "starting %s\n", f->name);
-	proccreate(f->proc, f, 32*1024);
+
+	incref(d);
+	proccreate(f, kd, Stack);
 }
 }
 
 
-static void
+static int
 usage(void)
 usage(void)
 {
 {
-	fprint(2, "usage: %s [-dkmn] [-a n] [ctlrno usbport]\n", argv0);
-	threadexitsall("usage");
+	werrstr("usage: usb/kb [-dkmn] [-a n]");
+	return -1;
 }
 }
 
 
-void
-threadmain(int argc, char **argv)
+int
+kbmain(Dev *d, int argc, char* argv[])
 {
 {
-
-	quotefmtinstall();
-	usbfmtinit();
-
-	pf.enabled = kf.enabled = 1;
+	int i;
+	int kena;
+	int pena;
+	int accel;
+	char *as;
+	Usbdev *ud;
+	Ep *ep;
+
+	kena = pena = 1;
+	accel = 0;
 	ARGBEGIN{
 	ARGBEGIN{
 	case 'a':
 	case 'a':
-		accel = strtol(EARGF(usage()), nil, 0);
+		as = ARGF();
+		if(as == nil)
+			return usage();
+		accel = strtol(as, nil, 0);
 		break;
 		break;
 	case 'd':
 	case 'd':
-		hdebug++;
-		usbdebug++;
+		kbdebug++;
 		break;
 		break;
 	case 'k':
 	case 'k':
-		kf.enabled = 1;
-		pf.enabled = 0;
+		kena = 1;
+		pena = 0;
 		break;
 		break;
 	case 'm':
 	case 'm':
-		kf.enabled = 0;
-		pf.enabled = 1;
-		break;
-	case 'n':
-		dryrun = 1;
+		kena = 0;
+		pena = 1;
 		break;
 		break;
 	default:
 	default:
-		usage();
+		return usage();
 	}ARGEND;
 	}ARGEND;
-
-	switch(argc){
-	case 0:
-		break;
-	case 2:
-		pf.ctlrno = kf.ctlrno = atoi(argv[0]);
-		pf.id = kf.id = atoi(argv[1]);
-		break;
-	default:
-		usage();
+	if(argc != 0){
+		return usage();
+	}
+	ud = d->usb;
+	d->aux = nil;
+	dprint(2, "kb: main: dev %s ref %ld\n", d->dir, d->ref);
+	for(i = 0; i < nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			break;
+		if(kena && ep->type == Eintr && ep->dir == Ein)
+		if(ep->iface->csp == KbdCSP)
+			kbstart(d, ep, &kbdin, kbdwork, accel);
+		if(pena && ep->type == Eintr && ep->dir == Ein)
+		if(ep->iface->csp == PtrCSP)
+			kbstart(d, ep, &ptrin, ptrwork, accel);
 	}
 	}
-	threadnotify(robusthandler, 1);
-
-	kf.name = "kbd";
-	kf.proc = kbdwork;
-	pf.name = "mouse";
-	pf.proc = ptrwork;
-
-	if(kf.enabled)
-		probedev(&kf, KbdCSP, KbdCSP, 0, 0);
-	if(kf.enabled)
-		initdev(&kf);
-	if(pf.enabled)
-		probedev(&pf, KbdCSP, PtrCSP, 0, 0);
-	if(pf.enabled)
-		if(kf.enabled && pf.ctlrno == kf.ctlrno && pf.id == kf.id)
-			pf.dev = kf.dev;
-		else
-			initdev(&pf);
-	rfork(RFNOTEG);
-	if(kf.enabled)
-		startdev(&kf);
-	if(pf.enabled)
-		startdev(&pf);
-	threadexits(nil);
+	closedev(d);
+	return 0;
 }
 }

+ 64 - 0
sys/src/cmd/usb/kb/main.c

@@ -0,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "hid.h"
+
+typedef struct Parg Parg;
+
+enum
+{
+	Ndevs = 10,
+	Arglen = 80,
+	Nargs = 10,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-dkm] [-a n] [dev...]\n", argv0);
+	threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char args[Arglen];
+	char *as;
+	char *ae;
+	int accel;
+	int pena;
+	int csps[] = { KbdCSP, PtrCSP, 0 };
+
+	quotefmtinstall();
+	pena = 1;
+	ae = args+sizeof(args);
+	as = seprint(args, ae, "kb");
+	ARGBEGIN{
+	case 'a':
+		accel = strtol(EARGF(usage()), nil, 0);
+		as = seprint(as, ae, " -a %d", accel);
+		break;
+	case 'd':
+		usbdebug++;
+		/* fall */
+	case 'k':
+		as = seprint(as, ae, " -k");
+		pena = 0;
+		break;
+	case 'm':
+		as = seprint(as, ae, " -m");
+		pena = 1;
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	rfork(RFNOTEG);
+	fmtinstall('U', Ufmt);
+	threadsetgrp(threadid());
+	if(pena == 0)
+		csps[1] = 0;
+	startdevs(args, argv, argc, matchdevcsp, csps, kbmain);
+	threadexits(nil);
+}

+ 13 - 9
sys/src/cmd/usb/kb/mkfile

@@ -1,31 +1,35 @@
 </$objtype/mkfile
 </$objtype/mkfile
 
 
 TARG=kb
 TARG=kb
-
-OFILES=\
-	kb.$O\
-
-
+OFILES=main.$O
+LIBDOFILES=kb.$O
 HFILES=\
 HFILES=\
 	../lib/usb.h\
 	../lib/usb.h\
 	hid.h\
 	hid.h\
 
 
-
-LIB=../lib/usb.a$O
+LIBD=../lib/usbdev.a$O
+LIBU=../lib/usb.a$O
+LIB=\
+	$LIBD\
+	$LIBU\
 
 
 BIN=/$objtype/bin/usb
 BIN=/$objtype/bin/usb
 
 
+
 UPDATE=\
 UPDATE=\
 	mkfile\
 	mkfile\
 	$HFILES\
 	$HFILES\
 	${OFILES:%.$O=%.c}\
 	${OFILES:%.$O=%.c}\
 
 
 </sys/src/cmd/mkone
 </sys/src/cmd/mkone
-
 CFLAGS=-I../lib $CFLAGS
 CFLAGS=-I../lib $CFLAGS
 
 
-$LIB:
+$LIBU:
 	cd ../lib
 	cd ../lib
 	mk install
 	mk install
 	mk clean
 	mk clean
 
 
+$LIBD:V: $LIBDOFILES
+	ar vu $LIBD $newprereq
+	rm $newprereq
+

+ 471 - 0
sys/src/cmd/usb/lib/dev.c

@@ -0,0 +1,471 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+
+/*
+ * epN.M -> N
+ */
+static int
+nameid(char *s)
+{
+	char *r;
+	char nm[20];
+
+	r = strrchr(s, 'p');
+	if(r == nil)
+		return -1;
+	strecpy(nm, nm+sizeof(nm), r+1);
+	r = strchr(nm, '.');
+	if(r == nil)
+		return -1;
+	*r = 0;
+	return atoi(nm);
+}
+
+Dev*
+openep(Dev *d, int id)
+{
+	char *mode;	/* How many modes? */
+	Ep *ep;
+	Altc *ac;
+	Dev *epd;
+	Usbdev *ud;
+	char name[40];
+
+	if(d->cfd < 0 || d->usb == nil){
+		werrstr("device not configured");
+		return nil;
+	}
+	ud = d->usb;
+	if(id < 0 || id >= nelem(ud->ep) || ud->ep[id] == nil){
+		werrstr("bad enpoint number");
+		return nil;
+	}
+	ep = ud->ep[id];
+	mode = "rw";
+	if(ep->dir == Ein)
+		mode = "r";
+	if(ep->dir == Eout)
+		mode = "w";
+	snprint(name, sizeof(name), "/dev/usb/ep%d.%d", d->id, id);
+	if(access(name, AEXIST) == 0){
+		dprint(2, "%s: %s already exists; trying to open\n", argv0, name);
+		return opendev(name);
+	}
+	if(devctl(d, "new %d %d %s", id, ep->type, mode) < 0){
+		dprint(2, "%s: %s: new: %r\n", argv0, d->dir);
+		return nil;
+	}
+	epd = opendev(name);
+	if(epd == nil)
+		return nil;
+	epd->id = id;
+	if(devctl(epd, "maxpkt %d", ep->maxpkt) < 0)
+		fprint(2, "%s: %s: openep: maxpkt: %r\n", argv0, epd->dir);
+	else
+		dprint(2, "%s: %s: maxpkt %d\n", argv0, epd->dir, ep->maxpkt);
+	epd->maxpkt = ep->maxpkt;
+	ac = ep->iface->altc[0];
+	if(ep->ntds > 1 && devctl(epd, "ntds %d", ep->ntds) < 0)
+		fprint(2, "%s: %s: openep: ntds: %r\n", argv0, epd->dir);
+	else
+		dprint(2, "%s: %s: ntds %d\n", argv0, epd->dir, ep->ntds);
+
+	/*
+	 * For iso endpoints and high speed interrupt endpoints the pollival is
+	 * actually 2ⁿ and not n.
+	 * The kernel usb driver must take that into account.
+	 * It's simpler this way.
+	 */
+
+	if(ac != nil && (ep->type == Eintr || ep->type == Eiso) && ac->interval != 0)
+		if(devctl(epd, "pollival %d", ac->interval) < 0)
+			fprint(2, "%s: %s: openep: pollival: %r\n", argv0, epd->dir);
+	return epd;
+}
+
+Dev*
+opendev(char *fn)
+{
+	Dev *d;
+	int l;
+
+	d = emallocz(sizeof(Dev), 1);
+	incref(d);
+
+	l = strlen(fn);
+	d->dfd = -1;
+	d->dir = emallocz(l + strlen("/data") + 1, 0);
+	strcpy(d->dir, fn);
+	strcpy(d->dir+l, "/ctl");
+	d->cfd = open(d->dir, ORDWR|OCEXEC);
+	d->dir[l] = 0;
+	d->id = nameid(fn);
+	if(d->cfd < 0){
+		werrstr("can't open endpoint %s: %r", d->dir);
+		free(d->dir);
+		free(d);
+		return nil;
+	}
+	dprint(2, "%s: opendev %#p %s\n", argv0, d, fn);
+	return d;
+}
+
+int
+opendevdata(Dev *d, int mode)
+{
+	int l;
+
+	l = strlen(d->dir);
+	strcpy(d->dir+l, "/data");
+	d->dfd = open(d->dir, mode|OCEXEC);
+	d->dir[l] = 0;
+	return d->dfd;
+}
+
+int
+loaddevconf(Dev *d, int n)
+{
+	uchar buf[1024];	/* enough room for extra descriptors */
+	int nr;
+	int type;
+
+	if(n >= nelem(d->usb->conf)){
+		werrstr("loaddevconf: bug: out of configurations in device");
+		fprint(2, "%s: %r\n", argv0);
+		return -1;
+	}
+	type = Rd2h|Rstd|Rdev;
+	nr = usbcmd(d, type, Rgetdesc, Dconf<<8|n, 0, buf, 1024);
+	if(nr < Dconflen)
+		return -1;
+	if(d->usb->conf[n] == nil)
+		d->usb->conf[n] = emallocz(sizeof(Conf), 1);
+	return parseconf(d->usb, d->usb->conf[n], buf, nr);
+}
+
+Ep*
+mkep(Usbdev *d, int id)
+{
+	Ep *ep;
+
+	d->ep[id] = ep = emallocz(sizeof(Ep), 1);
+	ep->id = id;
+	return ep;
+}
+
+static char*
+mkstr(uchar *b, int n)
+{
+	Rune r;
+	char *us;
+	char *s;
+	char *e;
+
+	if(n <= 2 || (n & 1) != 0)
+		return strdup("none");
+	n = (n - 2)/2;
+	b += 2;
+	us = s = emallocz(n*UTFmax+1, 0);
+	e = s + n*UTFmax+1;
+	for(; --n >= 0; b += 2){
+		r = GET2(b);
+		s = seprint(s, e, "%C", r);
+	}
+	return us;
+}
+
+char*
+loaddevstr(Dev *d, int sid)
+{
+	uchar buf[128];
+	int type;
+	int nr;
+
+	if(sid == 0)
+		return estrdup("none");
+	type = Rd2h|Rstd|Rdev;
+	nr=usbcmd(d, type, Rgetdesc, Dstr<<8|sid, 0, buf, sizeof(buf));
+	return mkstr(buf, nr);
+}
+
+int
+loaddevdesc(Dev *d)
+{
+	uchar buf[Ddevlen+255];
+	int nr;
+	int type;
+	Ep *ep0;
+
+	type = Rd2h|Rstd|Rdev;
+	nr = sizeof(buf);
+	memset(buf, 0, Ddevlen);
+	if((nr=usbcmd(d, type, Rgetdesc, Ddev<<8|0, 0, buf, nr)) < 0)
+		return -1;
+	/*
+	 * Several hubs are returning descriptors of 17 bytes, not 18.
+	 * We accept them and leave number of configurations as zero.
+	 * (a get configuration descriptor also fails for them!)
+	 */
+	if(nr < Ddevlen){
+		print("%s: %s: warning: device with short descriptor\n",
+			argv0, d->dir);
+		if(nr < Ddevlen-1){
+			werrstr("short device descriptor (%d bytes)", nr);
+			return -1;
+		}
+	}
+	d->usb = emallocz(sizeof(Usbdev), 1);
+	ep0 = mkep(d->usb, 0);
+	ep0->dir = Eboth;
+	ep0->type = Econtrol;
+	ep0->maxpkt = d->maxpkt = 8;		/* a default */
+	nr = parsedev(d, buf, nr);
+	if(nr >= 0){
+		d->usb->vendor = loaddevstr(d, d->usb->vsid);
+		if(strcmp(d->usb->vendor, "none") != 0){
+			d->usb->product = loaddevstr(d, d->usb->psid);
+			d->usb->serial = loaddevstr(d, d->usb->ssid);
+		}
+	}
+	return nr;
+}
+
+int
+configdev(Dev *d)
+{
+	int i;
+
+	if(d->dfd < 0)
+		opendevdata(d, ORDWR);
+	if(loaddevdesc(d) < 0)
+		return -1;
+	for(i = 0; i < d->usb->nconf; i++)
+		if(loaddevconf(d, i) < 0)
+			return -1;
+	return 0;
+}
+
+static void
+closeconf(Conf *c)
+{
+	int i;
+	int a;
+
+	if(c == nil)
+		return;
+	for(i = 0; i < nelem(c->iface); i++)
+		if(c->iface[i] != nil){
+			for(a = 0; a < nelem(c->iface[i]->altc); a++)
+				free(c->iface[i]->altc[a]);
+			free(c->iface[i]);
+		}
+	free(c);
+}
+
+void
+closedev(Dev *d)
+{
+	int i;
+	Usbdev *ud;
+
+	if(d==nil || decref(d) != 0)
+		return;
+	dprint(2, "%s: closedev %#p %s\n", argv0, d, d->dir);
+	if(d->free != nil)
+		d->free(d->aux);
+	if(d->cfd >= 0)
+		close(d->cfd);
+	if(d->dfd >= 0)
+		close(d->dfd);
+	d->cfd = d->dfd = -1;
+	free(d->dir);
+	d->dir = nil;
+	ud = d->usb;
+	d->usb = nil;
+	if(ud != nil){
+		free(ud->vendor);
+		free(ud->product);
+		free(ud->serial);
+		for(i = 0; i < nelem(ud->ep); i++)
+			free(ud->ep[i]);
+		for(i = 0; i < nelem(ud->ddesc); i++)
+			free(ud->ddesc[i]);
+				
+		for(i = 0; i < nelem(ud->conf); i++)
+			closeconf(ud->conf[i]);
+		free(ud);
+	}
+	free(d);
+}
+
+static char*
+reqstr(int type, int req)
+{
+	char *s;
+	static char* ds[] = { "dev", "if", "ep", "oth" };
+	static char buf[40];
+
+	if(type&Rd2h)
+		s = seprint(buf, buf+sizeof(buf), "d2h");
+	else
+		s = seprint(buf, buf+sizeof(buf), "h2d");
+	if(type&Rclass)
+		s = seprint(s, buf+sizeof(buf), "|cls");
+	else if(type&Rvendor)
+		s = seprint(s, buf+sizeof(buf), "|vnd");
+	else
+		s = seprint(s, buf+sizeof(buf), "|std");
+	s = seprint(s, buf+sizeof(buf), "|%s", ds[type&3]);
+
+	switch(req){
+	case Rgetstatus: s = seprint(s, buf+sizeof(buf), " getsts"); break;
+	case Rclearfeature: s = seprint(s, buf+sizeof(buf), " clrfeat"); break;
+	case Rsetfeature: s = seprint(s, buf+sizeof(buf), " setfeat"); break;
+	case Rsetaddress: s = seprint(s, buf+sizeof(buf), " setaddr"); break;
+	case Rgetdesc: s = seprint(s, buf+sizeof(buf), " getdesc"); break;
+	case Rsetdesc: s = seprint(s, buf+sizeof(buf), " setdesc"); break;
+	case Rgetconf: s = seprint(s, buf+sizeof(buf), " getcnf"); break;
+	case Rsetconf: s = seprint(s, buf+sizeof(buf), " setcnf"); break;
+	case Rgetiface: s = seprint(s, buf+sizeof(buf), " getif"); break;
+	case Rsetiface: s = seprint(s, buf+sizeof(buf), " setif"); break;
+	}
+	USED(s);
+	return buf;
+}
+
+static int
+cmdreq(Dev *d, int type, int req, int value, int index, uchar *data, int count)
+{
+	int ndata, n;
+	uchar *wp;
+	uchar buf[8];
+	char *hd, *rs;
+
+	assert(d != nil);
+	if(data == nil){
+		wp = buf;
+		ndata = 0;
+	}else{
+		ndata = count;
+		wp = emallocz(8+ndata, 0);
+	}
+	wp[0] = type;
+	wp[1] = req;
+	PUT2(wp+2, value);
+	PUT2(wp+4, index);
+	PUT2(wp+6, count);
+	if(data != nil)
+		memmove(wp+8, data, ndata);
+	if(usbdebug>2){
+		hd = hexstr(wp, ndata+8);
+		rs = reqstr(type, req);
+		fprint(2, "%s: %s val %d|%d idx %d cnt %d out[%d] %s\n",
+			d->dir, rs, value>>8, value&0xFF,
+			index, count, ndata+8, hd);
+		free(hd);
+	}
+	n = write(d->dfd, wp, 8+ndata);
+	if(wp != buf)
+		free(wp);
+	if(n < 0)
+		return -1;
+	if(n != 8+ndata){
+		dprint(2, "%s: cmd: short write: %d\n", argv0, n);
+		return -1;
+	}
+	return n;
+}
+
+static int
+cmdrep(Dev *d, void *buf, int nb)
+{
+	char *hd;
+
+	nb = read(d->dfd, buf, nb);
+	if(nb >0 && usbdebug > 2){
+		hd = hexstr(buf, nb);
+		fprint(2, "%s: in[%d] %s\n", d->dir, nb, hd);
+		free(hd);
+	}
+	return nb;
+}
+
+int
+usbcmd(Dev *d, int type, int req, int value, int index, uchar *data, int count)
+{
+	int r;
+	int i;
+	int nerr;
+	char err[64];
+
+	/*
+	 * Some devices do not respond to commands some times.
+	 * Others even report errors but later work just fine. Retry.
+	 */
+	r = -1;
+	*err = 0;
+	for(i = nerr = 0; i < Uctries; i++){
+		if(type&Rd2h)
+			r = cmdreq(d, type, req, value, index, nil, count);
+		else
+			r = cmdreq(d, type, req, value, index, data, count);
+		if(r > 0){
+			if((type&Rd2h) == 0)
+				break;
+			r = cmdrep(d, data, count);
+			if(r > 0)
+				break;
+			if(r == 0)
+				werrstr("no data from device");
+		}
+		nerr++;
+		if(*err == 0)
+			rerrstr(err, sizeof(err));
+		sleep(Ucdelay);
+	}
+	if(r > 0 && i >= 2){
+		/* let the user know the device is not in good shape */
+		fprint(2, "%s: usbcmd: %s: required %d attempts (%s)\n",
+			argv0, d->dir, i, err);
+	}
+	return r;
+}
+
+int
+unstall(Dev *dev, Dev *ep, int dir)
+{
+	int r;
+
+	if(dir == Ein)
+		dir = 0x80;
+	else
+		dir = 0;
+	r = Rh2d|Rstd|Rep;
+	if(usbcmd(dev, r, Rclearfeature, Fhalt, ep->id|dir, nil, 0)<0){
+		werrstr("unstall: %s: %r", ep->dir);
+		return -1;
+	}
+	if(devctl(ep, "clrhalt") < 0){
+		werrstr("clrhalt: %s: %r", ep->dir);
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ * To be sure it uses a single write.
+ */
+int
+devctl(Dev *dev, char *fmt, ...)
+{
+	char buf[128];
+	va_list arg;
+	char *e;
+
+	va_start(arg, fmt);
+	e = vseprint(buf, buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	return write(dev->cfd, buf, e-buf);
+}

+ 0 - 185
sys/src/cmd/usb/lib/device.c

@@ -1,185 +0,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include "usb.h"
-
-static int
-readnum(int fd)
-{
-	char buf[20];
-	int n;
-
-	for(;;){
-		n = read(fd, buf, sizeof buf);
-		if (n < 0){
-			rerrstr(buf, sizeof buf);
-			if (strcmp(buf, "interrupted") != 0)
-				break;
-		} else
-			break;
-	}
-	buf[sizeof(buf)-1] = 0;
-	return n <= 0? -1: strtol(buf, nil, 0);
-}
-
-Device*
-opendev(int ctlrno, int id)
-{
-	int isnew;
-	Device *d;
-	char name[100], *p;
-
-	d = emallocz(sizeof(Device), 1);
-	incref(d);
-
-	isnew = 0;
-	if(id == -1) {
-		sprint(name, "/dev/usb%d/new", ctlrno);
-		if((d->ctl = open(name, ORDWR)) < 0){
-Error0:
-			werrstr("open %s: %r", name);
-			if(d->ctl >= 0)
-				close(d->ctl);
-			free(d);
-			/* return nil; */
-			sysfatal("%r");
-		}
-		id = readnum(d->ctl);
-		isnew = 1;
-	}
-	sprint(name, "/dev/usb%d/%d/", ctlrno, id);
-	p = name+strlen(name);
-
-	if(!isnew) {
-		strcpy(p, "ctl");
-		if((d->ctl = open(name, ORDWR)) < 0)
-			goto Error0;
-	}
-
-	strcpy(p, "setup");
-	if((d->setup = open(name, ORDWR)) < 0){
-Error1:
-		if(d->setup >= 0)
-			close(d->setup);
-		goto Error0;
-	}
-
-	strcpy(p, "status");
-	if((d->status = open(name, OREAD)) < 0)
-		goto Error1;
-
-	d->ctlrno = ctlrno;
-	d->id = id;
-	d->ep[0] = newendpt(d, 0, 0);
-	return d;
-}
-
-void
-closedev(Device *d)
-{
-	int i;
-
-	if(d==nil)
-		return;
-	if(decref(d) != 0)
-		return;
-	close(d->ctl);
-	close(d->setup);
-	close(d->status);
-
-	for(i=0; i<nelem(d->ep); i++)
-		free(d->ep[i]);
-	free(d);
-}
-
-void
-setdevclass(Device *d, int n)
-{
-	Endpt *e;
-
-	if((e = d->ep[n]) == nil)
-		return;
-	if (verbose) fprint(2, "class %d %d %#6.6lux %d %#4.4ux %#4.4ux\n",
-		d->nif, n, e->csp, e->maxpkt, d->vid, d->did);
-	fprint(d->ctl, "class %d %d %#6.6lux %d %#4.4ux %#4.4ux",
-		d->nif, n, e->csp, e->maxpkt, d->vid, d->did);
-}
-
-int
-describedevice(Device *d)
-{
-	DDevice *dd;
-	byte buf[24];	/* length field is one byte */
-	int nr;
-
-	werrstr("");
-	if(debugdebug)
-		fprint(2, "describedevice\n");
-	if(setupreq(d->ep[0], RD2H|Rstandard|Rdevice, GET_DESCRIPTOR,
-	     DEVICE<<8|0, 0, sizeof buf) < 0){
-		fprint(2, "%s: describedevice: error writing usb device "
-			"request: get device descriptor: %r\n", argv0);
-		return -1;
-	}
-	if((nr = setupreply(d->ep[0], buf, sizeof buf)) < 8) {
-		fprint(2, "%s: describedevice: error reading usb device "
-			"descriptor, got %d of %d: %r\n",
-			argv0, nr, DDEVLEN);
-		return -1;
-	}
-	/* extract gubbins */
-	pdesc(d, -1, -1, buf, nr);
-	dd = (DDevice*)buf;
-	d->csp = CSP(dd->bDeviceClass, dd->bDeviceSubClass, dd->bDeviceProtocol);
-	d->ep[0]->maxpkt = dd->bMaxPacketSize0;
-	if (dd->bDeviceClass == 9)
-		d->class = Hubclass;
-	else
-		d->class = Otherclass;
-	if(nr >= DDEVLEN){
-		d->nconf = dd->bNumConfigurations;
-		d->vid = GET2(dd->idVendor);
-		d->did = GET2(dd->idProduct);
-	}else
-		fprint(2, "%s: describedevice: short usb device descriptor\n",
-			argv0);
-	return 0;
-}
-
-int
-loadconfig(Device *d, int n)
-{
-	byte buf[1023];
-	int nr = -1, len;
-
-	if(debugdebug)
-		fprint(2, "loadconfig\n");
-	if (setupreq(d->ep[0], RD2H|Rstandard|Rdevice, GET_DESCRIPTOR,
-	     CONFIGURATION<<8|n, 0, sizeof buf) < 0 ||
-	    (nr = setupreply(d->ep[0], buf, sizeof buf)) < 1) {
-		fprint(2, "%s: error reading usb configuration descriptor: "
-			"read %d bytes: %r\n", argv0, nr);
-		return -1;
-	}
-	if (buf[1] == CONFIGURATION) {
-		len = GET2(((DConfig*)buf)->wTotalLength);
-		if (len < nr)
-			nr = len;
-	}
-	/* extract gubbins */
-	pdesc(d, n, -1, buf, nr);
-	return 0;
-}
-
-Endpt *
-newendpt(Device *d, int id, ulong csp)
-{
-	Endpt *e;
-
-	e = mallocz(sizeof(*e), 1);
-	e->id = id;
-	e->dev = d;
-	e->csp = csp;
-	e->maxpkt = 32;
-	return e;
-}

+ 167 - 0
sys/src/cmd/usb/lib/devs.c

@@ -0,0 +1,167 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+
+typedef struct Parg Parg;
+
+enum
+{
+	Ndevs = 10,
+	Arglen = 80,
+	Nargs = 10,
+	Stack = 16 * 1024,
+};
+
+struct Parg
+{
+	char*	args;
+	Dev*	dev;
+	int 	(*f)(Dev*,int,char**);
+	Channel*	rc;
+};
+
+static void
+workproc(void *a)
+{
+	Parg *pa;
+	char args[Arglen];
+	char *argv[Nargs];
+	int argc;
+	Channel *rc;
+	Dev *d;
+	int (*f)(Dev*,int,char**);
+
+	pa = a;
+	strecpy(args, args+sizeof(args), pa->args);	/* don't leak */
+	d = pa->dev;
+	f = pa->f;
+	rc = pa->rc;
+	free(pa->args);
+	free(pa);
+	argc = tokenize(args, argv, nelem(argv)-1);
+	argv[argc] = nil;
+	if(f(d, argc, argv) < 0){
+		closedev(d);
+		fprint(2, "%s: devmain: %r\n", argv0);
+		sendul(rc, -1);
+		threadexits("devmain: %r");
+	}
+	sendul(rc, 0);
+	threadexits(nil);
+	
+}
+
+int
+matchdevcsp(char *info, void *a)
+{
+	char sbuf[40];
+	int *csps;
+
+	csps = a;
+	for(; *csps != 0; csps++){
+		snprint(sbuf, sizeof(sbuf), "csp %#08ux", *csps);
+		if(strstr(info, sbuf) != nil)
+			return 0;
+	}
+	return -1;
+}
+
+int
+finddevs(int (*matchf)(char*,void*), void *farg, char** dirs, int ndirs)
+{
+	int fd;
+	char fbuf[40];
+	char dbuf[512];
+	Dir *d;
+	int nd, nr;
+	int n;
+	int i;
+	char *nm;
+
+	fd = open("/dev/usb", OREAD);
+	if(fd < 0)
+		sysfatal("/dev/usb: %r");
+	nd = dirreadall(fd, &d);
+	close(fd);
+	if(nd < 2)
+		sysfatal("/dev/usb: no devs");
+	for(i = n = 0; i < nd && n < ndirs; i++){
+		nm = d[i].name;
+		if(strcmp(nm, "ctl") == 0 || strstr(nm, ".0") == nil)
+			continue;
+		snprint(fbuf, sizeof(fbuf), "/dev/usb/%s/ctl", nm);
+		fd = open(fbuf, OREAD);
+		if(fd < 0)
+			continue;	/* may be gone */
+		nr = read(fd, dbuf, sizeof(dbuf)-1);
+		close(fd);
+		if(nr < 0)
+			continue;
+		dbuf[nr] = 0;
+		if(strstr(dbuf, "enabled ") != nil && strstr(dbuf, " busy") == nil)
+			if(matchf(dbuf, farg) == 0)
+				dirs[n++] = smprint("/dev/usb/%s", nm);
+	}
+	free(d);
+	if(usbdebug > 1)
+		for(nd = 0; nd < n; nd++)
+			fprint(2, "finddevs: %s\n", dirs[nd]);
+	return n;
+}
+
+void
+startdevs(char *args, char *argv[], int argc, int (*mf)(char*,void*), void*ma, int (*df)(Dev*,int,char**))
+{
+	char *dirs[Ndevs];
+	char **dp;
+	Parg *parg;
+	int ndirs;
+	int ndevs;
+	Dev *dev;
+	int i;
+	Channel *rc;
+
+	if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
+		sysfatal("#u: %r");
+
+	if(argc > 0){
+		ndirs = argc;
+		dp = argv;
+	}else{
+		dp = dirs;
+		ndirs = finddevs(mf, ma, dp, Ndevs);
+		if(ndirs == nelem(dirs))
+			fprint(2, "%s: too many devices\n", argv0);
+	}
+	ndevs = 0;
+	rc = chancreate(sizeof(ulong), 0);
+	if(rc == nil)
+		sysfatal("no memory");
+	for(i = 0; i < ndirs; i++){
+		fprint(2, "%s: startdevs: opening #%d %s\n", argv0, i, dp[i]);
+		dev = opendev(dp[i]);
+		if(dev != nil){
+			if(configdev(dev) < 0){
+				fprint(2, "%s: %s: config: %r\n", argv0, dp[i]);
+				closedev(dev);
+			}else{
+				dprint(2, "%s: %U", argv0, dev);
+				parg = emallocz(sizeof(Parg), 0);
+				parg->args = estrdup(args);
+				parg->dev = dev;
+				parg->rc = rc;
+				parg->f = df;
+				proccreate(workproc, parg, Stack);
+				if(recvul(rc) == 0)
+					ndevs++;
+			}
+		}else
+			fprint(2, "%s: %s: %r\n", argv0, dp[i]);
+		if(dp != argv)
+			free(dirs[i]);
+	}
+	chanfree(rc);
+	if(ndevs == 0)
+		sysfatal("no device found");
+}

+ 136 - 564
sys/src/cmd/usb/lib/dump.c

@@ -4,603 +4,175 @@
 #include <bio.h>
 #include <bio.h>
 #include "usb.h"
 #include "usb.h"
 
 
-int verbose;
+int usbdebug;
 
 
-typedef struct Flags Flags;
-typedef struct Classes Classes;
-
-struct Flags {
-	int	bit;
-	char*	name0;
-	char*	name1;
-};
-
-struct Classes {
-	char*	name;
-	struct {
-		char*	name;
-		char*	proto[4];
-	} subclass[4];
+static char *edir[] = {"in", "out", "inout"};
+static char *etype[] = {"ctl", "iso", "bulk", "intr"};
+static char* cnames[] =
+{
+	"none", "audio", "comms", "hid", "",
+	"", "", "printer", "storage", "hub", "data"
 };
 };
-
-static Classes	classname[] = {
-	[CL_AUDIO]	{"audio",	{[1]{"control"}, [2]{"stream"}, [3]{"midi"}}},
-	[CL_COMMS]	{"comms",	{[1] {"abstract", {[1]"AT"}}}},
-	[CL_HID]	{"hid",		{[1] {"boot", {[1]"kbd", [2]"mouse"}}}},
-	[CL_PRINTER]	{"printer",	{[1]"printer", {[1]"uni", [2]"bi"}}},
-	[CL_HUB]	{"hub",		{[1]{"hub"}}},
-	[CL_DATA]	{"data"},
+static char* devstates[] =
+{
+	"detached", "attached", "enabled", "assigned", "configured"
 };
 };
 
 
-static	void	pflag(Flags*, uint);
-
-char *
-sclass(char *p, char *e, ulong csp)
+char*
+classname(int c)
 {
 {
-	Classes *cs;
-	int c, s, pr;
+	static char buf[30];
 
 
-	c = Class(csp);
-	s = Subclass(csp);
-	pr = Proto(csp);
-	if(c < 0 || c >= nelem(classname) || (cs = &classname[c])->name == nil)
-		return seprint(p, e, "%d.%d.%d", c, s, pr);
-	p = seprint(p, e, "%s.", cs->name);
-	if(s < 0 || s >= nelem(cs->subclass) || cs->subclass[s].name == nil)
-		p = seprint(p, e, "%d.%d", s, pr);
+	if(c >= 0 && c < nelem(cnames))
+		return cnames[c];
 	else{
 	else{
-		p = seprint(p, e, "%s.", cs->subclass[s].name);
-		if(pr < 0 || pr >= nelem(cs->subclass[s].proto) || cs->subclass[s].proto[pr] == nil)
-			p = seprint(p, e, "%d", pr);
-		else
-			p = seprint(p, e, "%s", cs->subclass[s].proto[pr]);
+		seprint(buf, buf+30, "%d", c);
+		return buf;
 	}
 	}
-	return p;
 }
 }
 
 
-void
-pdevice(Device *, int, ulong, void *b, int n)
+char*
+hexstr(void *a, int n)
 {
 {
-	DDevice *d;
-	char scbuf[64];
-
-	if(n < DDEVLEN)
-		return;
-	d = b;
-	if(debug & Dbginfo) {
-		fprint(2, "usb (bcd)%c%c%c%c",
-				'0'+((d->bcdUSB[1]>>4)&0xf), '0'+(d->bcdUSB[1]&0xf),
-				'0'+((d->bcdUSB[0]>>4)&0xf), '0'+(d->bcdUSB[0]&0xf));
-		sclass(scbuf, scbuf + sizeof scbuf,
-			CSP(d->bDeviceClass, d->bDeviceSubClass, d->bDeviceProtocol)),
-		fprint(2, " class %d subclass %d proto %d [%s] max0 %d",
-			d->bDeviceClass, d->bDeviceSubClass, d->bDeviceProtocol,
-			scbuf,
-			d->bMaxPacketSize0);
-		fprint(2, " vendor %#x product %#x device (bcd)%c%c%c%c",
-			GET2(d->idVendor), GET2(d->idProduct),
-			'0'+((d->bcdDevice[1]>>4)&0xf), '0'+(d->bcdDevice[1]&0xf),
-			'0'+((d->bcdDevice[0]>>4)&0xf), '0'+(d->bcdDevice[0]&0xf));
-		fprint(2, " man %d prod %d serial %d nconfig %d",
-			d->iManufacturer, d->iProduct, d->iSerialNumber,
-			d->bNumConfigurations);
-	}
+	int i;
+	uchar *b;
+	char *dbuff;
+	char *s;
+	char *e;
+
+	b = a;
+	dbuff = s = emallocz(1024, 0);
+	*s = 0;
+	e = s+1024;
+	for(i=0; i < n; i++)
+		s = seprint(s, e, " %.2ux", b[i]);
+	if(s == e)
+		fprint(2, "%s: usb/lib: hexdump: bug: small buffer\n", argv0);
+	return dbuff;
 }
 }
 
 
-void
-phid(Device *, int, ulong, void *b, int n)
+static char*
+seprintiface(char *s, char *e, Iface *i)
 {
 {
-	DHid *d;
-
-	if(n < DHIDLEN){
-		fprint(2, "%s: hid too short\n", argv0);
-		return;
-	}
-	d = b;
-	if(debug & Dbginfo)
-		fprint(2, "HID (bcd)%c%c%c%c country %d nhidclass %d classdtype %#x dlen %d\n",
-			'0'+((d->bcdHID[1]>>4)&0xf), '0'+(d->bcdHID[1]&0xf),
-			'0'+((d->bcdHID[0]>>4)&0xf), '0'+(d->bcdHID[0]&0xf),
-			d->bCountryCode, d->bNumDescriptors,
-			d->bClassDescriptorType, GET2(d->wItemLength));
-}
-
-static	Flags	ioflags[] = {
-	{0, "Data", "Constant"},
-	{1, "Array", "Variable"},
-	{2, "Absolute", "Relative"},
-	{3, "NoWrap", "Wrap"},
-	{4, "Linear", "NonLinear"},
-	{5, "PreferredState", "No Preferred State"},
-	{6, "No Null position", "Null state"},
-	{7, "Non Volatile", "Volatile"},
-	{8, "Bit Field", "Buffered Bytes"},
-	{-1, nil, nil},
-};
-
-static void
-pflag(Flags *tab, uint v)
-{
-	char buf[200], *s;
-	int n;
-
-	n = 0;
-	buf[0] = 0;
-	for(; tab->name0 != nil; tab++){
-		if(v & (1<<tab->bit))
-			s = tab->name1;
+	int	j;
+	Altc	*a;
+	Ep	*ep;
+	char	*eds, *ets;
+
+	s = seprint(s, e, "\t\tiface csp %s.%uld.%uld\n",
+		classname(Class(i->csp)), Subclass(i->csp), Proto(i->csp));
+	for(j = 0; j < Naltc; j++){
+		a=i->altc[j];
+		if(a == nil)
+			break;
+		s = seprint(s, e, "\t\t  alt %d attr %d ival %d",
+			j, a->attrib, a->interval);
+		if(a->aux != nil)
+			s = seprint(s, e, " devspec %p\n", a->aux);
 		else
 		else
-			s = tab->name0;
-		if(s != nil && *s)
-			n += snprint(buf+n, sizeof(buf)-n, ", %s", s);
+			s = seprint(s, e, "\n");
 	}
 	}
-	if((debug & Dbginfo) && buf[0])
-		fprint(2, "[%s]", buf+2);
+	for(j = 0; j < Nep; j++){
+		ep = i->ep[j];
+		if(ep == nil)
+			break;
+		eds = ets = "";
+		if(ep->dir <= nelem(edir))
+			eds = edir[ep->dir];
+		if(ep->type <= nelem(etype))
+			ets = etype[ep->type];
+		s = seprint(s, e, "\t\t  ep id %d addr %d dir %s type %s"
+			" itype %d maxpkt %d ntds %d\n",
+			ep->id, ep->addr, eds, ets, ep->isotype,
+			ep->maxpkt, ep->ntds);
+	}
+	return s;
 }
 }
 
 
-void
-preport(Device *, int, ulong, byte *b, int n)
+static char*
+seprintconf(char *s, char *e, Usbdev *d, int ci)
 {
 {
-	byte *s, *es;
-	int tag, nb, i, indent;
-	int v;
-	Flags *tab;
-
-	s = b+2;
-	es = b+n;
-	indent = 0;
-	while(s < es){
-		for(i=0; i<indent; i++)
-			fprint(2, " ");
-		tag = *s++;
-		if(tag == Tlong){
-			fprint(2, "long report tag");
-			return;
-		}
-		if((nb = tag&3) == 3)
-			nb = 4;
-		v = 0;
-		for(i=0; i<nb; i++)
-			v |= s[i] << (i*8);
-		switch(tag & Tmtype){
-		case Treserved:
-			if(tag == Tlong){
-				fprint(2, "long report tag");
-				return;
-			}
-			fprint(2, "illegal tag");
-			return;
-		case Tmain:
-			tab = nil;
-			if (debug & Dbginfo) {
-				switch(tag & Tmitem){
-				case Tinput:
-					fprint(2, "Input");
-					tab = ioflags;
-					break;
-				case Toutput:
-					fprint(2, "Output");
-					tab = ioflags;
-					break;
-				case Tfeature:
-					fprint(2, "Feature");
-					tab = ioflags;
-					break;
-				case Tcoll:
-					fprint(2, "Collection");
-					indent++;
-					break;
-				case Tecoll:
-					fprint(2, "End Collection");
-					indent--;
-					break;
-				default:
-					fprint(2, "unexpected item %.2x", tag);
-				}
-				fprint(2, "=%#ux", v);
-				if(tab != nil)
-					pflag(tab, v);
-			}
-			break;
-		case Tglobal:
-			if (debug & Dbginfo) {
-				fprint(2, "Global %#ux: ", v);
-				switch(tag & Tmitem){
-				case Tusagepage:
-					fprint(2, "Usage Page %#ux", v);
-					break;
-				case Tlmin:
-					fprint(2, "Logical Min %d", v);
-					break;
-				case Tlmax:
-					fprint(2, "Logical Max %d", v);
-					break;
-				case Tpmin:
-					fprint(2, "Physical Min %d", v);
-					break;
-				case Tpmax:
-					fprint(2, "Physical Max %d", v);
-					break;
-				case Tunitexp:
-					fprint(2, "Unit Exponent %d", v);
-					break;
-				case Tunit:
-					fprint(2, "Unit %d", v);
-					break;
-				case Trepsize:
-					fprint(2, "Report size %d", v);
-					break;
-				case TrepID:
-					fprint(2, "Report ID %#x", v);
-					break;
-				case Trepcount:
-					fprint(2, "Report Count %d", v);
-					break;
-				case Tpush:
-					fprint(2, "Push %d", v);
-					break;
-				case Tpop:
-					fprint(2, "Pop %d", v);
-					break;
-				default:
-					fprint(2, "Unknown %#ux", v);
-					break;
-				}
-			}
+	int i;
+	Conf *c;
+	char *hd;
+
+	c = d->conf[ci];
+	s = seprint(s, e, "\tconf: cval %d attrib %x %d mA\n",
+		c->cval, c->attrib, c->milliamps);
+	for(i = 0; i < Niface; i++)
+		if(c->iface[i] == nil)
 			break;
 			break;
-		case Tlocal:
-			if (debug & Dbginfo) {
-				fprint(2, "Local %#ux: ", v);
-				switch(tag & Tmitem){
-				case Tusage:
-					fprint(2, "Usage %d", v);
-					break;
-				case Tumin:
-					fprint(2, "Usage min %d", v);
-					break;
-				case Tumax:
-					fprint(2, "Usage max %d", v);
-					break;
-				case Tdindex:
-					fprint(2, "Designator index %d", v);
-					break;
-				case Tdmin:
-					fprint(2, "Designator min %d", v);
-					break;
-				case Tdmax:
-					fprint(2, "Designator max %d", v);
-					break;
-				case Tsindex:
-					fprint(2, "String index %d", v);
-					break;
-				case Tsmin:
-					fprint(2, "String min %d", v);
-					break;
-				case Tsmax:
-					fprint(2, "String max %d", v);
-					break;
-				case Tsetdelim:
-					fprint(2, "Set delim %#ux", v);
-					break;
-				default:
-					fprint(2, "Unknown %#ux", v);
-					break;
-				}
-			}
+		else
+			s = seprintiface(s, e, c->iface[i]);
+	for(i = 0; i < Nddesc; i++)
+		if(d->ddesc[i] == nil)
 			break;
 			break;
+		else if(d->ddesc[i]->conf == c){
+			hd = hexstr((uchar*)&d->ddesc[i]->data,
+				d->ddesc[i]->data.bLength);
+			s = seprint(s, e, "\t\tdev desc %x[%d]: %s\n",
+				d->ddesc[i]->data.bDescriptorType,
+				d->ddesc[i]->data.bLength, hd);
+			free(hd);
 		}
 		}
-		fprint(2, "\n");
-		s += nb;
-	}
-}
-
-void
-phub(Device *, int, ulong, void *b, int n)
-{
-	DHub *d;
-
-	if(n < DHUBLEN)
-		return;
-	d = b;
-	if (debug & Dbginfo)
-		fprint(2, "nport %d charac %#.4x pwr %dms current %dmA remov %#.2x",
-			d->bNbrPorts, GET2(d->wHubCharacteristics),
-			d->bPwrOn2PwrGood*2, d->bHubContrCurrent,
-			d->DeviceRemovable[0]);
+	return s;
 }
 }
 
 
-void
-pstring(Device *, int, ulong, void *b, int n)
-{
-	byte *rb;
-	char *s;
-	Rune r;
-	int l;
-
-	if(n <= 2){
-		fprint(2, "\"\"");
-		return;
-	}
-	if(n & 1){
-		fprint(2, "illegal count\n");
-		return;
-	}
-	n = (n - 2)/2;
-	rb = (byte*)b + 2;
-	s = malloc(n*UTFmax+1);
-	for(l=0; --n >= 0; rb += 2){
-		r = GET2(rb);
-		l += runetochar(s+l, &r);
-	}
-	s[l] = 0;
-	fprint(2, "\"%s\"", s);
-	free(s);
-}
-
-void
-pcs_raw(char *tag, byte *b, int n)
+int
+Ufmt(Fmt *f)
 {
 {
 	int i;
 	int i;
-
-	if (debug & Dbginfo) {
-		fprint(2, "%s", tag);
-		for(i=2; i<n; i++)
-			fprint(2, " %.2x", b[i]);
-	}
-}
-
-static void
-pcs_config(Device *, int, ulong, void *b, int n)
-{
-	pcs_raw("CS_CONFIG", b, n);
-}
-
-static void
-pcs_string(Device *, ulong, void *b, int n)
-{
-	pcs_raw("CS_STRING", b, n);
-}
-
-static void
-pcs_endpoint(Device *, int, ulong, void *bb, int n)
-{
-	byte *b = bb;
-
-	if (debug & Dbginfo) {
-		switch(b[2]) {
-		case 0x01:
-			fprint(2,
-		"CS_ENDPOINT for TerminalID %d, delay %d, format_tag %#ux\n",
-				b[3], b[4], b[5] | (b[6]<<8));
-			break;
-		case 0x02:
-			fprint(2,
-"CS_INTERFACE FORMAT_TYPE %d, channels %d, subframesize %d, resolution %d, freqtype %d, ",
-				b[3], b[4], b[5], b[6], b[7]);
-			fprint(2, "freq0 %d, freq1 %d\n",
-				b[8]  |  b[9]<<8 | b[10]<<16,
-				b[11] | b[12]<<8 | b[13]<<16);
+	Dev *d;
+	Usbdev *ud;
+	char buf[1024];
+	char *s, *e;
+
+	s = buf;
+	e = buf+sizeof(buf);
+	d = va_arg(f->args, Dev*);
+	if(d == nil)
+		return fmtprint(f, "<nildev>\n");
+	s = seprint(s, e, "%s", d->dir);
+	ud = d->usb;
+	if(ud == nil)
+		return fmtprint(f, "%s %ld refs\n", buf, d->ref);
+	s = seprint(s, e, " csp %s.%uld.%uld",
+		classname(Class(ud->csp)), Subclass(ud->csp), Proto(ud->csp));
+	s = seprint(s, e, " vid %#ux did %#ux", ud->vid, ud->did);
+	s = seprint(s, e, " refs %ld\n", d->ref);
+	s = seprint(s, e, "\t%s %s %s\n", ud->vendor, ud->product, ud->serial);
+	for(i = 0; i < Nconf; i++){
+		if(ud->conf[i] == nil)
 			break;
 			break;
-		default:
-			pcs_raw("CS_INTERFACE", bb, n);
-		}
+		else
+			s = seprintconf(s, e, ud, i);
 	}
 	}
+	return fmtprint(f, "%s", buf);
 }
 }
 
 
-static void
-pcs_interface(Device *, int n, ulong, void *bb, int nb)
+char*
+estrdup(char *s)
 {
 {
+	char *d;
 
 
-	if ((debug & Dbginfo) && n >= 0)
-		pcs_raw("CS_INTERFACE", bb, nb);
+	d = strdup(s);
+	if(d == nil)
+		sysfatal("strdup: %r");
+	setmalloctag(d, getcallerpc(&s));
+	return d;
 }
 }
 
 
-void
-pdesc(Device *d, int c, ulong csp, byte *b, int n)
+void*
+emallocz(ulong size, int zero)
 {
 {
-	int class, subclass, proto, dalt, i, ep, ifc, len;
-	DConfig *dc;
-	DEndpoint *de;
-	DInterface *di;
-	Dinf *dif;
-	Endpt *dep;
-	void (*f)(Device *, int, ulong, void*, int);
-	char scbuf[64];
-
-	class = Class(csp);
-	dalt = -1;
-	ifc = -1;
-	if (c >= nelem(d->config)) {
-		fprint(2, "Too many interfaces (%d of %d)\n",
-			c, nelem(d->config));
-		return;
-	}
-	if(debug & Dbginfo)
-		fprint(2, "pdesc %d.%d [%d]\n", d->id, c, n);
-	len = -1;
-	while(n > 2 && b[0] && b[0] <= n){
-		if (debug & Dbginfo)
-			fprint(2, "desc %d.%d [%d] %#2.2x: ", d->id, c, b[0], b[1]);
-		switch (b[1]) {
-		case CONFIGURATION:
-			if(b[0] < DCONFLEN){
-				if(debug & Dbginfo)
-					fprint(2, "short config %d < %d", b[0], DCONFLEN);
-				return;
-			}
-			dc = (DConfig*)b;
-			d->config[c]->nif = dc->bNumInterfaces;
-			d->config[c]->cval = dc->bConfigurationValue;
-			d->config[c]->attrib = dc->bmAttributes;
-			d->config[c]->milliamps = dc->MaxPower*2;
-			d->nif += d->config[c]->nif;
-			if (debug & Dbginfo)
-				fprint(2, "config %d: tdlen %d ninterface %d iconfig %d attr %#.2x power %dmA\n",
-					dc->bConfigurationValue,
-					GET2(dc->wTotalLength),
-					dc->bNumInterfaces, dc->iConfiguration,
-					dc->bmAttributes, dc->MaxPower*2);
-			break;
-		case INTERFACE:
-			if(n < DINTERLEN){
-				if(debug & Dbginfo)
-					fprint(2, "short interface %d < %d", b[0], DINTERLEN);
-				return;
-			}
-			di = (DInterface *)b;
-			class = di->bInterfaceClass;
-			subclass = di->bInterfaceSubClass;
-			proto = di->bInterfaceProtocol;
-			csp = CSP(class, subclass, proto);
-			if(debug & Dbginfo){
-				sclass(scbuf, scbuf + sizeof scbuf, csp);
-				fprint(2, "interface %d: alt %d nept %d class %#x subclass %#x proto %d [%s] iinterface %d\n",
-					di->bInterfaceNumber,
-					di->bAlternateSetting,
-					di->bNumEndpoints, class, subclass,
-					proto, scbuf, di->iInterface);
-			}
-			if (c < 0) {
-				fprint(2, "Unexpected INTERFACE message\n");
-				return;
-			}
-			ifc = di->bInterfaceNumber;
-			dalt = di->bAlternateSetting;
-			if (ifc < 0 || ifc >= nelem(d->config[c]->iface))
-				sysfatal("Bad interface number %d", ifc);
-			if (dalt < 0 ||
-			    dalt >= nelem(d->config[c]->iface[ifc]->dalt))
-				sysfatal("Bad alternate number %d", dalt);
-			if (d->config[c] == nil)
-				sysfatal("No config");
-			if (ifc == 0) {
-				if (c == 0)
-					d->ep[0]->csp = csp;
-				d->config[c]->csp = csp;
-			}
-			dif = d->config[c]->iface[ifc];
-			if (dif == nil) {
-				d->config[c]->iface[ifc] = dif =
-					mallocz(sizeof(Dinf), 1);
-				dif->csp = csp;
-			}
-			dif->interface = di->bInterfaceNumber;
-			break;
-		case ENDPOINT:
-			if(n < DENDPLEN)
-				return;
-			de = (DEndpoint *)b;
-			if(debug & Dbginfo) {
-				fprint(2, "addr %#2.2x attrib %#2.2x maxpkt %d interval %dms",
-					de->bEndpointAddress, de->bmAttributes,
-					GET2(de->wMaxPacketSize), de->bInterval);
-				if(de->bEndpointAddress & 0x80)
-					fprint(2, " [IN]");
-				else
-					fprint(2, " [OUT]");
-				switch(de->bmAttributes&0x33){
-				case 0:
-					fprint(2, " [Control]");
-					break;
-				case 1:
-					fprint(2, " [Iso]");
-					switch(de->bmAttributes&0xc){
-					case 0x4:
-						fprint(2, " [Asynchronous]");
-						break;
-					case 0x8:
-						fprint(2, " [Adaptive]");
-						break;
-					case 0xc:
-						fprint(2, " [Synchronous]");
-						break;
-					}
-					break;
-				case 2:
-					fprint(2, " [Bulk]");
-					break;
-				case 3:
-					fprint(2, " [Interrupt]");
-					break;
-				}
-				if(b[0] == 9)
-					fprint(2, "refresh %d synchaddress %d",
-						b[7], b[8]);
-				fprint(2, "\n");
-			}
-			if (c < 0 || ifc < 0 || dalt < 0) {
-				fprint(2, "Unexpected ENDPOINT message\n");
-				return;
-			}
-			dif = d->config[c]->iface[ifc];
-			if (dif == nil)
-				sysfatal("d->config[%d]->iface[%d] == nil",
-					c, ifc);
-			if (dif->dalt[dalt] == nil)
-				dif->dalt[dalt] = mallocz(sizeof(Dalt),1);
-			dif->dalt[dalt]->attrib = de->bmAttributes;
-			dif->dalt[dalt]->interval = de->bInterval;
-			ep = de->bEndpointAddress & 0xf;
-			dep = d->ep[ep];
-			if(debug)
-				fprint(2, "%s: endpoint addr %d\n", argv0, ep);
-			if(dep == nil){
-				d->ep[ep] = dep = newendpt(d, ep, class);
-				dep->dir = de->bEndpointAddress & 0x80
-					? Ein : Eout;
-			}else if ((dep->addr&0x80) !=
-			    (de->bEndpointAddress&0x80))
-				dep->dir = Eboth;
-			dep->maxpkt = GET2(de->wMaxPacketSize);
-			dep->addr = de->bEndpointAddress;
-			dep->type = de->bmAttributes & 0x03;
-			dep->isotype = (de->bmAttributes>>2) & 0x03;
-			dep->csp = csp;
-			dep->conf = d->config[c];
-			dep->iface = dif;
-			for(i = 0; i < nelem(dif->endpt); i++)
-				if(dif->endpt[i] == nil){
-					dif->endpt[i] = dep;
-					break;
-				}
-			if(i == nelem(dif->endpt))
-				fprint(2, "Too many endpoints\n");
-			if (d->nif <= ep)
-				d->nif = ep+1;
-			break;
-		default:
-			assert(nelem(dprinter) == 0x100);
-			f = dprinter[b[1]];
-			if(f != nil){
-				(*f)(d, c, dalt<<24 | ifc<<16 | (csp&0xffff),
-					b, b[0]);
-				if(debug & Dbginfo)
-					fprint(2, "\n");
-			}else
-				if(verbose){
-					int i;
-
-					switch(b[1]){
-					case DEVICE:
-						fprint(2, "(device)");
-						break;
-					case STRING:
-						fprint(2, "(string)");
-						break;
-					default:
-						fprint(2, "(unknown type)");
-					}
-					for(i=1; i<b[0]; i++)
-						fprint(2, " %.2x", b[i]);
-					fprint(2, "\n");
-				}else if(debug & Dbginfo)
-					fprint(2, "\n");
-			break;
-		}
-		len = b[0];
-		n -= len;
-		b += len;
-	}
-	if(n)
-		fprint(2, "pdesc: %d bytes left unparsed, b[0]=%d, b[-len]=%d, len=%d\n", n, b[0], b[-len], len);	
+	void *x;
+
+	x = malloc(size);
+	if(x == nil)
+		sysfatal("malloc: %r");
+	if(zero)
+		memset(x, 0, size);
+	setmalloctag(x, getcallerpc(&size));
+	return x;
 }
 }
+

+ 0 - 21
sys/src/cmd/usb/lib/fmt.c

@@ -1,21 +0,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include "usb.h"
-
-int
-Dfmt(Fmt *f)
-{
-	Device *d;
-
-	d = va_arg(f->args, Device*);
-	if(d == nil)
-		return fmtprint(f, "<null device>");
-	return fmtprint(f, "usb%d/%d", d->ctlrno, d->id);
-}
-
-void
-usbfmtinit(void)
-{
-	fmtinstall('D', Dfmt);
-}

+ 672 - 0
sys/src/cmd/usb/lib/fs.c

@@ -0,0 +1,672 @@
+/*
+ * Framework for USB devices that provide a file tree.
+ * The main process (usbd or the driver's main proc)
+ * calls fsinit() to start FS operation.
+ *
+ * One or more drivers call fsstart/fsend to register
+ * or unregister their operations for their subtrees.
+ *
+ * root dir has qids with 0 in high 32 bits.
+ * for other files we keep the device id in there.
+ * The low 32 bits for directories at / must be 0.
+ */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include "usb.h"
+#include "usbfs.h"
+
+#undef dprint
+#define dprint if(usbfsdebug)fprint
+
+typedef struct Rpc Rpc;
+
+enum
+{
+	Nproc = 3,		/* max nb. of cached FS procs */
+
+	Nofid = ~0,		/* null value for fid number */
+	Notag = ~0,		/* null value for tags */
+	Dietag = 0xdead,		/* fake tag to ask outproc to die */
+
+	Stack = 16 * 1024,
+
+	/* Fsproc requests */
+	Run = 0,		/* call f(r) */
+	Exit,			/* terminate */
+
+};
+
+struct Rpc
+{
+	Fcall	t;
+	Fcall	r;
+	Fid*	fid;
+	int	flushed;
+	Rpc*	next;
+	char	data[Bufsize];
+};
+
+int usbfsdebug;
+
+char Enotfound[] = "file not found";
+char Etoosmall[] = "parameter too small";
+char Eio[] = "i/o error";
+char Eperm[] = "permission denied";
+char Ebadcall[] = "unknown fs call";
+char Ebadfid[] = "fid not found";
+char Einuse[] = "fid already in use";
+char Eisopen[] = "it is already open";
+char Ebadctl[] = "unknown control request";
+
+static char *user;
+static ulong epoch;
+static ulong msgsize = Msgsize;
+static int fsfd = -1;
+static Channel *outc;	/* of Rpc* */
+
+static QLock rpclck;	/* protect vars in this block */
+static Fid *freefids;
+static Fid *fids;
+static Rpc *freerpcs;
+static Rpc *rpcs;
+
+static Channel*procc;
+static Channel*endc;
+
+static Usbfs* fsops;
+
+static void fsioproc(void*);
+
+static void
+schedproc(void*)
+{
+	Channel *proc[Nproc];
+	int nproc;
+	Channel *p;
+
+	Alt a[] =
+	{
+		{procc, &proc[0], CHANSND},
+		{endc, &p, CHANRCV},
+		{nil, nil, CHANEND}
+	};
+	memset(proc, 0, sizeof(proc));
+	nproc = 0;
+	for(;;){
+		if(nproc == 0){
+			proc[0] = chancreate(sizeof(Rpc*), 0);
+			proccreate(fsioproc, proc[0], Stack);
+			nproc++;
+		}
+		switch(alt(a)){
+		case 0:
+			proc[0] = nil;
+			if(nproc > 1){
+				proc[0] = proc[nproc-1];
+				proc[nproc-1] = nil;
+			}
+			nproc--;
+			break;
+		case 1:
+			if(nproc < nelem(proc))
+				proc[nproc++] = p;
+			else
+				sendp(p, nil);
+			break;
+		default:
+			sysfatal("alt");
+		}
+	}
+}
+
+static void
+dump(void)
+{
+	Rpc *rpc;
+	Fid *fid;
+
+	qlock(&rpclck);
+	fprint(2, "dump:\n");
+	for(rpc = rpcs; rpc != nil; rpc = rpc->next)
+		fprint(2, "rpc %#p %F next %#p\n", rpc, &rpc->t, rpc->next);
+	for(fid = fids; fid != nil; fid = fid->next)
+		fprint(2, "fid %d qid %#llux omode %d aux %#p\n",
+			fid->fid, fid->qid.path, fid->omode, fid->aux);
+	fprint(2, "\n");
+	qunlock(&rpclck);
+}
+
+static Rpc*
+newrpc(void)
+{
+	Rpc *r;
+
+	qlock(&rpclck);
+	r = freerpcs;
+	if(r != nil)
+		freerpcs = r->next;
+	else
+		r = emallocz(sizeof(Rpc), 0);
+	r->next = rpcs;
+	rpcs = r;
+	r->t.tag = r->r.tag = Notag;
+	r->t.fid = r->r.fid = Nofid;
+	r->t.type = r->r.type = 0;
+	r->flushed = 0;
+	r->fid = nil;
+	r->r.data = (char*)r->data;
+	qunlock(&rpclck);
+	return r;
+}
+
+static void
+freerpc(Rpc *r)
+{
+	Rpc **l;
+	if(r == nil)
+		return;
+	qlock(&rpclck);
+	for(l = &rpcs; *l != nil && *l != r; l = &(*l)->next)
+		;
+	assert(*l == r);
+	*l = r->next;
+	r->next = freerpcs;
+	freerpcs = r;
+	r->t.type = 0;
+	r->t.tag = 0x77777777;
+	qunlock(&rpclck);
+}
+
+static void
+flushrpc(int tag)
+{
+	Rpc *r;
+
+	qlock(&rpclck);
+	for(r = rpcs; r != nil; r = r->next)
+		if(r->t.tag == tag){
+			r->flushed = 1;
+			break;
+		}
+	qunlock(&rpclck);
+}
+
+static Fid*
+getfid(int fid, int alloc)
+{
+	Fid *f;
+
+	qlock(&rpclck);
+	for(f = fids; f != nil && f->fid != fid; f = f->next)
+		;
+	if(f != nil && alloc != 0){	/* fid in use */
+		qunlock(&rpclck);
+		return nil;
+	}
+	if(f == nil && alloc != 0){
+		if(freefids != nil){
+			f = freefids;
+			freefids = freefids->next;
+		}else
+			f = emallocz(sizeof(Fid), 1);
+		f->fid = fid;
+		f->aux = nil;
+		f->omode = ONONE;
+		f->next = fids;
+		fids = f;
+	}
+	qunlock(&rpclck);
+	return f;
+}
+
+static void
+freefid(Fid *f)
+{
+	Fid **l;
+
+	if(f == nil)
+		return;
+	if(fsops->clunk != nil)
+		fsops->clunk(fsops, f);
+	qlock(&rpclck);
+	for(l = &fids; *l != nil && *l != f; l = &(*l)->next)
+		;
+	assert(*l == f);
+	*l = f->next;
+	f->next = freefids;
+	freefids = f;
+	qunlock(&rpclck);
+}
+
+static Rpc*
+fserror(Rpc *rpc, char* fmt, ...)
+{
+	va_list arg;
+	char *c;
+
+	va_start(arg, fmt);
+	c = (char*)rpc->data;
+	vseprint(c, c+sizeof(rpc->data), fmt, arg);
+	va_end(arg);
+	rpc->r.type = Rerror;
+	rpc->r.ename = (char*)rpc->data;
+	return rpc;
+}
+
+static Rpc*
+fsversion(Rpc *r)
+{
+	if(r->t.msize < 256)
+		return fserror(r, Etoosmall);
+	if(strncmp(r->t.version, "9P2000", 6) != 0)
+		return fserror(r, "wrong version");
+	if(r->t.msize < msgsize)
+		msgsize = r->t.msize;
+	r->r.msize = msgsize;
+	r->r.version = "9P2000";
+	return r;
+}
+
+static Rpc*
+fsattach(Rpc *r)
+{
+	static int already;
+
+	/* Reload user because at boot it could be still none */
+	user=getuser();
+	if(already++ > 0 && strcmp(r->t.uname, user) != 0)
+		return fserror(r, Eperm);
+	if(r->fid == nil)
+		return fserror(r, Einuse);
+
+	r->r.qid.type = QTDIR;
+	r->r.qid.path = fsops->qid;
+	r->r.qid.vers = 0;
+	r->fid->qid = r->r.qid;
+	return r;
+}
+
+static Rpc*
+fswalk(Rpc *r)
+{
+	int i;
+	Fid *nfid;
+	Fid *ofid;
+
+	if(r->fid->omode != ONONE)
+		return fserror(r, Eisopen);
+
+	nfid = nil;
+	ofid = r->fid;
+	if(r->t.newfid != r->t.fid){
+		nfid = getfid(r->t.newfid, 1);
+		if(nfid == nil)
+			return fserror(r, Einuse);
+		nfid->qid = r->fid->qid;
+		if(fsops->clone != nil)
+			fsops->clone(fsops, ofid, nfid);
+		else
+			nfid->aux = r->fid->aux;
+		r->fid = nfid;
+	}
+	r->r.nwqid = 0;
+	for(i = 0; i < r->t.nwname; i++)
+		if(fsops->walk(fsops, r->fid, r->t.wname[i]) < 0)
+			break;
+		else
+			r->r.wqid[i] = r->fid->qid;
+	r->r.nwqid = i;
+	if(i != r->t.nwname && r->t.nwname > 0){
+		if(nfid != nil)
+			freefid(nfid);
+		r->fid = ofid;
+	}
+	if(i == 0 && r->t.nwname > 0)
+		return fserror(r, "%r");
+	return r;
+}
+
+static void
+fsioproc(void* a)
+{
+	Channel *p = a;
+	Rpc *rpc;
+	long rc;
+	Fcall *t;
+	Fcall *r;
+	Fid *fid;
+
+	dprint(2, "%s: fsioproc pid %d\n", argv0, getpid());
+	while((rpc = recvp(p)) != nil){
+		t = &rpc->t;
+		r = &rpc->r;
+		fid = rpc->fid;
+		rc = -1;
+		dprint(2, "%s: fsioproc pid %d: req %d\n", argv0, getpid(), t->type);
+		switch(t->type){
+		case Topen:
+			rc = fsops->open(fsops, fid, t->mode);
+			if(rc >= 0){
+				r->iounit = 0;
+				r->qid = fid->qid;
+				fid->omode = t->mode & 3;
+			}
+			break;
+		case Tread:
+			rc = fsops->read(fsops,fid,r->data,t->count,t->offset);
+			if(rc >= 0){
+				if(rc > t->count)
+					print("%s: bug: read %ld bytes > %ud wanted\n",
+						argv0, rc, t->count);
+				r->count = rc;
+			}
+			break;
+		case Twrite:
+			rc = fsops->write(fsops,fid,t->data,t->count,t->offset);
+			r->count = rc;
+			break;
+		default:
+			sysfatal("fsioproc: bad type");
+		}
+		if(rc < 0)
+			sendp(outc, fserror(rpc, "%r"));
+		else
+			sendp(outc, rpc);
+		sendp(endc, p);
+	}
+	chanfree(p);
+	dprint(2, "%s: fsioproc %d exiting\n", argv0, getpid());
+	threadexits(nil);
+}
+
+static Rpc*
+fsopen(Rpc *r)
+{
+	Channel *p;
+
+	if(r->fid->omode != ONONE)
+		return fserror(r, Eisopen);
+	if((r->t.mode & 3) != OREAD && (r->fid->qid.type & QTDIR) != 0)
+		return fserror(r, Eperm);
+	p = recvp(procc);
+	sendp(p, r);
+	return nil;
+}
+
+int
+usbdirread(Usbfs*f, Qid q, char *data, long cnt, vlong off, Dirgen gen, void *arg)
+{
+	Dir d;
+	char name[Namesz];
+	int i;
+	int n;
+	int nd;
+
+	memset(&d, 0, sizeof(d));
+	d.name = name;
+	d.uid = d.gid = d.muid = user;
+	d.atime = time(nil);
+	d.mtime = epoch;
+	d.length = 0;
+	for(i = n = 0; gen(f, q, i, &d, arg) >= 0; i++){
+		if(usbfsdebug > 1)
+			fprint(2, "%s: dir %d q %#llux: %D\n", argv0, i, q.path, &d);
+		nd = convD2M(&d, (uchar*)data+n, cnt-n);
+		if(nd <= BIT16SZ)
+			break;
+		if(off > 0)
+			off -= nd;
+		else
+			n += nd;
+		d.name = name;
+		d.uid = d.gid = d.muid = user;
+		d.atime = time(nil);
+		d.mtime = epoch;
+		d.length = 0;
+	}
+	return n;
+}
+
+long
+usbreadbuf(void *data, long count, vlong offset, void *buf, long n)
+{
+	if(offset >= n)
+		return 0;
+	if(offset + count > n)
+		count = n - offset;
+	memmove(data, (char*)buf + offset, count);
+	return count;
+}
+
+static Rpc*
+fsread(Rpc *r)
+{
+	Channel *p;
+
+	if(r->fid->omode != OREAD && r->fid->omode != ORDWR)
+		return fserror(r, Eperm);
+	p = recvp(procc);
+	sendp(p, r);
+	return nil;
+}
+
+static Rpc*
+fswrite(Rpc *r)
+{
+	Channel *p;
+
+	if(r->fid->omode != OWRITE && r->fid->omode != ORDWR)
+		return fserror(r, Eperm);
+	p = recvp(procc);
+	sendp(p, r);
+	return nil;
+}
+
+static Rpc*
+fsclunk(Rpc *r)
+{
+	freefid(r->fid);
+	return r;
+}
+
+static Rpc*
+fsno(Rpc *r)
+{
+	return fserror(r, Eperm);
+}
+
+static Rpc*
+fsstat(Rpc *r)
+{
+	Dir d;
+	char name[Namesz];
+
+	memset(&d, 0, sizeof(d));
+	d.name = name;
+	d.uid = d.gid = d.muid = user;
+	d.atime = time(nil);
+	d.mtime = epoch;
+	d.length = 0;
+	if(fsops->stat(fsops, r->fid->qid, &d) < 0)
+		return fserror(r, "%r");
+	r->r.stat = (uchar*)r->data;
+	r->r.nstat = convD2M(&d, (uchar*)r->data, msgsize);
+	return r;
+}
+
+static Rpc*
+fsflush(Rpc *r)
+{
+	/*
+	 * Flag it as flushed and respond.
+	 * Either outproc will reply to the flushed request
+	 * before responding to flush, or it will never reply to it.
+	 * Note that we do NOT abort the ongoing I/O.
+	 * That might leave the affected endpoints in a failed
+	 * state. Instead, we pretend the request is aborted.
+	 *
+	 * Only open, read, and write are processed
+	 * by auxiliary processes and other requests wil never be
+	 * flushed in practice.
+	 */
+	flushrpc(r->t.oldtag);
+	return r;
+}
+
+Rpc* (*fscalls[])(Rpc*) = {
+	[Tversion]	fsversion,
+	[Tauth]		fsno,
+	[Tattach]	fsattach,
+	[Twalk]		fswalk,
+	[Topen]		fsopen,
+	[Tcreate]	fsno,
+	[Tread]		fsread,
+	[Twrite]	fswrite,
+	[Tclunk]	fsclunk,
+	[Tremove]	fsno,
+	[Tstat]		fsstat,
+	[Twstat]	fsno,
+	[Tflush]	fsflush,
+};
+
+static void
+outproc(void*)
+{
+	static uchar buf[Bufsize];
+	Rpc *rpc;
+	int nw;
+	static int once = 0;
+
+	if(once++ != 0)
+		sysfatal("more than one outproc");
+	for(;;){
+		do
+			rpc = recvp(outc);
+		while(rpc == nil);		/* a delayed reply */
+		if(rpc->t.tag == Dietag)
+			break;
+		if(rpc->flushed){
+			dprint(2, "outproc: tag %d flushed\n", rpc->t.tag);
+			freerpc(rpc);
+			continue;
+		}
+		dprint(2, "-> %F\n", &rpc->r);
+		nw = convS2M(&rpc->r, buf, sizeof(buf));
+		if(nw == sizeof(buf))
+			fprint(2, "%s: outproc: buffer is too small\n", argv0);
+		if(nw <= BIT16SZ)
+			fprint(2, "%s: conS2M failed\n", argv0);
+		else if(write(fsfd, buf, nw) != nw){
+			fprint(2, "%s: outproc: write: %r", argv0);
+			/* continue and let the reader abort us */
+		}
+		if(usbfsdebug > 1)
+			dump();
+		freerpc(rpc);
+	}
+	dprint(2, "%s: outproc: exiting\n", argv0);
+}
+
+static void
+usbfs(void*)
+{
+	Rpc *rpc;
+	int nr;
+	static int once = 0;
+
+	if(once++ != 0)
+		sysfatal("more than one usbfs proc");
+
+	outc = chancreate(sizeof(Rpc*), 1);
+	procc = chancreate(sizeof(Channel*), 0);
+	endc = chancreate(sizeof(Channel*), 0);
+	if(outc == nil || procc == nil || endc == nil)
+		sysfatal("chancreate: %r");
+	threadcreate(schedproc, nil, Stack);
+	proccreate(outproc, nil, Stack);
+	for(;;){
+		rpc = newrpc();
+		do{
+			nr = read9pmsg(fsfd, rpc->data, sizeof(rpc->data));
+		}while(nr == 0);
+		if(nr < 0){
+			dprint(2, "%s: usbfs: read: '%r'", argv0);
+			if(fsops->end != nil)
+				fsops->end(fsops);
+			else
+				closedev(fsops->dev);
+			rpc->t.tag = Dietag;
+			sendp(outc, rpc);
+			break;
+		}
+		if(convM2S((uchar*)rpc->data, nr, &rpc->t) <=0){
+			dprint(2, "%s: convM2S failed\n", argv0);
+			freerpc(rpc);
+			continue;
+		}
+		dprint(2, "<- %F\n", &rpc->t);
+		rpc->r.tag = rpc->t.tag;
+		rpc->r.type = rpc->t.type + 1;
+		rpc->r.fid = rpc->t.fid;
+		if(fscalls[rpc->t.type] == nil){
+			sendp(outc, fserror(rpc, Ebadcall));
+			continue;
+		}
+		if(rpc->t.fid != Nofid){
+			if(rpc->t.type == Tattach)
+				rpc->fid = getfid(rpc->t.fid, 1);
+			else
+				rpc->fid = getfid(rpc->t.fid, 0);
+			if(rpc->fid == nil){
+				sendp(outc, fserror(rpc, Ebadfid));
+				continue;
+			}
+		}
+		sendp(outc, fscalls[rpc->t.type](rpc));
+	}
+	dprint(2, "%s: ubfs: eof: exiting\n", argv0);
+}
+
+void
+usbfsinit(char* srv, char *mnt, Usbfs *f, int flag)
+{
+	int fd[2];
+	int sfd;
+	int afd;
+	char sfile[40];
+
+	fsops = f;
+	if(pipe(fd) < 0)
+		sysfatal("pipe: %r");
+	user = getuser();
+	epoch = time(nil);
+
+	fmtinstall('D', dirfmt);
+	fmtinstall('M', dirmodefmt);
+	fmtinstall('F', fcallfmt);
+	fsfd = fd[1];
+	procrfork(usbfs, nil, Stack, RFNAMEG);	/* no RFFDG */
+	if(srv != nil){
+		snprint(sfile, sizeof(sfile), "#s/%s", srv);
+		remove(sfile);
+		sfd = create(sfile, OWRITE, 0660);
+		if(sfd < 0)
+			sysfatal("post: %r");
+		snprint(sfile, sizeof(sfile), "%d", fd[0]);
+		if(write(sfd, sfile, strlen(sfile)) != strlen(sfile))
+			sysfatal("post: %r");
+		close(sfd);
+	}
+	if(mnt != nil){
+		sfd = dup(fd[0], -1);	/* debug */
+		afd = fauth(sfd, "");
+		if(afd >= 0)
+			sysfatal("authentication required??");
+		if(mount(sfd, -1, mnt, flag, "") < 0)
+			sysfatal("mount: %r");
+	}
+	close(fd[0]);
+}
+

+ 418 - 0
sys/src/cmd/usb/lib/fsdir.c

@@ -0,0 +1,418 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include "usb.h"
+#include "usbfs.h"
+
+typedef struct Rpc Rpc;
+
+enum
+{
+	Incr = 3,	/* increments for fs array */
+	Dtop = 0,	/* high 32 bits for / 	*/
+	Qdir = 0,	/* low 32 bits for /devdir */
+};
+
+QLock fslck;
+static Usbfs** fs;
+static int nfs;
+static int fsused;
+static int exitonclose = 1;
+
+void
+usbfsexits(int y)
+{
+	exitonclose = y;
+}
+
+static int
+qiddev(uvlong path)
+{
+	return (int)(path>>32) & 0xFF;
+}
+
+static int
+qidfile(uvlong path)
+{
+	return (int)(path & 0xFFFFFFFFULL);
+}
+
+static uvlong
+mkqid(int qd, int qf)
+{
+	return ((uvlong)qd << 32) | (uvlong)qf;
+}
+
+void
+usbfsdirdump(void)
+{
+	int i;
+
+	qlock(&fslck);
+	fprint(2, "%s: fs list: (%d used %d total)\n", argv0, fsused, nfs);
+	for(i = 1; i < nfs; i++)
+		if(fs[i] != nil)
+			if(fs[i]->dev != nil)
+				fprint(2, "%s\t%s dev %#p refs %ld\n",
+					argv0, fs[i]->name, fs[i]->dev, fs[i]->dev->ref);
+			else
+				fprint(2, "%s:\t%s\n", argv0, fs[i]->name);
+	qunlock(&fslck);
+}
+
+void
+usbfsadd(Usbfs *dfs)
+{
+	int i, j;
+
+	dprint(2, "%s: fsadd %s\n", argv0, dfs->name);
+	qlock(&fslck);
+	for(i = 1; i < nfs; i++)
+		if(fs[i] == nil)
+			break;
+	if(i >= nfs){
+		if((nfs%Incr) == 0){
+			fs = realloc(fs, sizeof(Usbfs*) * (nfs+Incr));
+			if(fs == nil)
+				sysfatal("realloc: %r");
+			for(j = nfs; j < nfs+Incr; j++)
+				fs[j] = nil;
+		}
+		if(nfs == 0)	/* do not use entry 0 */
+			nfs++;
+		fs[nfs++] = dfs;
+	}else
+		fs[i] = dfs;
+	dfs->qid = mkqid(i, 0);
+	fsused++;
+	qunlock(&fslck);
+}
+
+static void
+usbfsdelnth(int i)
+{
+	if(fs[i] != nil){
+		dprint(2, "%s: fsdel %s", argv0, fs[i]->name);
+		if(fs[i]->dev != nil){
+			dprint(2, " dev %#p ref %ld\n",
+				fs[i]->dev, fs[i]->dev->ref);
+		}else
+			dprint(2, "no dev\n");
+		if(fs[i]->end != nil)
+			fs[i]->end(fs[i]);
+		else
+			closedev(fs[i]->dev);
+		fsused--;
+	}
+	fs[i] = nil;
+	if(fsused == 0 && exitonclose != 0){
+		fprint(2, "%s: all file systems gone: exiting\n", argv0);
+		threadexitsall(nil);
+	}
+}
+
+void
+usbfsdel(Usbfs *dfs)
+{
+	int i;
+
+	qlock(&fslck);
+	for(i = 0; i < nfs; i++)
+		if(dfs == nil || fs[i] == dfs){
+			usbfsdelnth(i);
+			if(dfs != nil)
+				break;
+		}
+	qunlock(&fslck);
+}
+
+static void
+fsend(Usbfs*)
+{
+	dprint(2, "%s: fsend\n", argv0);
+	usbfsdel(nil);
+}
+
+void
+usbfsgone(char *dir)
+{
+	int i;
+
+	qlock(&fslck);
+	/* devices may have more than one fs */
+	for(i = 0; i < nfs; i++)
+		if(fs[i] != nil && fs[i]->dev != nil)
+		if(strcmp(fs[i]->dev->dir, dir) == 0)
+			usbfsdelnth(i);
+	qunlock(&fslck);
+}
+
+static void
+fsclone(Usbfs*, Fid *o, Fid *n)
+{
+	int qd;
+	Dev *dev;
+	void (*xfsclone)(Usbfs *fs, Fid *of, Fid *nf);
+
+	xfsclone = nil;
+	dev = nil;
+	qd = qiddev(o->qid.path);
+	qlock(&fslck);
+	if(qd != Dtop && fs[qd] != nil && fs[qd]->clone != nil){
+		dev = fs[qd]->dev;
+		if(dev != nil)
+			incref(dev);
+		xfsclone = fs[qd]->clone;
+	}
+	qunlock(&fslck);
+	if(xfsclone != nil){
+		xfsclone(fs[qd], o, n);
+	}
+	if(dev != nil)
+		closedev(dev);
+}
+
+static int
+fswalk(Usbfs*, Fid *fid, char *name)
+{
+	Qid q;
+	int qd, qf;
+	int i;
+	int rc;
+	Dev *dev;
+	Dir d;
+	int (*xfswalk)(Usbfs *fs, Fid *f, char *name);
+
+	q = fid->qid;
+	qd = qiddev(q.path);
+	qf = qidfile(q.path);
+
+	q.type = QTDIR;
+	q.vers = 0;
+	if(strcmp(name, "..") == 0)
+		if(qd == Dtop || qf == Qdir){
+			q.path = mkqid(Dtop, Qdir);
+			fid->qid = q;
+			return 0;
+		}
+	if(qd != 0){
+		qlock(&fslck);
+		if(fs[qd] == nil){
+			qunlock(&fslck);
+			werrstr(Eio);
+			return -1;
+		}
+		dev = fs[qd]->dev;
+		if(dev != nil)
+			incref(dev);
+		xfswalk = fs[qd]->walk;
+		qunlock(&fslck);
+		rc = xfswalk(fs[qd], fid, name);
+		if(dev != nil)
+			closedev(dev);
+		return rc;
+	}
+	qlock(&fslck);
+	for(i = 0; i < nfs; i++)
+		if(fs[i] != nil && strcmp(name, fs[i]->name) == 0){
+			q.path = mkqid(i, Qdir);
+			fs[i]->stat(fs[i], q, &d); /* may be a file */
+			fid->qid = d.qid;
+			qunlock(&fslck);
+			return 0;
+		}
+	qunlock(&fslck);
+	werrstr(Enotfound);
+	return -1;
+}
+
+static int
+fsopen(Usbfs*, Fid *fid, int mode)
+{
+	int qd;
+	int rc;
+	Dev *dev;
+	int (*xfsopen)(Usbfs *fs, Fid *f, int mode);
+
+	qd = qiddev(fid->qid.path);
+	if(qd == Dtop)
+		return 0;
+	qlock(&fslck);
+	if(fs[qd] == nil){
+		qunlock(&fslck);
+		werrstr(Eio);
+		return -1;
+	}
+	dev = fs[qd]->dev;
+	if(dev != nil)
+		incref(dev);
+	xfsopen = fs[qd]->open;
+	qunlock(&fslck);
+	if(xfsopen != nil)
+		rc = xfsopen(fs[qd], fid, mode);
+	else
+		rc = 0;
+	if(dev != nil)
+		closedev(dev);
+	return rc;
+}
+
+static int
+dirgen(Usbfs*, Qid, int n, Dir *d, void *)
+{
+	int i;
+	Dev *dev;
+	char *nm;
+
+	qlock(&fslck);
+	for(i = 0; i < nfs; i++)
+		if(fs[i] != nil && n-- == 0){
+			d->qid.type = QTDIR;
+			d->qid.path = mkqid(i, Qdir);
+			d->qid.vers = 0;
+			dev = fs[i]->dev;
+			if(dev != nil)
+				incref(dev);
+			nm = d->name;
+			fs[i]->stat(fs[i], d->qid, d);
+			d->name = nm;
+			strncpy(d->name, fs[i]->name, Namesz);
+			if(dev != nil)
+				closedev(dev);
+			qunlock(&fslck);
+			return 0;
+		}
+	qunlock(&fslck);
+	return -1;
+}
+
+static long
+fsread(Usbfs*, Fid *fid, void *data, long cnt, vlong off)
+{
+	int qd;
+	int rc;
+	Dev *dev;
+	Qid q;
+	long (*xfsread)(Usbfs *fs, Fid *f, void *data, long count, vlong );
+
+	q = fid->qid;
+	qd = qiddev(q.path);
+	if(qd == Dtop)
+		return usbdirread(nil, q, data, cnt, off, dirgen, nil);
+	qlock(&fslck);
+	if(fs[qd] == nil){
+		qunlock(&fslck);
+		werrstr(Eio);
+		return -1;
+	}
+	dev = fs[qd]->dev;
+	if(dev != nil)
+		incref(dev);
+	xfsread = fs[qd]->read;
+	qunlock(&fslck);
+	rc = xfsread(fs[qd], fid, data, cnt, off);
+	if(dev != nil)
+		closedev(dev);
+	return rc;
+}
+
+static long
+fswrite(Usbfs*, Fid *fid, void *data, long cnt, vlong off)
+{
+	int qd;
+	int rc;
+	Dev *dev;
+	long (*xfswrite)(Usbfs *fs, Fid *f, void *data, long count, vlong );
+
+	qd = qiddev(fid->qid.path);
+	if(qd == Dtop)
+		sysfatal("fswrite: not for usbd /");
+	qlock(&fslck);
+	if(fs[qd] == nil){
+		qunlock(&fslck);
+		werrstr(Eio);
+		return -1;
+	}
+	dev = fs[qd]->dev;
+	if(dev != nil)
+		incref(dev);
+	xfswrite = fs[qd]->write;
+	qunlock(&fslck);
+	rc = xfswrite(fs[qd], fid, data, cnt, off);
+	if(dev != nil)
+		closedev(dev);
+	return rc;
+}
+
+
+static void
+fsclunk(Usbfs*, Fid* fid)
+{
+	int qd;
+	Dev *dev;
+	void (*xfsclunk)(Usbfs *fs, Fid *f);
+
+	dev = nil;
+	qd = qiddev(fid->qid.path);
+	qlock(&fslck);
+	if(qd != Dtop && fs[qd] != nil){
+		dev=fs[qd]->dev;
+		if(dev != nil)
+			incref(dev);
+		xfsclunk = fs[qd]->clunk;
+	}else
+		xfsclunk = nil;
+	qunlock(&fslck);
+	if(xfsclunk != nil){
+		xfsclunk(fs[qd], fid);
+	}
+	if(dev != nil)
+		closedev(dev);
+}
+
+static int
+fsstat(Usbfs*, Qid qid, Dir *d)
+{
+	int qd;
+	int rc;
+	Dev *dev;
+	int (*xfsstat)(Usbfs *fs, Qid q, Dir *d);
+
+	qd = qiddev(qid.path);
+	if(qd == Dtop){
+		d->qid = qid;
+		d->name = "usb";
+		d->length = 0;
+		d->mode = 0555|DMDIR;
+		return 0;
+	}
+	qlock(&fslck);
+	if(fs[qd] == nil){
+		qunlock(&fslck);
+		werrstr(Eio);
+		return -1;
+	}
+	xfsstat = fs[qd]->stat;
+	dev = fs[qd]->dev;
+	if(dev != nil)
+		incref(dev);
+	qunlock(&fslck);
+	rc = xfsstat(fs[qd], qid, d);
+	if(dev != nil)
+		closedev(dev);
+	return rc;
+}
+
+Usbfs usbdirfs =
+{
+	.walk = fswalk,
+	.clone = fsclone,
+	.clunk = fsclunk,
+	.open = fsopen,
+	.read = fsread,
+	.write = fswrite,
+	.stat = fsstat,
+	.end = fsend,
+};
+

+ 11 - 4
sys/src/cmd/usb/lib/mkfile

@@ -2,14 +2,17 @@
 
 
 LIB=usb.a$O
 LIB=usb.a$O
 OFILES=\
 OFILES=\
-	device.$O\
+	dev.$O\
+	devs.$O\
 	dump.$O\
 	dump.$O\
-	fmt.$O\
-	setup.$O\
-	util.$O\
+	parse.$O\
+	fs.$O\
+	fsdir.$O\
 
 
 HFILES=\
 HFILES=\
 	usb.h\
 	usb.h\
+	usbfs.h\
+
 
 
 UPDATE=\
 UPDATE=\
 	$HFILES\
 	$HFILES\
@@ -20,3 +23,7 @@ UPDATE=\
 
 
 install:V:	$LIB
 install:V:	$LIB
 	date
 	date
+nuke:V:
+	rm -f *.[$OS] y.tab.? y.output y.error
+	rm -f $LIB
+	rm -f usbdev.a8

+ 269 - 0
sys/src/cmd/usb/lib/parse.c

@@ -0,0 +1,269 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <bio.h>
+#include "usb.h"
+
+int
+parsedev(Dev *xd, uchar *b, int n)
+{
+	Usbdev *d;
+	DDev *dd;
+	char *hd;
+
+	d = xd->usb;
+	assert(d != nil);
+	dd = (DDev*)b;
+	if(usbdebug>1){
+		hd = hexstr(b, Ddevlen);
+		fprint(2, "%s: parsedev %s: %s\n", argv0, xd->dir, hd);
+		free(hd);
+	}
+	if(dd->bLength < Ddevlen){
+		werrstr("short dev descr. (%d < %d)", dd->bLength, Ddevlen);
+		return -1;
+	}
+	if(dd->bDescriptorType != Ddev){
+		werrstr("%d is not a dev descriptor", dd->bDescriptorType);
+		return -1;
+	}
+	d->csp = CSP(dd->bDevClass, dd->bDevSubClass, dd->bDevProtocol);
+	d->ep[0]->maxpkt = xd->maxpkt = dd->bMaxPacketSize0;
+	d->class = dd->bDevClass;
+	d->nconf = dd->bNumConfigurations;
+	if(d->nconf == 0)
+		dprint(2, "%s: %s: no configurations\n", argv0, xd->dir);
+	d->vid = GET2(dd->idVendor);
+	d->did = GET2(dd->idProduct);
+	d->vsid = dd->iManufacturer;
+	d->psid = dd->iProduct;
+	d->ssid = dd->iSerialNumber;
+	if(n > Ddevlen && usbdebug>1)
+		fprint(2, "%s: %s: parsedev: %d bytes left",
+			argv0, xd->dir, n - Ddevlen);
+	return Ddevlen;
+}
+
+static int
+parseiface(Usbdev *d, Conf *c, uchar *b, int n, Iface **ipp, Altc **app)
+{
+	int class, subclass, proto;
+	int ifid, altid;
+	DIface *dip;
+	Iface *ip;
+
+	assert(d != nil && c != nil);
+	if(n < Difacelen){
+		werrstr("short interface descriptor");
+		return -1;
+	}
+	dip = (DIface *)b;
+	ifid = dip->bInterfaceNumber;
+	if(ifid < 0 || ifid >= nelem(c->iface)){
+		werrstr("bad interface number %d", ifid);
+		return -1;
+	}
+	if(c->iface[ifid] == nil)
+		c->iface[ifid] = emallocz(sizeof(Iface), 1);
+	ip = c->iface[ifid];
+	class = dip->bInterfaceClass;
+	subclass = dip->bInterfaceSubClass;
+	proto = dip->bInterfaceProtocol;
+	ip->csp = CSP(class, subclass, proto);
+	if(d->csp == 0)				/* use csp from 1st iface */
+		d->csp = ip->csp;		/* if device has none */
+	if(d->class == 0)
+		d->class = class;
+	ip->id = ifid;
+	if(c == d->conf[0] && ifid == 0)	/* ep0 was already there */
+		d->ep[0]->iface = ip;
+	altid = dip->bAlternateSetting;
+	if(altid < 0 || altid >= nelem(ip->altc)){
+		werrstr("bad alternate conf. number %d", altid);
+		return -1;
+	}
+	if(ip->altc[altid] == nil)
+		ip->altc[altid] = emallocz(sizeof(Altc), 1);
+	*ipp = ip;
+	*app = ip->altc[altid];
+	return Difacelen;
+}
+
+extern Ep* mkep(Usbdev *, int);
+
+static int
+parseendpt(Usbdev *d, Conf *c, Iface *ip, Altc *altc, uchar *b, int n, Ep **epp)
+{
+	Ep *ep;
+	DEp *dep;
+	int epid;
+	int dir;
+	int i;
+
+	assert(d != nil && c != nil && ip != nil && altc != nil);
+	if(n < Deplen){
+		werrstr("short endpoint descriptor");
+		return -1;
+	}
+	dep = (DEp *)b;
+	altc->attrib = dep->bmAttributes;	/* here? */
+	altc->interval = dep->bInterval;
+
+	epid = dep->bEndpointAddress & 0xF;
+	assert(epid < nelem(d->ep));
+	if(dep->bEndpointAddress & 0x80)
+		dir = Ein;
+	else
+		dir = Eout;
+	ep = d->ep[epid];
+	if(ep == nil){
+		ep = mkep(d, epid);
+		ep->dir = dir;
+	}else if((ep->addr&0x80) != (dep->bEndpointAddress & 0x80))
+		ep->dir = Eboth;
+	ep->maxpkt = GET2(dep->wMaxPacketSize);
+	ep->ntds = 1 + ((ep->maxpkt >> 11) & 3);
+	ep->maxpkt &= 0x7FF;
+	ep->addr = dep->bEndpointAddress;
+	ep->type = dep->bmAttributes & 0x03;
+	ep->isotype = (dep->bmAttributes>>2) & 0x03;
+	ep->conf = c;
+	ep->iface = ip;
+	for(i = 0; i < nelem(ip->ep); i++)
+		if(ip->ep[i] == nil)
+			break;
+	if(i == nelem(ip->ep)){
+		werrstr("parseendpt: bug: too many end points");
+		fprint(2, "%s: %r\n", argv0);
+		return -1;
+	}
+	*epp = ip->ep[i] = ep;
+	return Dep;
+}
+
+static char*
+dname(int dtype)
+{
+	switch(dtype){
+	case Ddev:	return "device";
+	case Dconf: 	return "config";
+	case Dstr: 	return "string";
+	case Diface:	return "interface";
+	case Dep:	return "endpoint";
+	case Dreport:	return "report";
+	case Dphysical:	return "phys";
+	default:	return "desc";
+	}
+}
+
+int
+parsedesc(Usbdev *d, Conf *c, uchar *b, int n)
+{
+	int	len;
+	int	tot;
+	Iface	*ip;
+	Ep 	*ep;
+	Altc	*altc;
+	int	nd;
+	char	*hd;
+
+	assert(d != nil && c != nil);
+	tot = 0;
+	ip = nil;
+	ep = nil;
+	altc = nil;
+	for(nd = 0; nd < nelem(d->ddesc); nd++)
+		if(d->ddesc[nd] == nil)
+			break;
+
+	while(n > 2 && b[0] != 0 && b[0] <= n){
+		len = b[0];
+		if(usbdebug>1){
+			hd = hexstr(b, len);
+			fprint(2, "%s:\t\tparsedesc %s %x[%d] %s\n",
+				argv0, dname(b[1]), b[1], b[0], hd);
+			free(hd);
+		}
+		switch(b[1]){
+		case Ddev:
+		case Dconf:
+			werrstr("unexpected descriptor %d", b[1]);
+			ddprint(2, "%s\tparsedesc: %r", argv0);
+			break;
+		case Diface:
+			if(parseiface(d, c, b, n, &ip, &altc) < 0){
+				ddprint(2, "%s\tparsedesc: %r\n", argv0);
+				return -1;
+			}
+			break;
+		case Dep:
+			if(ip == nil || altc == nil){
+				werrstr("unexpected endpoint descriptor");
+				break;
+			}
+			if(parseendpt(d, c, ip, altc, b, n, &ep) < 0){
+				ddprint(2, "%s\tparsedesc: %r\n", argv0);
+				return -1;
+			}
+			break;
+		default:
+			if(nd == nelem(d->ddesc)){
+				fprint(2, "%s: parsedesc: too many ddesc\n", argv0);
+				break;
+			}
+			d->ddesc[nd] = emallocz(sizeof(Desc)+b[0], 0);
+			d->ddesc[nd]->iface = ip;
+			d->ddesc[nd]->ep = ep;
+			d->ddesc[nd]->altc = altc;
+			d->ddesc[nd]->conf = c;
+			memmove(&d->ddesc[nd]->data, b, len);
+			++nd;
+		}
+		n -= len;
+		b += len;
+		tot += len;
+	}
+	return tot;
+}
+
+int
+parseconf(Usbdev *d, Conf *c, uchar *b, int n)
+{
+	DConf* dc;
+	int	l;
+	int	nr;
+	char	*hd;
+
+	assert(d != nil && c != nil);
+	dc = (DConf*)b;
+	if(usbdebug>1){
+		hd = hexstr(b, Dconflen);
+		fprint(2, "%s:\tparseconf  %s\n", argv0, hd);
+		free(hd);
+	}
+	if(dc->bLength < Dconflen){
+		werrstr("short configuration descriptor");
+		return -1;
+	}
+	if(dc->bDescriptorType != Dconf){
+		werrstr("not a configuration descriptor");
+		return -1;
+	}
+	c->cval = dc->bConfigurationValue;
+	c->attrib = dc->bmAttributes;
+	c->milliamps = dc->MaxPower*2;
+	l = GET2(dc->wTotalLength);
+	if(n < l){
+		werrstr("truncated configuration info");
+		return -1;
+	}
+	n -= Dconflen;
+	b += Dconflen;
+	nr = 0;
+	if(n > 0 && (nr=parsedesc(d, c, b, n)) < 0)
+		return -1;
+	n -= nr;
+	if(n > 0 && usbdebug>1)
+		fprint(2, "%s:\tparseconf: %d bytes left\n", argv0, n);
+	return l;
+}

+ 0 - 101
sys/src/cmd/usb/lib/setup.c

@@ -1,101 +0,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include "usb.h"
-
-int
-setupcmd(Endpt *e, int type, int req, int value, int index, byte *data, int count)
-{
-	byte *wp;
-	int n, i, fd;
-
-	if (e == nil)
-		abort();
-	fd = e->dev->setup;
-	if(fd < 0)
-		sysfatal("RSC: this used to use the global usbsetup0");
-	wp = malloc(8+count);
-	if (wp == nil) sysfatal("setupcmd: malloc");
-	wp[0] = type;
-	wp[1] = req;
-	PUT2(wp+2, value);
-	PUT2(wp+4, index);
-	PUT2(wp+6, count);
-	memmove(wp+8, data, count);
-	if (debugdebug) {
-		fprint(2, "out\t%d\t[%d]", fd, 8+count);
-		for(i=0; i<8+count; i++)
-			fprint(2, " %.2ux", wp[i]);
-		fprint(2, "\n");
-	}
-	n = write(fd, wp, 8+count);
-	if (n < 0) {
-		fprint(2, "setupcmd: write err: %r\n");
-		return -1;
-	}
-	if (n != 8+count) {
-		fprint(2, "setupcmd: short write: %d\n", n);
-		return -1;
-	}
-	return n;
-}
-
-int
-setupreq(Endpt *e, int type, int req, int value, int index, int count)
-{
-	byte *wp, buf[8];
-	int n, i, fd;
-
-	if(e == nil)
-		abort();
-	fd = e->dev->setup;
-	if(fd < 0)
-		sysfatal("RSC: this used to use the global usbsetup0");
-	wp = buf;
-	wp[0] = type;
-	wp[1] = req;
-	PUT2(wp+2, value);
-	PUT2(wp+4, index);
-	PUT2(wp+6, count);
-	if(debugdebug){
-		fprint(2, "out\t%d\t[8]", fd);
-		for(i=0; i<8; i++)
-			fprint(2, " %.2ux", buf[i]);
-		fprint(2, "\n");
-	}
-	n = write(fd, buf, 8);
-	if (n < 0) {
-		fprint(2, "setupreq: write err: %r\n");
-		return -1;
-	}
-	if (n != 8) {
-		fprint(2, "setupreq: short write: %d\n", n);
-		return -1;
-	}
-	return n;
-}
-
-int
-setupreply(Endpt *e, void *buf, int nb)
-{
-	uchar *p;
-	int i, fd, nr;
-	char err[32];
-
-	fd = e->dev->setup;
-	if(fd < 0)
-		sysfatal("RSC: this used to use the global usbsetup0");
-	while ((nr = read(fd, buf, nb)) < 0) {
-		rerrstr(err, sizeof err);
-		if (strcmp(err, "interrupted") != 0)
-			break;
-	}
-	p = buf;
-	if (debugdebug) {
-		fprint(2, "in\t%d\t[%d]", fd, nr);
-		for(i=0; i<nr; i++)
-			fprint(2, " %.2ux", p[i]);
-		fprint(2, "\n");
-	}
-	return nr;
-}

+ 269 - 305
sys/src/cmd/usb/lib/usb.h

@@ -1,152 +1,93 @@
-/*
- * USB implementation for Plan 9
- *	(c) 1998, 1999 C H Forsyth
- */
-
-enum {
-	Dbginfo =	0x01,
-	Dbgfs =		0x02,
-	Dbgproc =	0x04,
-	Dbgcontrol =	0x08,
-};
-
-extern int debug, debugdebug, usbdebug, verbose;
-
-typedef uchar byte;
-
-#ifndef CHANNOP
-typedef struct Ref Ref;
-
-#define threadprint fprint
-#endif
-
-/*
- * USB definitions
- */
+typedef struct Altc Altc;
+typedef struct Conf Conf;
+typedef struct DConf DConf;
+typedef struct DDesc DDesc;
+typedef struct DDev DDev;
+typedef struct DEp DEp;
+typedef struct DIface DIface;
+typedef struct Desc Desc;
+typedef struct Dev Dev;
+typedef struct Ep Ep;
+typedef struct Iface Iface;
+typedef struct Usbdev Usbdev;
 
 
-typedef struct DConfig DConfig;
-typedef struct DDevice DDevice;
-typedef struct DEndpoint DEndpoint;
-typedef struct DHid DHid;
-typedef struct DHub DHub;
-typedef struct DInterface DInterface;
-typedef struct Dconf Dconf;
-typedef struct Dalt Dalt;
-typedef struct Device Device;
-typedef struct Dinf Dinf;
-typedef struct Endpt Endpt;
-
-typedef struct Namelist Namelist;
-
-#define	GET2(p)	((((p)[1]&0xFF)<<8)|((p)[0]&0xFF))
-#define	PUT2(p,v)	{((p)[0] = (v)); ((p)[1] = (v)>>8);}
 
 
 enum
 enum
 {
 {
+	Uctries	= 4,		/* nb. of tries for usbcmd */
+	Ucdelay = 50,		/* delay before retrying */
+
 	/* request type */
 	/* request type */
-	RH2D = 0<<7,
-	RD2H = 1<<7,
+	Rh2d	= 0<<7,		/* host to device */
+	Rd2h	= 1<<7,		/* device to host */ 
 
 
-	Rstandard = 0<<5,	/* types */
-	Rclass =  1<<5,
-	Rvendor = 2<<5,
+	Rstd	= 0<<5,		/* types */
+	Rclass	= 1<<5,
+	Rvendor	= 2<<5,
 
 
-	Rdevice = 0,		/* recipients */
-	Rinterface = 1,
-	Rendpt = 2,
-	Rother = 3,
+	Rdev	= 0,		/* recipients */
+	Riface	= 1,
+	Rep	= 2,		/* endpoint */
+	Rother	= 3,
 
 
 	/* standard requests */
 	/* standard requests */
-	GET_STATUS = 0,
-	CLEAR_FEATURE = 1,
-	SET_FEATURE = 3,
-	SET_ADDRESS = 5,
-	GET_DESCRIPTOR = 6,
-	SET_DESCRIPTOR = 7,
-	GET_CONFIGURATION = 8,
-	SET_CONFIGURATION = 9,
-	GET_INTERFACE = 10,
-	SET_INTERFACE = 11,
-	SYNCH_FRAME = 12,
-
-	GET_CUR = 0x81,
-	GET_MIN = 0x82,
-	GET_MAX = 0x83,
-	GET_RES = 0x84,
-	SET_CUR = 0x01,
-	SET_MIN = 0x02,
-	SET_MAX = 0x03,
-	SET_RES = 0x04,
-
-	/* hub class feature selectors */
-	C_HUB_LOCAL_POWER = 0,
-	C_HUB_OVER_CURRENT,
-	PORT_CONNECTION = 0,
-	PORT_ENABLE = 1,
-	PORT_SUSPEND = 2,
-	PORT_OVER_CURRENT = 3,
-	PORT_RESET = 4,
-	PORT_POWER = 8,
-	PORT_LOW_SPEED = 9,
-	C_PORT_CONNECTION = 16,
-	C_PORT_ENABLE,
-	C_PORT_SUSPEND,
-	C_PORT_OVER_CURRENT,
-	C_PORT_RESET,
+	Rgetstatus	= 0,
+	Rclearfeature	= 1,
+	Rsetfeature	= 3,
+	Rsetaddress	= 5,
+	Rgetdesc	= 6,
+	Rsetdesc	= 7,
+	Rgetconf	= 8,
+	Rsetconf	= 9,
+	Rgetiface	= 10,
+	Rsetiface	= 11,
+	Rsynchframe	= 12,
+
+	Rgetcur	= 0x81,
+	Rgetmin	= 0x82,
+	Rgetmax	= 0x83,
+	Rgetres	= 0x84,
+	Rsetcur	= 0x01,
+	Rsetmin	= 0x02,
+	Rsetmax	= 0x03,
+	Rsetres	= 0x04,
+
+	/* dev classes */
+	Clnone		= 0,		/* not in usb */
+	Claudio		= 1,
+	Clcomms		= 2,
+	Clhid		= 3,
+	Clprinter	= 7,
+	Clstorage	= 8,
+	Clhub		= 9,
+	Cldata		= 10,
+
+	/* standard descriptor sizes */
+	Ddevlen		= 18,
+	Dconflen	= 9,
+	Difacelen	= 9,
+	Deplen		= 7,
 
 
 	/* descriptor types */
 	/* descriptor types */
-	DEVICE = 1,
-	CONFIGURATION = 2,
-	STRING = 3,
-	INTERFACE = 4,
-	ENDPOINT = 5,
-	HID = 0x21,
-	REPORT = 0x22,
-	PHYSICAL = 0x23,
-	HUB	= 0x29,
+	Ddev		= 1,
+	Dconf		= 2,
+	Dstr		= 3,
+	Diface		= 4,
+	Dep		= 5,
+	Dreport		= 0x22,
+	Dfunction		= 0x24,
+	Dphysical	= 0x23,
 
 
 	/* feature selectors */
 	/* feature selectors */
-	DEVICE_REMOTE_WAKEUP = 1,
-	ENDPOINT_STALL = 0,
-
-	/* report types */
-	Tmtype = 3<<2,
-	Tmitem = 0xF0,
-	Tmain = 0<<2,
-		Tinput = 0x80,
-		Toutput = 0x90,
-		Tfeature = 0xB0,
-		Tcoll = 0xA0,
-		Tecoll = 0xC0,
-	 Tglobal = 1<<2,
-		Tusagepage = 0x00,
-		Tlmin = 0x10,
-		Tlmax = 0x20,
-		Tpmin = 0x30,
-		Tpmax = 0x40,
-		Tunitexp = 0x50,
-		Tunit = 0x60,
-		Trepsize = 0x70,
-		TrepID = 0x80,
-		Trepcount = 0x90,
-		Tpush = 0xA0,
-		Tpop = 0xB0,
-	 Tlocal = 2<<2,
-		Tusage = 0x00,
-		Tumin = 0x10,
-		Tumax = 0x20,
-		Tdindex = 0x30,
-		Tdmin = 0x40,
-		Tdmax = 0x50,
-		Tsindex = 0x70,
-		Tsmin = 0x80,
-		Tsmax = 0x90,
-		Tsetdelim = 0xA0,
-	 Treserved = 3<<2,
-	 Tlong = 0xFE,
+	Fdevremotewakeup = 1,
+	Fhalt 	= 0,
 
 
 	/* parameters */
 	/* parameters */
-	Nendpt =	16,
+	Nep = 16,
+	Niface = 16,
+	Naltc = 16,
+	Nddesc = 32,
+	Nconf = 16,
 
 
 	/* device state */
 	/* device state */
 	Detached = 0,
 	Detached = 0,
@@ -155,11 +96,6 @@ enum
 	Assigned,
 	Assigned,
 	Configured,
 	Configured,
 
 
-	/* classes */
-	Noclass = 0,
-	Hubclass,
-	Otherclass,
-
 	/* endpoint direction */
 	/* endpoint direction */
 	Ein = 0,
 	Ein = 0,
 	Eout,
 	Eout,
@@ -176,221 +112,249 @@ enum
 	Easync = 1,
 	Easync = 1,
 	Eadapt = 2,
 	Eadapt = 2,
 	Esync = 3,
 	Esync = 3,
+
+	/* config attrib */
+	Cbuspowered = 1<<7,
+	Cselfpowered = 1<<6,
+	Cremotewakeup = 1<<5,
+
+	/* report types */
+	Tmtype	= 3<<2,
+	Tmitem	= 0xF0,
+	Tmain	= 0<<2,
+		Tinput	= 0x80,
+		Toutput	= 0x90,
+		Tfeature = 0xB0,
+		Tcoll	= 0xA0,
+		Tecoll	= 0xC0,
+	 Tglobal	= 1<<2,
+		Tusagepage = 0x00,
+		Tlmin	= 0x10,
+		Tlmax	= 0x20,
+		Tpmin	= 0x30,
+		Tpmax	= 0x40,
+		Tunitexp	= 0x50,
+		Tunit	= 0x60,
+		Trepsize	= 0x70,
+		TrepID	= 0x80,
+		Trepcount = 0x90,
+		Tpush	= 0xA0,
+		Tpop	= 0xB0,
+	 Tlocal	= 2<<2,
+		Tusage	= 0x00,
+		Tumin	= 0x10,
+		Tumax	= 0x20,
+		Tdindex	= 0x30,
+		Tdmin	= 0x40,
+		Tdmax	= 0x50,
+		Tsindex	= 0x70,
+		Tsmin	= 0x80,
+		Tsmax	= 0x90,
+		Tsetdelim = 0xA0,
+	 Treserved	= 3<<2,
+	 Tlong	= 0xFE,
+
 };
 };
 
 
-enum
+/*
+ * Usb device (when used for ep0s) or endpoint.
+ * RC: One ref because of existing, another one per ogoing I/O.
+ * per-driver resources (including FS if any) are released by aux
+ * once the last ref is gone. This may include other Devs using
+ * to access endpoints for actual I/O.
+ */
+struct Dev
+{
+	Ref;
+	char*	dir;		/* path for the endpoint dir */
+	int	id;		/* usb id for device or ep. number */
+	int	dfd;		/* descriptor for the data file */
+	int	cfd;		/* descriptor for the control file */
+	int	maxpkt;		/* cached from usb description */
+	Ref	nerrs;		/* number of errors in requests */
+	Usbdev*	usb;		/* USB description */
+	void*	aux;		/* for the device driver */
+	void	(*free)(void*);	/* idem. to release aux */
+};
+
+/*
+ * device description as reported by USB (unpacked).
+ */
+struct Usbdev
 {
 {
-	CL_AUDIO = 1,
-	CL_COMMS = 2,
-	CL_HID = 3,
-	CL_PRINTER = 7,
-	CL_STORAGE = 8,
-	CL_HUB = 9,
-	CL_DATA = 10,
+	ulong	csp;		/* USB class/subclass/proto */
+	int	vid;		/* vendor id */
+	int	did;		/* product (device) id */
+	char*	vendor;
+	char*	product;
+	char*	serial;
+	int	vsid;
+	int	psid;
+	int	ssid;
+	int	class;		/* from descriptor */
+	int	nconf;		/* from descriptor */
+	Conf*	conf[Nconf];	/* configurations */
+	Ep*	ep[Nep];	/* all endpoints in device */
+	Desc*	ddesc[Nddesc];	/* (raw) device specific descriptors */
 };
 };
 
 
-struct Endpt
+struct Ep
 {
 {
-	uchar	addr;		/* endpoint address, 0-15 (|0x80 if direction==Ein) */
+	uchar	addr;		/* endpt address, 0-15 (|0x80 if Ein) */
 	uchar	dir;		/* direction, Ein/Eout */
 	uchar	dir;		/* direction, Ein/Eout */
 	uchar	type;		/* Econtrol, Eiso, Ebulk, Eintr */
 	uchar	type;		/* Econtrol, Eiso, Ebulk, Eintr */
-	uchar	isotype;	/* Eunknown, Easync, Eadapt, Esync */
+	uchar	isotype;		/* Eunknown, Easync, Eadapt, Esync */
 	int	id;
 	int	id;
-	int	class;
-	ulong	csp;
-	int	maxpkt;
-	Device*	dev;
-	Dconf*	conf;
-	Dinf*	iface;
+	int	maxpkt;		/* max. packet size */
+	int	ntds;		/* nb. of Tds per µframe */
+	Conf*	conf;		/* the endpoint belongs to */
+	Iface*	iface;		/* the endpoint belongs to */
 };
 };
 
 
-struct Dalt
+struct Altc
 {
 {
 	int	attrib;
 	int	attrib;
 	int	interval;
 	int	interval;
-	void*	devspec;	/* device specific settings */
+	void*	aux;		/* for the driver program */
 };
 };
 
 
-struct Dinf
+struct Iface
 {
 {
-	int 	interface;	/* interface number */
+	int 	id;		/* interface number */
 	ulong	csp;		/* USB class/subclass/proto */
 	ulong	csp;		/* USB class/subclass/proto */
-	Dalt*	dalt[16];
-	Endpt*	endpt[16];
+	Altc*	altc[Naltc];
+	Ep*	ep[Nep];
+	void*	aux;		/* for the driver program */
 };
 };
 
 
-struct Dconf
+struct Conf
 {
 {
-	ulong	csp;		/* USB class/subclass/proto */
-	int	nif;		/* number of interfaces */
 	int	cval;		/* value for set configuration */
 	int	cval;		/* value for set configuration */
 	int	attrib;
 	int	attrib;
-	int	milliamps;	/* maximum power in this configuration */
-	Dinf*	iface[16];	/* up to 16 interfaces */
+	int	milliamps;	/* maximum power in this config. */
+	Iface*	iface[Niface];	/* up to 16 interfaces */
 };
 };
 
 
-/* Dconf.attrib */
-enum
+/*
+ * Device-specific descriptors.
+ * They show up mixed with other descriptors
+ * within a configuration.
+ * These are unknown to the library but handed to the driver.
+ */
+struct Desc
 {
 {
-	Cbuspowered = 1<<7,
-	Cselfpowered = 1<<6,
-	Cremotewakeup = 1<<5,
+	Conf*	conf;		/* where this descriptor was read */
+	Iface*	iface;		/* last iface before desc in conf. */
+	Ep*	ep;		/* last endpt before desc in conf. */
+	Altc*	altc;		/* last alt.c. before desc in conf. */
+	DDesc	data;		/* unparsed standard USB descriptor */
 };
 };
 
 
-struct Device
+struct DDesc
 {
 {
-	Ref;
-	int	ctlrno;
-	int	ctl;		/* fd */
-	int	setup;		/* fd */
-	int	status;		/* fd */
-	int	state;
-	int	id;
-	int	class;
-	int	npt;
-	int	ls;		/* low speed */
-	ulong	csp;		/* USB class/subclass/proto */
-	int	nconf;
-	int	nif;		/* # of interfaces (sum of per-conf `nif's) */
-	int	vid;		/* vendor id */
-	int	did;		/* product (device) id */
-	Dconf*	config[16];
-	Endpt*	ep[Nendpt];
-	Device*	setupfd;	/* for usbprobe */
-	Device*	cfd;		/* for usbprobe */
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bbytes[1];
+	/* extra bytes allocated here to keep the rest of it */
 };
 };
 
 
 /*
 /*
  * layout of standard descriptor types
  * layout of standard descriptor types
  */
  */
-struct DDevice
-{
-	byte	bLength;
-	byte	bDescriptorType;
-	byte	bcdUSB[2];
-	byte	bDeviceClass;
-	byte	bDeviceSubClass;
-	byte	bDeviceProtocol;
-	byte	bMaxPacketSize0;
-	byte	idVendor[2];
-	byte	idProduct[2];
-	byte	bcdDevice[2];
-	byte	iManufacturer;
-	byte	iProduct;
-	byte	iSerialNumber;
-	byte	bNumConfigurations;
-};
-#define	DDEVLEN	18
-
-struct DConfig
-{
-	byte	bLength;
-	byte	bDescriptorType;
-	byte	wTotalLength[2];
-	byte	bNumInterfaces;
-	byte	bConfigurationValue;
-	byte	iConfiguration;
-	byte	bmAttributes;
-	byte	MaxPower;
-};
-#define	DCONFLEN	9
-
-struct DInterface
-{
-	byte	bLength;
-	byte	bDescriptorType;
-	byte	bInterfaceNumber;
-	byte	bAlternateSetting;
-	byte	bNumEndpoints;
-	byte	bInterfaceClass;
-	byte	bInterfaceSubClass;
-	byte	bInterfaceProtocol;
-	byte	iInterface;
-};
-#define	DINTERLEN	9
-
-struct DEndpoint
+struct DDev
 {
 {
-	byte	bLength;
-	byte	bDescriptorType;
-	byte	bEndpointAddress;
-	byte	bmAttributes;
-	byte	wMaxPacketSize[2];
-	byte	bInterval;
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bcdUSB[2];
+	uchar	bDevClass;
+	uchar	bDevSubClass;
+	uchar	bDevProtocol;
+	uchar	bMaxPacketSize0;
+	uchar	idVendor[2];
+	uchar	idProduct[2];
+	uchar	bcdDev[2];
+	uchar	iManufacturer;
+	uchar	iProduct;
+	uchar	iSerialNumber;
+	uchar	bNumConfigurations;
 };
 };
-#define	DENDPLEN	7
 
 
-struct DHid
+struct DConf
 {
 {
-	byte	bLength;
-	byte	bDescriptorType;
-	byte	bcdHID[2];
-	byte	bCountryCode;
-	byte	bNumDescriptors;
-	byte	bClassDescriptorType;
-	byte	wItemLength[2];
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	wTotalLength[2];
+	uchar	bNumInterfaces;
+	uchar	bConfigurationValue;
+	uchar	iConfiguration;
+	uchar	bmAttributes;
+	uchar	MaxPower;
 };
 };
-#define	DHIDLEN	9
 
 
-struct DHub
+struct DIface
 {
 {
-	byte	bLength;
-	byte	bDescriptorType;
-	byte	bNbrPorts;
-	byte	wHubCharacteristics[2];
-	byte	bPwrOn2PwrGood;
-	byte	bHubContrCurrent;
-	byte	DeviceRemovable[1];	/* variable length */
-/*	byte	PortPwrCtrlMask;		/* variable length, deprecated in USB v1.1 */
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bInterfaceNumber;
+	uchar	bAlternateSetting;
+	uchar	bNumEndpoints;
+	uchar	bInterfaceClass;
+	uchar	bInterfaceSubClass;
+	uchar	bInterfaceProtocol;
+	uchar	iInterface;
 };
 };
-#define	DHUBLEN	9
 
 
-struct Namelist
+struct DEp
 {
 {
-	short	index;
-	char		*name;
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bEndpointAddress;
+	uchar	bmAttributes;
+	uchar	wMaxPacketSize[2];
+	uchar	bInterval;
 };
 };
 
 
-typedef struct Drivetab
-{
-	ulong	csp;
-	void	(*driver)(Device *d);
-} Drivetab;
-
 #define Class(csp)	((csp)&0xff)
 #define Class(csp)	((csp)&0xff)
 #define Subclass(csp)	(((csp)>>8)&0xff)
 #define Subclass(csp)	(((csp)>>8)&0xff)
 #define Proto(csp)	(((csp)>>16)&0xff)
 #define Proto(csp)	(((csp)>>16)&0xff)
 #define CSP(c, s, p)	((c) | ((s)<<8) | ((p)<<16))
 #define CSP(c, s, p)	((c) | ((s)<<8) | ((p)<<16))
+#define	GET2(p)		((((p)[1]&0xFF)<<8)|((p)[0]&0xFF))
+#define	PUT2(p,v)	{((p)[0] = (v)); ((p)[1] = (v)>>8);}
+#define	GET4(p)		((((p)[3]&0xFF)<<24)|(((p)[2]&0xFF)<<16)|(((p)[1]&0xFF)<<8)|((p)[0]&0xFF))
+#define	PUT4(p,v)	{((p)[0] = (v)); ((p)[1] = (v)>>8); ((p)[2] = (v)>>16); ((p)[3] = (v)>>24);}
+#define dprint if(usbdebug)fprint
+#define ddprint if(usbdebug > 1)fprint
+
+
+# 	|c/f2p *.c |sort +1
+
+#pragma	varargck	type  "U"	Dev*
+#pragma	varargck	argpos	devctl	2
+
+int	Ufmt(Fmt *f);
+char*	classname(int c);
+void	closedev(Dev *d);
+int	configdev(Dev *d);
+int	devctl(Dev *dev, char *fmt, ...);
+void*	emallocz(ulong size, int zero);
+char*	estrdup(char *s);
+int	matchdevcsp(char *info, void *a);
+int	finddevs(int (*matchf)(char*,void*), void *farg, char** dirs, int ndirs);
+char*	hexstr(void *a, int n);
+int	loaddevconf(Dev *d, int n);
+int	loaddevdesc(Dev *d);
+char*	loaddevstr(Dev *d, int sid);
+Dev*	opendev(char *fn);
+int	opendevdata(Dev *d, int mode);
+Dev*	openep(Dev *d, int id);
+int	parseconf(Usbdev *d, Conf *c, uchar *b, int n);
+int	parsedesc(Usbdev *d, Conf *c, uchar *b, int n);
+int	parsedev(Dev *xd, uchar *b, int n);
+void	startdevs(char *args, char *argv[], int argc, int (*mf)(char*,void*), void*ma, int (*df)(Dev*,int,char**));
+int	unstall(Dev *dev, Dev *ep, int dir);
+int	usbcmd(Dev *d, int type, int req, int value, int index, uchar *data, int count);
+
+
+extern int usbdebug;	/* more messages for bigger values */
 
 
-extern void (*dprinter[0x100])(Device *, int, ulong, void *b, int n);
 
 
-/*
- * format routines
- */
-void	pdesc	(Device *, int, ulong, byte *, int);
-void	preport	(Device *, int, ulong, byte *, int);
-void	pstring	(Device *, int, ulong, void *, int);
-void	phub	(Device *, int, ulong, void *, int);
-void	pdevice	(Device *, int, ulong, void *, int);
-void	phid	(Device *, int, ulong, void *, int);
-void	pcs_raw(char *tag, byte *b, int n);
-
-/*
- * interface
- */
-void	usbfmtinit(void);
-Device*	opendev(int, int);
-void	closedev(Device*);
-int	describedevice(Device*);
-int	loadconfig(Device *d, int n);
-Endpt *	newendpt(Device *d, int id, ulong csp);
-int	setupcmd(Endpt*, int, int, int, int, byte*, int);
-int	setupreq(Endpt*, int, int, int, int, int);
-int	setupreply(Endpt*, void*, int);
-void	setdevclass(Device *d, int n);
-void *	emalloc(ulong);
-void *	emallocz(ulong, int);
-
-char *	namefor(Namelist *, int);
-
-#pragma	varargck	type  "D"	Device*

+ 61 - 0
sys/src/cmd/usb/lib/usbfs.h

@@ -0,0 +1,61 @@
+typedef struct Usbfs Usbfs;
+typedef struct Fid Fid;
+
+enum
+{
+	Hdrsize	= 128,		/* plenty of room for headers */
+	Msgsize	= 8 * 1024,
+	Bufsize	= Hdrsize + Msgsize,
+	Namesz = 40,
+	Errmax = 128,
+	ONONE = ~0,		/* omode in Fid when not open */
+};
+
+struct Fid
+{
+	int	fid;
+	Qid	qid;
+	int	omode;
+	Fid*	next;
+	void*	aux;
+};
+
+struct Usbfs
+{
+	char	name[Namesz];
+	uvlong	qid;
+	Dev*	dev;
+	void*	aux;
+
+	int	(*walk)(Usbfs *fs, Fid *f, char *name);
+	void	(*clone)(Usbfs *fs, Fid *of, Fid *nf);
+	void	(*clunk)(Usbfs *fs, Fid *f);
+	int	(*open)(Usbfs *fs, Fid *f, int mode);
+	long	(*read)(Usbfs *fs, Fid *f, void *data, long count, vlong offset);
+	long	(*write)(Usbfs *fs, Fid*f, void *data, long count, vlong offset);
+	int	(*stat)(Usbfs *fs, Qid q, Dir *d);
+	void	(*end)(Usbfs *fs);
+};
+
+typedef int (*Dirgen)(Usbfs*, Qid, int, Dir*, void*);
+
+long	usbreadbuf(void *data, long count, vlong offset, void *buf, long n);
+void	usbfsadd(Usbfs *dfs);
+void	usbfsdel(Usbfs *dfs);
+int	usbdirread(Usbfs*f, Qid q, char *data, long cnt, vlong off, Dirgen gen, void *arg);
+void	usbfsinit(char* srv, char *mnt, Usbfs *f, int flag);
+
+void	usbfsdirdump(void);
+
+extern char Enotfound[];
+extern char Etoosmall[];
+extern char Eio[];
+extern char Eperm[];
+extern char Ebadcall[];
+extern char Ebadfid[];
+extern char Einuse[];
+extern char Eisopen[];
+extern char Ebadctl[];
+
+extern Usbfs usbdirfs;
+extern int usbfsdebug;

+ 0 - 41
sys/src/cmd/usb/lib/util.c

@@ -1,41 +0,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include "usb.h"
-
-int debug, debugdebug, usbdebug, verbose;
-
-char *
-namefor(Namelist *list, int item)
-{
-	while (list->name){
-		if (list->index == item)
-			return list->name;
-		list++;
-	}
-	return "<unnamed>";
-}
-
-void *
-emalloc(ulong size)
-{
-	void *x;
-
-	x = malloc(size);
-	if (x == nil)
-		sysfatal("maloc: %r");
-	return x;
-}
-
-void *
-emallocz(ulong size, int zero)
-{
-	void *x;
-
-	x = malloc(size);
-	if (x == nil)
-		sysfatal("maloc: %r");
-	if (zero)
-		memset(x, 0, size);
-	return x;
-}

+ 0 - 32
sys/src/cmd/usb/misc/mkfile

@@ -1,32 +0,0 @@
-</$objtype/mkfile
-
-TARG=\
-	usbmouse\
-#	readir\
-
-HFILES=\
-	../lib/usb.h\
-
-LIB=../lib/usb.a$O
-
-UPDATE=\
-	$HFILES\
-	${OFILES:%.$O=%.c}\
-	mkfile\
-	print\
-	probe\
-
-BIN=/$objtype/bin/usb
-</sys/src/cmd/mkmany
-
-CFLAGS=-I../lib $CFLAGS
-
-$LIB:
-	cd ../lib
-	mk install
-	mk clean
-
-$BIN/print: print
-	cp -x $newprereq $target
-$BIN/probe: probe
-	cp -x $newprereq $target

+ 0 - 12
sys/src/cmd/usb/misc/print

@@ -1,12 +0,0 @@
-#!/bin/rc
-# usbprint - bind usb printer endpoint to /dev/lp
-rfork e
-for (id in /dev/usb[0-9]*/[0-9]*)
-	if (grep -s 'Enabled 0x020107' $id/status >[2]/dev/null){
-		echo -n 'ep 2 bulk w 64 32' >$id/ctl
-		aux/stub /dev/lp
-		bind $id/ep2data /dev/lp
-		exit ''
-	}
-echo $0: no usb printer found >[1=2]
-exit 'no printer'

+ 0 - 12
sys/src/cmd/usb/misc/probe

@@ -1,12 +0,0 @@
-#!/bin/rc
-# list all usb devices
-rfork e
-if(! test -r '#U'/usb0)
-	exit no-usb
-if(! test -r /dev/usb0)
-	bind -a '#U' /dev
-for (id in /dev/usb[0-9]*/[0-9]*/status)
-	if (test -e $id) {
-		echo $id | sed 's;/status$;:	;' | tr -d '\12'
-		grep '^[A-Z]' $id
-	}

+ 0 - 273
sys/src/cmd/usb/misc/usbmouse.c

@@ -1,273 +0,0 @@
-/*
- * usbmouse - listen for usb mouse events and turn them into
- *	writes on /dev/mousein.
- */
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <thread.h>
-#include "usb.h"
-
-typedef struct {
-	int	epno;
-	int	maxpkt;
-	int	pollms;
-} Mouseinfo;
-
-void (*dprinter[])(Device *, int, ulong, void *b, int n) = {
-	[STRING] pstring,
-	[DEVICE] pdevice,
-	[HID] phid,
-};
-
-int mousefd, ctlfd, mousein;
-
-char hbm[]		= "0x020103";
-char *mouseinfile	= "/dev/mousein";
-
-char *statfmt		= "/dev/usb%d/%d/status";
-char *ctlfmt		= "/dev/usb%d/%d/ctl";
-char *msefmt		= "/dev/usb%d/%d/ep%ddata";
-
-char *ctlmsgfmt		= "ep %d %d r %d";
-
-char ctlfile[32];
-char msefile[32];
-
-int verbose;
-int nofork;
-int debug;
-
-int accel;
-int scroll;
-int maxacc = 3;
-int nbuts;
-
-void work(void *);
-
-int
-findendpoint(int ctlr, int id, Mouseinfo *mp)
-{
-	int i;
-	Device *d;
-	Endpt *ep;
-
-	d = opendev(ctlr, id);
-	d->config[0] = emallocz(sizeof(*d->config[0]), 1);
-	if (describedevice(d) < 0 || loadconfig(d, 0) < 0) {
-		closedev(d);
-		return -1;
-	}
-	for (i = 1; i < Nendpt; i++) {
-		if ((ep = d->ep[i]) == nil)
-			continue;
-		if (ep->csp == 0x020103 && ep->type == Eintr && ep->dir != Eout) {
-			if (ep->iface == nil || ep->iface->dalt[0] == nil)
-				continue;
-			mp->epno = i;
-			mp->maxpkt = ep->maxpkt;
-			mp->pollms = ep->iface->dalt[0]->interval;
-			closedev(d);
-			return 0;
-		}
-	}
-	closedev(d);
-	return -1;
-}
-
-int
-robusthandler(void*, char *s)
-{
-	if (debug)
-		fprint(2, "inthandler: %s\n", s);
-	return s && (strstr(s, "interrupted") || strstr(s, "hangup"));
-}
-
-long
-robustread(int fd, void *buf, long sz)
-{
-	long r;
-	char err[ERRMAX];
-
-	do {
-		r = read(fd , buf, sz);
-		if (r < 0)
-			rerrstr(err, sizeof(err));
-	} while (r < 0 && robusthandler(nil, err));
-	return r;
-}
-
-static int
-scale(int x)
-{
-	int sign = 1;
-
-	if(x < 0){
-		sign = -1;
-		x = -x;
-	}
-	switch(x){
-	case 0:
-	case 1:
-	case 2:
-	case 3:
-		break;
-	case 4:
-		x = 6 + (accel>>2);
-		break;
-	case 5:
-		x = 9 + (accel>>1);
-		break;
-	default:
-		x *= maxacc;
-		break;
-	}
-	return sign*x;
-}
-
-char maptab[] = {
-	0x0, 0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7
-};
-
-void
-usage(void)
-{
-	fprint(2, "usage: %s [-dfsv] [-a accel] [ctlrno usbport]\n", argv0);
-	threadexitsall("usage");
-}
-
-void
-threadmain(int argc, char *argv[])
-{
-	int ctlrno, i;
-	char *p;
-	char buf[256];
-	Biobuf *f;
-	Mouseinfo mouse;
-
-	ARGBEGIN{
-	case 'a':
-		accel = strtol(EARGF(usage()), nil, 0);
-		break;
-	case 'd':
-		debug = usbdebug = 1;
-		break;
-	case 'f':
-		nofork = 1;
-		break;
-	case 's':
-		scroll = 1;
-		break;
-	case 'v':
-		verbose = 1;
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	memset(&mouse, 0, sizeof mouse);
-	f = nil;
-	switch (argc) {
-	case 0:
-		for (ctlrno = 0; ctlrno < 16; ctlrno++) {
-			sprint(buf, "/dev/usb%d", ctlrno);
-			if (access(buf, AEXIST) < 0)
-				continue;
-			for (i = 1; i < 128; i++) {
-				snprint(buf, sizeof buf, statfmt, ctlrno, i);
-				f = Bopen(buf, OREAD);
-				if (f == nil)
-					break;
-				while ((p = Brdline(f, '\n')) != 0) {
-					p[Blinelen(f)-1] = '\0';
-					if (strncmp(p, "Enabled ", 8) == 0)
-						continue;
-					if (strstr(p, hbm) != nil) {
-						Bterm(f);
-						goto found;
-					}
-				}
-				Bterm(f);
-			}
-		}
-		threadexitsall("no mouse");
-	case 2:
-		ctlrno = atoi(argv[0]);
-		i = atoi(argv[1]);
-found:
-		if(findendpoint(ctlrno, i, &mouse) < 0) {
-			fprint(2, "%s: invalid usb device configuration\n",
-				argv0);
-			threadexitsall("no mouse");
-		}
-		snprint(ctlfile, sizeof ctlfile, ctlfmt, ctlrno, i);
-		snprint(msefile, sizeof msefile, msefmt, ctlrno, i, mouse.epno);
-		break;
-	default:
-		usage();
-	}
-	if (f)
-		Bterm(f);
-
-	nbuts = (scroll? 5: 3);
-	if (nbuts > mouse.maxpkt)
-		nbuts = mouse.maxpkt;
-	if ((ctlfd = open(ctlfile, OWRITE)) < 0)
-		sysfatal("%s: %r", ctlfile);
-	if (verbose)
-		fprint(2, "Send mouse.ep %d %d r %d to %s\n",
-			mouse.epno, mouse.pollms, mouse.maxpkt, ctlfile);
-	fprint(ctlfd, ctlmsgfmt, mouse.epno, mouse.pollms, mouse.maxpkt);
-	close(ctlfd);
-
-	if ((mousefd = open(msefile, OREAD)) < 0)
-		sysfatal("%s: %r", msefile);
-	if (verbose)
-		fprint(2, "Start reading from %s\n", msefile);
-	if ((mousein = open(mouseinfile, OWRITE)) < 0)
-		sysfatal("%s: %r", mouseinfile);
-	atnotify(robusthandler, 1);
-	if (nofork)
-		work(nil);
-	else
-		proccreate(work, nil, 4*1024);
-	threadexits(nil);
-}
-
-void
-work(void *)
-{
-	char buf[6];
-	int x, y, buts;
-
-	for (;;) {
-		buts = 0;
-		switch (robustread(mousefd, buf, nbuts)) {
-		case 4:
-			if(buf[3] == 1)
-				buts |= 0x08;
-			else if(buf[3] == -1)
-				buts |= 0x10;
-			/* Fall through */
-		case 5:
-			if(buf[3] > 10)
-				buts |= 0x08;
-			else if(buf[3] < -10)
-				buts |= 0x10;
-			/* Fall through */
-		case 3:
-			if (accel) {
-				x = scale(buf[1]);
-				y = scale(buf[2]);
-			} else {
-				x = buf[1];
-				y = buf[2];
-			}
-			fprint(mousein, "m%11d %11d %11d",
-				x, y, buts | maptab[buf[0]&0x7]);
-			break;
-		case -1:
-			sysfatal("read error: %r");
-		}
-	}
-}

+ 15 - 3
sys/src/cmd/usb/mkfile

@@ -2,23 +2,35 @@
 
 
 DIRS=\
 DIRS=\
 	lib\
 	lib\
-	audio\
 	disk\
 	disk\
 	kb\
 	kb\
-	misc\
+	audio\
 	usbd\
 	usbd\
+	ether\
+	print\
+#	knx\
 
 
 UPDATE=\
 UPDATE=\
 	mkfile\
 	mkfile\
 
 
+default:V: all
+
 none:VQ:
 none:VQ:
 	echo mk all, install, installall, clean, nuke, or update
 	echo mk all, install, installall, clean, nuke, or update
 
 
-all install installall clean nuke:VQ:
+all clean nuke:VQ:
+	for (i in $DIRS) @{
+		cd $i
+		mk $target
+	}
+
+install installall:V:
 	for (i in $DIRS) @{
 	for (i in $DIRS) @{
 		cd $i
 		cd $i
 		mk $target
 		mk $target
 	}
 	}
+	cp usbfat: /rc/bin
+	cp probe /$objtype/bin/usb/probe
 
 
 update:V:
 update:V:
 	update $UPDATEFLAGS $UPDATE
 	update $UPDATEFLAGS $UPDATE

+ 47 - 0
sys/src/cmd/usb/print/main.c

@@ -0,0 +1,47 @@
+/*
+ * usb/print - usb printer
+ */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+
+enum
+{
+	Arglen = 80,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-d] [dev...]\n", argv0);
+	threadexitsall("usage");
+}
+
+static int csps[] = { 0x020107, 0 };
+
+extern int printmain(Dev*, int, char**);
+
+void
+threadmain(int argc, char **argv)
+{
+	char args[Arglen];
+	char *ae;
+
+	quotefmtinstall();
+	ae = args+sizeof(args);
+	seprint(args, ae, "print");
+	ARGBEGIN{
+	case 'd':
+		usbdebug++;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	rfork(RFNOTEG);
+	threadsetgrp(threadid());
+	fmtinstall('U', Ufmt);
+	startdevs(args, argv, argc, matchdevcsp, csps, printmain);
+	threadexits(nil);
+}

+ 22 - 0
sys/src/cmd/usb/print/mkfile

@@ -0,0 +1,22 @@
+</$objtype/mkfile
+
+TARG=print
+LIB=../lib/usb.a$O
+
+HFILES=\
+	../lib/usb.h\
+	../lib/usbfs.h\
+
+OFILES=\
+	main.$O\
+	print.$O\
+
+BIN=/$objtype/bin/usb
+</sys/src/cmd/mkone
+CFLAGS=-I../lib $CFLAGS
+
+$LIB:
+	cd ../lib
+	mk install
+	mk clean
+

+ 86 - 0
sys/src/cmd/usb/print/print.c

@@ -0,0 +1,86 @@
+/*
+ * usb/print - usb printer file server
+ * BUG: Assumes the printer will be always connected and
+ * not hot-plugged. (Otherwise should stay running and
+ * listen to errors to keep the device there as long as it has
+ * not failed). Also, this is untested and done ad-hoc to
+ * replace the print script.
+ */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+
+enum
+{
+	Qdir = 0,
+	Qctl,
+	Qraw,
+	Qdata,
+	Qmax,
+};
+
+int
+findendpoints(Dev *dev)
+{
+	Ep *ep;
+	Dev *d;
+	Usbdev *ud;
+	int i;
+	int epout;
+
+	epout = -1;
+	ud = dev->usb;
+	for(i = 0; i < nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			break;
+		if(ep->iface->csp != 0x020107)
+			continue;
+		if(ep->type == Ebulk && (ep->dir == Eboth || ep->dir == Eout))
+			if(epout == -1)
+				epout = ep->id;
+	}
+	dprint(2, "print: ep ids: out %d\n", epout);
+	if(epout == -1)
+		return -1;
+	d = openep(dev, epout);
+	if(d == nil){
+		fprint(2, "print: openep %d: %r\n", epout);
+		return -1;
+	}
+	opendevdata(d, OWRITE);
+	if(d->dfd < 0){
+		fprint(2, "print: open i/o ep data: %r\n");
+		closedev(d);
+		return -1;
+	}
+	dprint(2, "print: ep out %s\n", d->dir);
+	if(usbdebug > 1)
+		devctl(d, "debug 1");
+	devctl(d, "name lp%d", dev->id);
+	return 0;
+}
+
+static int
+usage(void)
+{
+	werrstr("usage: usb/print");
+	return -1;
+}
+
+int
+printmain(Dev *dev, int argc, char **argv)
+{
+	ARGBEGIN{
+	default:
+		return usage();
+	}ARGEND
+	if(argc != 0)
+		return usage();
+
+	if(findendpoints(dev) < 0){
+		werrstr("print: endpoints not found");
+		return -1;
+	}
+	return 0;
+}

+ 21 - 0
sys/src/cmd/usb/probe

@@ -0,0 +1,21 @@
+#!/bin/rc
+rfork e
+test -e /dev/usb || bind -a '#u' /dev || {
+	echo no '#u/usb' >[1=2]
+	exit nousb
+}
+
+awk 'BEGIN{ep="";}
+	$1 ~ /ep[0-9]+\.0/ && $2 == "enabled" && $NF ~ /busy|idle/ {
+		ep=$1;
+		next;
+	}
+	{
+		if(ep != ""){
+			printf("%s %s\n", ep, $0);
+			ep="";
+		}
+	}
+' /dev/usb/ctl
+
+exit ''

+ 0 - 30
sys/src/cmd/usb/usbd/dat.h

@@ -1,30 +0,0 @@
-typedef struct Hub Hub;
-typedef struct Port Port;
-
-struct Hub
-{
-	byte		pwrmode;
-	byte		compound;
-	byte		pwrms;		/* time to wait in ms after powering port */
-	byte		maxcurrent;
-	byte		nport;
-	Port		*port;
-
-	int		isroot;		/* set if this hub is a root hub */
-	int		portfd;		/* fd of /dev/usb%d/port if root hub */
-	int		ctlrno;		/* number of controller this hub is on */
-	Device*	dev0;		/* device 0 of controller */
-	Device*	d;			/* device of hub (same as dev0 for root hub) */
-};
-
-struct Port
-{
-	byte		removable;
-	byte		pwrctl;
-	Device	*d;			/* attached device (if non-nil) */
-	Hub		*hub;		/* non-nil if hub attached */
-};
-
-#pragma	varargck	type  "H"	Hub*
-
-extern int verbose;

+ 252 - 0
sys/src/cmd/usb/usbd/dev.c

@@ -0,0 +1,252 @@
+/*
+ * Framework for USB devices.
+ * Some of them may be embedded into usbd and some of
+ * them may exist as /bin/usb/* binaries on their own.
+ *
+ * When embedded, devmain() is given a ref of an already
+ * configured and open Dev. If devmain()
+ * does not fail it should release this ref when done and
+ * use incref to add further refs to it.
+ */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbd.h"
+
+extern Devtab devtab[];
+static char* cputype;
+
+static int
+cspmatch(Devtab *dt, int dcsp)
+{
+	int	i;
+	int	csp;
+
+	for(i = 0; i < nelem(dt->csps); i++)
+		if((csp=dt->csps[i]) != 0)
+		if(csp == dcsp)
+			return 1;
+		else if((csp&DCL) && (csp&~DCL) == Class(dcsp))
+			return 1;
+	return 0;
+}
+
+static int
+devmatch(Devtab *dt, Usbdev *d)
+{
+	int i;
+	int c;
+	Conf *cp;
+
+	if(dt->vid != -1 && d->vid != dt->vid)
+		return 0;
+	if(dt->did != -1 && d->did != dt->did)
+		return 0;
+	if(cspmatch(dt, d->csp))
+		return 1;
+	for(c = 0; c < Nconf; c++)
+		if((cp=d->conf[c]) != nil)
+			for(i = 0; i < Niface; i++)
+				if(cp->iface[i] != nil)
+					if(cspmatch(dt, cp->iface[i]->csp))
+						return 1;
+	return 0;
+}
+
+/* We can't use procexec to execute drivers, because
+ * procexec mounts #| at /mnt/temp and we do *not*
+ * have /mnt/temp at boot time.
+ * Instead, we use access to guess if we can execute the file.
+ * and reply as procexec. Be careful that the child inherits
+ * all the shared state of the thread library. It should run unnoticed.
+ */
+static void
+xexec(Channel *c, char *nm, char *args[])
+{
+	int	pid;
+
+	if(access(nm, AEXEC) == 0){
+		pid = rfork(RFFDG|RFREND|RFPROC);
+		switch(pid){
+		case 0:
+			exec(nm, args);
+			_exits("exec");
+		case -1:
+			break;
+		default:
+			sendul(c, pid);
+			threadexits(nil);
+		}
+	}
+}
+
+typedef struct Sarg Sarg;
+struct Sarg{
+	Port *pp;
+	Devtab* dt;
+	Channel*rc;
+	char fname[80];
+	char	args[128];
+	char	*argv[40];
+};
+
+static void
+startdevproc(void *a)
+{
+	Sarg	*sa = a;
+	Dev	*d;
+	Devtab *dt;
+	int	argc;
+	char *args, **argv;
+	char *fname;
+
+	d = sa->pp->dev;
+	dt = sa->dt;
+	args = sa->args;
+	argv = sa->argv;
+	fname = sa->fname;
+
+	threadsetgrp(threadid());
+	if(dt->args != nil)
+		strncpy(args, dt->args, sizeof(sa->args));
+	else
+		args[0] = 0;
+	dprint(2, "%s: start: %s %s\n", argv0, dt->name, args);
+	argv[0] = dt->name;
+	argc = 1;
+	if(args[0] != 0)
+		argc += tokenize(args, argv+1, nelem(sa->argv)-2);
+	argv[argc] = nil;
+	if(dt->init == nil){
+		if(d->dfd > 0 ){
+			close(d->dfd);
+			d->dfd = -1;
+		}
+		rfork(RFCFDG);
+		open("/dev/null", OREAD);
+		open("/dev/cons", OWRITE);
+		open("/dev/cons", OWRITE);
+
+		xexec(sa->rc, argv[0], argv);
+		snprint(fname, sizeof(sa->fname), "/bin/usb/%s", dt->name);
+		xexec(sa->rc, fname, argv);
+		snprint(fname, sizeof(sa->fname), "/boot/%s", dt->name);
+		xexec(sa->rc, fname, argv);
+		if(cputype == nil)
+			cputype = getenv("cputype");
+		if(cputype != nil){
+			snprint(fname, sizeof(sa->fname), "/%s/bin/%s",
+				cputype, dt->name);
+			argv[0] = fname;
+			xexec(sa->rc, fname, argv);
+		}
+		fprint(2, "%s: %s: not found. can't exec\n", argv0, dt->name);
+		sendul(sa->rc, -1);
+		threadexits("exec");
+	}else {
+		sa->pp->dev = opendev(d->dir);
+		sendul(sa->rc, 0);
+		if(dt->init(d, argc, argv) < 0){
+			fprint(2, "%s: %s: %r\n", argv0, dt->name);
+			closedev(d);
+		}
+		free(sa);
+	}
+	threadexits(nil);
+}
+
+static void
+writeinfo(Dev *d)
+{
+	char buf[128];
+	char *s;
+	char *se;
+	Usbdev *ud;
+	Conf *c;
+	Iface *ifc;
+	int i, j;
+
+	ud = d->usb;
+	s = buf;
+	se = buf+sizeof(buf);
+	s = seprint(s, se, "info %s csp %#08ulx", classname(ud->class), ud->csp);
+	for(i = 0; i < ud->nconf; i++){
+		c = ud->conf[i];
+		if(c == nil)
+			break;
+		for(j = 0; j < nelem(c->iface); j++){
+			ifc = c->iface[j];
+			if(ifc == nil)
+				break;
+			if(ifc->csp != ud->csp)
+				s = seprint(s, se, " csp %#08ulx", ifc->csp);
+		}
+	}
+	s = seprint(s, se, " vid %06#x did %06#x", ud->vid, ud->did);
+	seprint(s, se, " %q %q", ud->vendor, ud->product);
+	devctl(d, "%s", buf);
+}
+
+int
+startdev(Port *pp)
+{
+	Dev *d;
+	Usbdev *ud;
+	Devtab *dt;
+	Sarg *sa;
+	Channel *rc;
+
+	d = pp->dev;
+	assert(d);
+	ud = d->usb;
+	assert(ud != nil);
+
+	writeinfo(d);
+
+	if(ud->class == Clhub){
+		/*
+		 * Hubs are handled directly by this process avoiding
+		 * concurrent operation so that at most one device
+		 * has the config address in use.
+		 * We cancel kernel debug for these eps. too chatty.
+		 */
+		pp->hub = newhub(d->dir, d);
+		if(pp->hub == nil)
+			fprint(2, "%s: %s: %r\n", argv0, d->dir);
+		else
+			fprint(2, "usb/hub... ");
+		if(usbdebug > 1)
+			devctl(d, "debug 0");	/* polled hubs are chatty */
+		return pp->hub == nil ? -1 : 0;
+	}
+
+	for(dt = devtab; dt->name != nil; dt++)
+		if(devmatch(dt, ud))
+			break;
+	/*
+	 * From here on the device is for the driver.
+	 * When we return pp->dev contains a Dev just for us
+	 * with only the ctl open. Both released on the last closedev,
+	 * driver's upon I/O errors and ours upon port dettach.
+	 */
+	if(dt->name == nil){
+		dprint(2, "%s: no configured entry for %s (csp %#08lx)\n",
+			argv0, d->dir, ud->csp);
+		close(d->dfd);
+		d->dfd = -1;
+		return 0;
+	}
+	sa = emallocz(sizeof(Sarg), 1);
+	sa->pp = pp;
+	sa->dt = dt;
+	rc = sa->rc = chancreate(sizeof(ulong), 1);
+	procrfork(startdevproc, sa, Stack, RFNOTEG);
+	if(recvul(rc) != 0)
+		free(sa);
+	chanfree(rc);
+	fprint(2, "usb/%s... ", dt->name);
+
+	sleep(Spawndelay);		/* in case we re-spawn too fast */
+	return 0;
+}

+ 0 - 21
sys/src/cmd/usb/usbd/fns.h

@@ -1,21 +0,0 @@
-/* hub.c */
-Hub* roothub(int);
-Hub* newhub(Hub*, Device*);
-void	freehub(Hub*);
-int	Hfmt(Fmt*);
-void	portenable(Hub*, int, int);
-void	portreset(Hub*, int);
-void	portpower(Hub*, int, int);
-int	portstatus(Hub*, int);
-
-/* setup.c */
-void	devspeed(Device*, int);
-void	setup0(Device*, int, int, int, int, int);
-void	setconfig(Device*, int);
-int	getmaxpkt(Device*);
-int	setaddress(Device*, int);
-
-/* usbd.c */
-void	enumerate(void *);
-Device* configure(Hub *h, int port);
-void detach(Hub *h, int port);

+ 0 - 229
sys/src/cmd/usb/usbd/hub.c

@@ -1,229 +0,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include "usb.h"
-
-#include "dat.h"
-#include "fns.h"
-
-Hub*
-roothub(int ctlrno)
-{
-	Hub *h;
-	char name[100];
-
-	h = emallocz(sizeof(Hub), 1);
-	h->isroot = 1;
-	h->ctlrno = ctlrno;
-	h->nport = 2;			/* BUG */
-	h->port = emallocz(h->nport*sizeof(Port), 1);
-
-	sprint(name, "/dev/usb%d/port", ctlrno);
-	if((h->portfd = open(name, ORDWR)) < 0){
-		werrstr("open %s: %r", name);
-		free(h);
-		return nil;
-	}
-
-	h->dev0 = opendev(ctlrno, 0);
-	h->d = h->dev0;
-	incref(h->d);
-	return h;
-}
-
-Hub*
-newhub(Hub *parent, Device *d)
-{
-	Port *p;
-	Hub *h;
-	DHub *dd;
-	byte buf[128], *PortPwrCtrlMask;
-	int nr, nport, nmap, i, offset, mask;
-
-	h = emallocz(sizeof(Hub), 1);
-	h->d = d;
-	h->ctlrno = parent->ctlrno;
-	h->dev0 = parent->dev0;
-
-	if (setupreq(d->ep[0], RD2H|Rclass|Rdevice, GET_DESCRIPTOR, HUB<<8|0,
-	    0, DHUBLEN) < 0 ||
-	   (nr = setupreply(d->ep[0], buf, sizeof(buf))) < DHUBLEN) {
-		fprint(2, "usbd: error reading hub descriptor\n");
-		free(h);
-		return nil;
-	}
-	pdesc(d, -1, -1, buf, nr);
-	dd = (DHub*)buf;
-	nport = dd->bNbrPorts;
-	nmap = 1 + nport/8;
-	if(nr < 7 + 2*nmap) {
-		fprint(2, "usbd: hub descriptor too small\n");
-		free(h);
-		return nil;
-	}
-
-	h->nport = nport;
-	h->port = emallocz(nport*sizeof(Port), 1);
-	h->pwrms = dd->bPwrOn2PwrGood*2;
-	h->maxcurrent = dd->bHubContrCurrent;
-	h->pwrmode = dd->wHubCharacteristics[0] & 3;
-	h->compound = (dd->wHubCharacteristics[0] & (1<<2))!=0;
-
-	PortPwrCtrlMask = dd->DeviceRemovable + nmap;
-	for(i = 1; i <= nport; i++) {
-		p = &h->port[i-1];
-		offset = i/8;
-		mask = 1<<(i%8);
-		p->removable = (dd->DeviceRemovable[offset] & mask) != 0;
-		p->pwrctl = (PortPwrCtrlMask[offset] & mask) != 0;
-	}
-	incref(d);
-	incref(h->dev0);
-	return h;
-}
-
-void
-freehub(Hub *h)
-{
-	int i;
-	Port *p;
-
-	if(h == nil)
-		return;
-	for(i = 1; i <= h->nport; i++) {
-		p = &h->port[i-1];
-		freehub(p->hub);
-		closedev(p->d);
-	}
-	free(h->port);
-	if(h->isroot)
-		close(h->portfd);
-	else
-		closedev(h->d);
-	closedev(h->dev0);
-	free(h);
-}
-
-int
-Hfmt(Fmt *f)
-{
-	Hub *h;
-
-	h = va_arg(f->args, Hub*);
-	return fmtprint(f, "usb%d/%d", h->ctlrno, h->d->id);
-}
-
-static void
-hubfeature(Hub *h, int port, int feature, int on)
-{
-	int cmd;
-
-	cmd = CLEAR_FEATURE;
-	if(on)
-		cmd = SET_FEATURE;
-	setup0(h->d, RH2D|Rclass|Rother, cmd, feature, port, 0);
-}
-
-void
-portenable(Hub *h, int port, int on)
-{
-	if(h->isroot){
-		if(fprint(h->portfd, "%s %d", on? "enable": "disable", port) < 0)
-			sysfatal("usbd: portenable: write error: %r");
-		return;
-	}
-	if(port == 0)
-		return;
-	hubfeature(h, port, PORT_ENABLE, on);
-}
-
-void
-portreset(Hub *h, int port)
-{
-	if(h->isroot) {
-		if(fprint(h->portfd, "reset %d", port) < 0)
-			sysfatal("usbd: portreset: write error: %r");
-		sleep(100);
-		return;
-	}
-	if(port == 0)
-		return;
-	hubfeature(h, port, PORT_RESET, 1);
-}
-
-void
-portpower(Hub *h, int port, int on)
-{
-	if(h->isroot) {
-		/* no power control */
-		return;
-	}
-	if(port == 0)
-		return;
-	hubfeature(h, port, PORT_POWER, on);
-}
-
-static struct
-{
-	int	bit;
-	char	*name;
-}
-statustab[] =
-{
-	{ 1<<PORT_SUSPEND,		"suspend", },
-	{ 1<<PORT_RESET,		"reset", },
-	{ 1<<PORT_LOW_SPEED,		"lowspeed", },
-	{ 1<<PORT_ENABLE,		"enable", },
-	{ 1<<PORT_CONNECTION,		"present", },
-};
-
-int
-portstatus(Hub *h, int port)
-{
-	int x;
-	Endpt *e;
-	byte buf[4];
-	int n, nf, i, j;
-	char *status, *q, *qe, *field[20];
-
-	if(h->isroot) {
-		seek(h->portfd, 0, 0);
-		status = malloc(8192);
-		n = read(h->portfd, status, 8192);
-		if (n <= 0)
-			sysfatal("usbd: can't read usb port status: %r");
-		status[n] = '\0';
-		q = status;
-		for(;;) {
-			qe = strchr(q, '\n');
-			if(qe == nil)
-				sysfatal("usbd: port %H.%d not found", h, port);
-			*qe = '\0';
-			nf = tokenize(q, field, nelem(field));
-			if(nf < 2)
-				sysfatal("Ill-formed port status: %s", q);
-			if(strtol(field[0], nil, 0) == port)
-				break;
-			q = qe+1;
-		}
-		x = 0;
-		for(i = 2; i < nf; i++) {
-			for(j = 0; j < nelem(statustab); j++) {
-				if(strcmp(field[i], statustab[j].name) == 0) {
-					x |= statustab[j].bit;
-					break;
-				}
-			}
-		}
-		free(status);
-		return x;
-	}
-	e = h->d->ep[0];
-	if (setupreq(e, RD2H|Rclass|Rother, GET_STATUS, 0, port, sizeof buf) < 0
-	  || setupreply(e, buf, sizeof(buf)) < sizeof(buf)) {
-		if (debug)
-			sysfatal("usbd: error reading hub status %H.%d", h, port);
-		return 0;
-	}
-	return GET2(buf);
-}

+ 113 - 0
sys/src/cmd/usb/usbd/mkdev

@@ -0,0 +1,113 @@
+#!/bin/rc
+rfork e
+
+DB=usbdb
+HDR=../lib/usb.h
+
+subs=`{	grep '^	Cl.*' $HDR | 
+		sed -e 's/.*Cl([a-z]+)[ 	]+=[ 	]+([0-9]+).*/-e s.\1,.\2,./' |
+		tr A-Z a-z
+}
+cat<<EOF
+/* machine generated. do not edit */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbd.h"
+
+EOF
+
+awk '
+collect && /^[^ \t]/{
+	collect = 0;
+}
+$0 ~ /^(embed|auto)/{
+	section = $0;
+	collect = 1;
+	next;
+}
+collect {
+	if(section ~ "embed"){
+		printf("extern int %smain(Dev*, int, char**);\n", $1);
+	}
+}
+' $DB
+cat <<EOF
+
+Devtab devtab[] = {
+	/* device, entrypoint, {csp, csp, csp csp}, vid, did */
+EOF
+awk '
+collect && /^[^ \t]/{
+	collect = 0;
+}
+$0 ~ /^(embed|auto)/{
+	section = $0;
+	collect = 1;
+	next;
+}
+collect {
+	printf("	{\"%s\"", $1);
+	if(section ~ "embed"){
+		fns[nfns++] = $1;
+		printf(",\t%smain", $1);
+	} else
+		printf(", nil");
+	printf(",\t{");
+	ncsp = 0;
+	vid="-1";
+	did="-1";
+	args="";
+	for(i = 2; i <= NF; i++)
+		if($i ~ "^args="){
+			sub("args=", "", $i);
+			for(j = i; j <= NF; j++)
+				if(j > i)
+					args = args  " " $j;
+				else
+					args = $j
+		}
+	for(i = 2; i <= NF; i++){
+		if($i ~ "^csp="){
+			ncsp++;
+			sub("csp=", "", $i);
+			printf("%s, ", $i);
+		} else
+		if($i ~ "^subclass="){
+			ncsp++;
+			sub("subclass=", "", $i);
+			printf("DSC|%s, ", $i);
+		} else
+		if($i ~ "^class="){
+			ncsp++;
+			sub("class=", "", $i);
+			printf("DCL|%s, ", $i);
+		} else
+		if($i ~ "^proto="){
+			ncsp++;
+			sub("proto=", "", $i);
+			printf("DPT|%s, ", $i);
+		} else
+		if($i ~ "^vid="){
+			sub("vid=", "", $i);
+			vid=$i
+		} else
+		if($i ~ "did="){
+			sub("did=", "", $i);
+			did=$i
+		}
+	}
+	for(i = ncsp; i < 4; i++)
+		printf("0, ");
+	printf("}, %s, %s, \"%s\"},\n", vid, did, args);
+}
+' $DB |  sed $subs
+
+cat<<EOF
+	{nil, nil,	{0, 0, 0, 0, }, -1, -1, nil},
+};
+
+/* end of machine generated */
+EOF
+

+ 15 - 6
sys/src/cmd/usb/usbd/mkfile

@@ -2,16 +2,20 @@
 
 
 TARG=usbd
 TARG=usbd
 OFILES=\
 OFILES=\
-	hub.$O\
-	setup.$O\
 	usbd.$O\
 	usbd.$O\
+	dev.$O\
+	devtab.$O\
 
 
 HFILES=\
 HFILES=\
-	dat.h\
-	fns.h\
+	usbd.h\
 	../lib/usb.h\
 	../lib/usb.h\
+	../lib/usbfs.h\
 
 
-LIB=../lib/usb.a$O
+LIBD=../lib/usbdev.a$O
+LIBU=../lib/usb.a$O
+LIB=\
+	$LIBD\
+	$LIBU\
 
 
 UPDATE=\
 UPDATE=\
 	$HFILES\
 	$HFILES\
@@ -23,8 +27,13 @@ BIN=/$objtype/bin/usb
 </sys/src/cmd/mkone
 </sys/src/cmd/mkone
 
 
 CFLAGS=-I../lib $CFLAGS
 CFLAGS=-I../lib $CFLAGS
+CLEANFILES=devtab.c
 
 
-$LIB:
+$LIBU:
 	cd ../lib
 	cd ../lib
 	mk install
 	mk install
 	mk clean
 	mk clean
+
+devtab.c: usbdb ../lib/usb.h mkdev
+	mkdev >$target
+

+ 0 - 63
sys/src/cmd/usb/usbd/setup.c

@@ -1,63 +0,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include "usb.h"
-
-#include "dat.h"
-#include "fns.h"
-
-void
-devspeed(Device *d, int ls)
-{
-	if(fprint(d->ctl, "speed %d", !ls) < 0)
-		sysfatal("devspeed: write error: %r");
-	if(debug)
-		fprint(2, "usbd: %D: set speed %s\n", d, ls?"low":"high");
-}
-
-void
-setup0(Device *d, int type, int req, int value, int index, int count)
-{
-	if(setupreq(d->ep[0], type, req, value, index, count) < 0)
-		sysfatal("usbd: setup0: %D: transaction error", d);
-}
-
-void
-setconfig(Device *d, int n)
-{
-	setup0(d, RH2D|Rstandard|Rdevice, SET_CONFIGURATION, n, 0, 0);
-	d->state = Configured;
-}
-
-int
-getmaxpkt(Device *d)
-{
-	DDevice *dd;
-	byte buf[8];
-	int nr;
-
-	werrstr("");
-	if(setupreq(d->ep[0], RD2H|Rstandard|Rdevice, GET_DESCRIPTOR,
-	    DEVICE<<8|0, 0, sizeof buf) < 0){
-		fprint(2, "usbd: getmaxpkt: error writing usb device request: "
-			"GET_DESCRIPTOR for %D: %r\n", d);
-		return -1;
-	}
-	if((nr = setupreply(d->ep[0], buf, sizeof buf)) < sizeof buf){
-		fprint(2, "usbd: getmaxpkt: error reading device descriptor "
-			"for %D, got %d of %d: %r\n", d, nr, sizeof buf);
-		return -1;
-	}
-	dd = (DDevice*)buf;
-	return dd->bMaxPacketSize0;
-}
-
-int
-setaddress(Device *d, int id)
-{
-	if(setupreq(d->ep[0], RH2D, SET_ADDRESS, id, 0, 0) < 0){
-		fprint(2, "usbd: set address %D <- %d failed\n", d, id);
-		return -1;
-	}
-	return 0;
-}

+ 725 - 278
sys/src/cmd/usb/usbd/usbd.c

@@ -1,336 +1,783 @@
 #include <u.h>
 #include <u.h>
 #include <libc.h>
 #include <libc.h>
 #include <thread.h>
 #include <thread.h>
+#include <fcall.h>
 #include "usb.h"
 #include "usb.h"
+#include "usbfs.h"
+#include "usbd.h"
 
 
-#include "dat.h"
-#include "fns.h"
+static Channel *portc;
+static int win;
+static int verbose;
 
 
-#define STACKSIZE 128*1024
+int mainstacksize = Stack;
+static Hub *hubs;
+static int nhubs;
+static int mustdump;
+static int pollms = Pollms;
 
 
-static int dontfork;
-static int usbnum;
+static char *dsname[] = { "disabled", "attached", "configed" };
 
 
-Ref	busy;
-
-int debug;
-
-typedef struct Enum Enum;
-struct Enum
+static int
+hubfeature(Hub *h, int port, int f, int on)
 {
 {
-	Hub *hub;
-	int port;
-};
+	int cmd;
 
 
-void (*dprinter[])(Device *, int, ulong, void *b, int n) = {
-	[STRING] pstring,
-	[DEVICE] pdevice,
-	[0x29] phub,
-};
-
-static void
-usage(void)
-{
-	fprint(2, "usage: usbd [-DfV] [-d mask] [-u root-hub]\n");
-	threadexitsall("usage");
+	if(on)
+		cmd = Rsetfeature;
+	else
+		cmd = Rclearfeature;
+	return usbcmd(h->dev, Rh2d|Rclass|Rother, cmd, f, port, nil, 0);
 }
 }
 
 
 /*
 /*
- * based on libthread's threadsetname, but drags in less library code.
- * actually just sets the arguments displayed.
+ * This may be used to detect overcurrent on the hub
  */
  */
-void
-procsetname(char *fmt, ...)
+static void
+checkhubstatus(Hub *h)
 {
 {
-	int fd;
-	char *cmdname;
-	char buf[32];
-	va_list arg;
-
-	va_start(arg, fmt);
-	cmdname = vsmprint(fmt, arg);
-	va_end(arg);
-	if (cmdname == nil)
+	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;
 		return;
-	snprint(buf, sizeof buf, "#p/%d/args", getpid());
-	if((fd = open(buf, OWRITE)) >= 0){
-		write(fd, cmdname, strlen(cmdname)+1);
-		close(fd);
 	}
 	}
-	free(cmdname);
+	sts = GET2(buf);
+	dprint(2, "hub %s: status %#ux\n", h->dev->dir, sts);
 }
 }
 
 
-void
-work(void *a)
+static int
+confighub(Hub *h)
 {
 {
-	int port;
-	char name[100];
-	Hub *hub;
-	Enum *arg;
-
-	hub = a;
-	snprint(name, sizeof name, "%H", hub);
-	procsetname(name);
-
-	for(port = 1; port <= hub->nport; port++){
-		if (debug)
-			fprint(2, "enumerate port %H.%d\n", hub, port);
-		arg = emallocz(sizeof(Enum), 1);
-		arg->hub = hub;
-		arg->port = port;
-		incref(&busy);
-		threadcreate(enumerate, arg, STACKSIZE);
-	}
+	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;
 
 
-	decref(&busy);
-	for(;;) {
-		yield();
-		if (busy.ref == 0)
-			sleep(2000);
+	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;
 }
 }
 
 
-void
-realmain(void *)
+static void
+configroothub(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)
 {
 {
-	int i;
 	Hub *h;
 	Hub *h;
+	int i;
+	Usbdev *ud;
 
 
-	if (!dontfork) {
-		/* don't hold window open */
-		close(0);
-		close(1);
-		open("/dev/null", OREAD);
-		open("/dev/null", OWRITE);
-		if(!debug && !verbose) {
-			close(2);
-			open("/dev/null", OWRITE);
+	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(usbnum < 0){
-		/* always fork off usb[1—n] */
-		for(i=1; (h = roothub(i)) != nil; i++) {
-			incref(&busy);
-			proccreate(work, h, STACKSIZE);
+		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;
 		}
 		}
-		usbnum = 0;
 	}
 	}
-	/* usb0 might be handled in this proc */
-	if((h = roothub(usbnum)) != nil){
-		incref(&busy);
-		if (dontfork)
-			work(h);
-		else
-			proccreate(work, h, STACKSIZE);
+	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);
 	}
 	}
-	if (debug)
-		fprint(2, "done\n");
-	while (busy.ref)
-		sleep(100);
-	threadexits(nil);
+	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;
 }
 }
 
 
-void
-threadmain(int argc, char **argv)
+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 void
+hubfail(Hub *h)
 {
 {
-	usbnum = -1;
-	ARGBEGIN{
-	case 'd':
-		debug = atoi(EARGF(usage()));
-		break;
-	case 'D':
-		debugdebug++;
-		break;
-	case 'f':
-		dontfork = 1;
-		break;
-	case 'u':
-		usbnum = atoi(EARGF(usage()));
-		break;
-	case 'V':
-		verbose = 1;
-		break;
-	}ARGEND
-	if(argc)
-		usage();
-	if(access("/dev/usb0", 0) < 0 && bind("#U", "/dev", MBEFORE) < 0)
-		sysfatal("%s: can't bind -b #U /dev: %r", argv0);
+	int i;
+
+	for(i = 1; i <= h->nport; i++)
+		portdetach(h, i);
+	h->failed = 1;
+}
 
 
-	usbfmtinit();
-	fmtinstall('H', Hfmt);
+static void
+closehub(Hub *h)
+{
+	Hub **hl;
 
 
-	if (dontfork)
-		realmain(nil);
+	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 int
+portstatus(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
 	else
-		procrfork(realmain, nil, STACKSIZE, RFNOTEG | RFFDG);
-	threadexits(nil);
+		sts = GET2(buf);
+	usbdebug = dbg;
+	return sts;
 }
 }
 
 
-void
-enumerate(void *v)
+static char*
+stsstr(int sts)
 {
 {
-	int i, port;
-	Device *d;
-	Enum *arg;
-	Hub *h, *nh;
+	static char s[80];
+	char *e;
 
 
-	arg = v;
-	h = arg->hub;
-	port = arg->port;
-	free(arg);
+	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 int
+getmaxpkt(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 void
+portdetach(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->dev != nil){
+		devctl(pp->dev, "detach");
+		usbfsgone(pp->dev->dir);
+		closedev(pp->dev);
+		pp->dev = nil;
+	}
+}
+
+static int
+portgone(Port *pp, int sts)
+{
+	if(sts < 0)
+		return 1;
+	/*
+	 * If it was enabled and it's not now then it may be reconnect.
+	 * We pretend it's gone and later we'll see it as attached.
+	 */
+	if((pp->sts & PSenable) != 0 && (sts & PSenable) == 0)
+		return 1;
+	return (pp->sts & PSpresent) != 0 && (sts & PSpresent) == 0;
+}
+
+static int
+enumhub(Hub *h, int p)
+{
+	int sts;
+	Dev *d;
+	Port *pp;
+	int onhubs;
+
+	if(h->failed)
+		return 0;
+	d = h->dev;
+	if(usbdebug > 3)
+		fprint(2, "%s: %s: port %d enumhub\n", argv0, d->dir, p);
+
+	sts = portstatus(h, p);
+	if(sts < 0){
+		hubfail(h);		/* avoid delays on detachment */
+		return -1;
+	}
+	pp = &h->port[p];
+	onhubs = nhubs;
+	if((sts & PSsuspend) != 0){
+		if(hubfeature(h, p, Fportenable, 1) < 0)
+			dprint(2, "%s: %s: port %d: enable: %r\n", argv0, d->dir, p);
+		sleep(Enabledelay);
+		sts = portstatus(h, p);
+		fprint(2, "%s: %s: port %d: resumed (sts %#ux)\n", argv0, d->dir, p, sts);
+	}
+	if((pp->sts & PSpresent) == 0 && (sts & PSpresent) != 0){
+		if(portattach(h, p, sts) != nil)
+			if(startdev(pp) < 0)
+				portdetach(h, p);
+	}else if(portgone(pp, sts))
+		portdetach(h, p);
+	else if(pp->sts != sts){
+		dprint(2, "%s: %s port %d: sts %s %#x ->",
+			argv0, d->dir, p, stsstr(pp->sts), pp->sts);
+		dprint(2, " %s %#x\n",stsstr(sts), sts);
+	}
+	pp->sts = sts;
+	if(onhubs != nhubs)
+		return -1;
+	return 0;
+}
+
+static void
+dump(void)
+{
+	Hub *h;
+	int i;
+
+	mustdump = 0;
+	for(h = hubs; h != nil; h = h->next)
+		for(i = 1; i <= h->nport; i++)
+			fprint(2, "%s: hub %#p %s port %d: %U",
+				argv0, h, h->dev->dir, i, h->port[i].dev);
+	usbfsdirdump();
+
+}
+
+static void
+work(void *a)
+{
+	Channel *portc;
+	char *fn;
+	Hub *h;
+	int i;
+
+	portc = a;
+	hubs = nil;
+	/*
+	 * Receive requests for root hubs
+	 */
+	while((fn = recvp(portc)) != nil){
+		dprint(2, "%s: %s starting\n", argv0, fn);
+		h = newhub(fn, nil);
+		if(h == nil)
+			fprint(2, "%s: %s: %r\n", argv0, fn);
+		free(fn);
+	}
+	/*
+	 * Enumerate (and acknowledge after first enumeration).
+	 * Do NOT perform enumeration concurrently for the same
+	 * controller. new devices attached respond to a default
+	 * address (0) after reset, thus enumeration has to work
+	 * one device at a time at least before addresses have been
+	 * assigned.
+	 * Do not use hub interrupt endpoint because we
+	 * have to poll the root hub(s) in any case.
+	 */
 	for(;;){
 	for(;;){
-		if((portstatus(h, port) & (1<<PORT_CONNECTION)) == 0){
-			decref(&busy);
-			if(verbose)
-				fprint(2, "usbd: %H: port %d empty\n", h, port);
-			do{
-				yield();
-				if(debugdebug)
-					fprint(2, "usbd: probing %H.%d\n",
-						h, port);
-				sleep(500);
-			}while((portstatus(h, port) & (1<<PORT_CONNECTION)) == 0);
-			incref(&busy);
+	Again:
+		for(h = hubs; h != nil; h = h->next)
+			for(i = 1; i <= h->nport; i++)
+				if(enumhub(h, i) < 0){
+					/* changes in hub list; repeat */
+					goto Again;
+				}
+		if(portc != nil){
+			sendp(portc, nil);
+			portc = nil;
 		}
 		}
-		if(verbose)
-			fprint(2, "usbd: %H: port %d attached\n", h, port);
-		d = configure(h, port);
-		if(d == nil){
-			if(verbose)
-				fprint(2, "usbd: can't configure port %H.%d\n", h, port);
-			decref(&busy);
-			threadexits("configure");
-		}
-		if(d->class == Hubclass){
-			if(debug)
-				fprint(2, "usbd: %H.%d: hub %d attached\n",
-					h, port, d->id);
-			setconfig(d, 1);
-			nh = newhub(h, d);
-			if(nh == nil) {
-				detach(h, port);
-				decref(&busy);
-				threadexits("describehub");
-			}
-			if(debug)
-				fprint(2, "usbd: traversing hub %H\n", nh);
-			/* TO DO: initialise status endpoint */
-			for(i=1; i<=nh->nport; i++)
-				portpower(nh, i, 1);
-			sleep(nh->pwrms);
-			for(i=1; i<=nh->nport; i++) {
-				arg = emallocz(sizeof(Enum), 1);
-				arg->hub = nh;
-				arg->port = i;
-				incref(&busy);
-				threadcreate(enumerate, arg, STACKSIZE);
-			}
-		}else{
-			if(debug)
-				fprint(2,
-					"usbd: %H.%d: %d: not hub, %s speed\n",
-					h, port, d->id, d->ls?"low":"high");
-			setconfig(d, 1);	/* TO DO */
-//unconscionable kludge (testing camera)
-if(d->class == 10) setup0(d, RH2D|Rinterface, SET_INTERFACE, 10, 0, 0);
-		}
-		decref(&busy);
-		while(portstatus(h, port) & (1<<PORT_CONNECTION)) {
-			if (debugdebug)
-				fprint(2, "checking %H.%d\n", h, port);
-			yield();
-			if (d->state == Detached) {
-				if (verbose)
-					fprint(2,
-					 "%H: port %d detached by parent hub\n",
-						h, port);
-				/* parent hub died */
-				threadexits(nil);
-			}
-		}
-		if(verbose)
-			fprint(2, "%H: port %d detached\n", h, port);
-		detach(h, port);
+		sleep(pollms);
+		if(mustdump)
+			dump();
 	}
 	}
+
 }
 }
 
 
-Device*
-configure(Hub *h, int port)
+static int
+cfswalk(Usbfs*, Fid *, char *)
 {
 {
-	Port *p;
-	Device *d;
-	int i, s, maxpkt, ls;
-
-	portenable(h, port, 1);
-	sleep(20);
-	portreset(h, port);
-	sleep(20);
-	s = portstatus(h, port);
-	if (debug)
-		fprint(2, "%H.%d status %#ux\n", h, port, s);
-	if ((s & (1<<PORT_CONNECTION)) == 0)
-		return nil;
-	if ((s & (1<<PORT_SUSPEND)) == 0) {
-		if (debug)
-			fprint(2, "enabling port %H.%d\n", h, port);
-		portenable(h, port, 1);
-		s = portstatus(h, port);
-		if (debug)
-			fprint(2, "%H.%d status now %#ux\n", h, port, s);
-	}
+	werrstr(Enotfound);
+	return -1;
+}
 
 
-	ls = (s & (1<<PORT_LOW_SPEED)) != 0;
-	devspeed(h->dev0, ls);
-	maxpkt = getmaxpkt(h->dev0);
-	if(debugdebug)
-		fprint(2, "%H.%d maxpkt: %d\n", h, port, maxpkt);
-	if(maxpkt < 0){
-Error0:
-		portenable(h, port, 0);
-		return nil;
+static int
+cfsopen(Usbfs*, Fid *, int)
+{
+	return 0;
+}
+
+static long
+cfsread(Usbfs*, Fid *, void *, long , vlong )
+{
+	return 0;
+}
+
+static void
+setdrvargs(char *name, char *args)
+{
+	Devtab *dt;
+	extern Devtab devtab[];
+
+	for(dt = devtab; dt->name != nil; dt++)
+		if(strstr(dt->name, name) != nil)
+			dt->args = estrdup(args);
+}
+
+
+static long
+cfswrite(Usbfs*, Fid *, void *data, long cnt, vlong )
+{
+	char buf[80];
+	char *toks[4];
+
+	if(cnt > sizeof(buf))
+		cnt = sizeof(buf) - 1;
+	strncpy(buf, data, cnt);
+	buf[cnt] = 0;
+	if(cnt > 0 && buf[cnt-1] == '\n')
+		buf[cnt-1] = 0;
+	if(strncmp(buf, "dump", 4) == 0){
+		mustdump = 1;
+		return cnt;
 	}
 	}
-	d = opendev(h->ctlrno, -1);
-	d->ls = ls;
-	d->state = Enabled;
-	d->ep[0]->maxpkt = maxpkt;
-	if(fprint(d->ctl, "maxpkt 0 %d", maxpkt) < 0){
-Error1:
-		closedev(d);
-		goto Error0;
+	if(strncmp(buf, "reset", 5) == 0){
+		werrstr("reset not implemented");
+		return -1;
 	}
 	}
-	if(setaddress(h->dev0, d->id) < 0)
-		goto Error1;
-	d->state = Assigned;
-	devspeed(d, ls);
-	if(describedevice(d) < 0)
-		goto Error1;
-
-	/* read configurations 0 to n */
-	for(i=0; i<d->nconf; i++){
-		if(d->config[i] == nil)
-			d->config[i] = mallocz(sizeof(*d->config[i]),1);
-		loadconfig(d, i);
+	if(tokenize(buf, toks, nelem(toks)) != 2){
+		werrstr("usage: debug|fsdebug n");
+		return -1;
 	}
 	}
-	for(i=0; i<16; i++)
-		setdevclass(d, i);
-	p = &h->port[port-1];
-	p->d = d;
-	return d;
+	if(strcmp(toks[0], "debug") == 0)
+		usbdebug = atoi(toks[1]);
+	else if(strcmp(toks[0], "fsdebug") == 0)
+		usbfsdebug = atoi(toks[1]);
+	else if(strcmp(toks[0], "kbargs") == 0)
+		setdrvargs("kb", toks[1]);
+	else if(strcmp(toks[0], "diskargs") == 0)
+		setdrvargs("disk", toks[1]);
+	else{
+		werrstr("unkown ctl '%s'", buf);
+		return -1;
+	}
+	fprint(2, "%s: debug %d fsdebug %d\n", argv0, usbdebug, usbfsdebug);
+	return cnt;
+}
+
+static int
+cfsstat(Usbfs* fs, Qid qid, Dir *d)
+{
+	d->qid = qid;
+	d->qid.path |= fs->qid;
+	d->qid.type = 0;
+	d->qid.vers = 0;
+	d->name = "usbdctl";
+	d->length = 0;
+	d->mode = 0664;
+	return 0;
+}
+
+static Usbfs ctlfs =
+{
+	.walk = cfswalk,
+	.open = cfsopen,
+	.read = cfsread,
+	.write = cfswrite,
+	.stat = cfsstat
+};
+
+static void
+args(void)
+{
+	char *s;
+
+	s = getenv("usbdebug");
+	if(s != nil)
+		usbdebug = atoi(s);
+	free(s);
+	s = getenv("usbfsdebug");
+	if(s != nil)
+		usbfsdebug = atoi(s);
+	free(s);
+	s = getenv("kbargs");
+	if(s != nil)
+		setdrvargs("kb", s);
+	free(s);
+	s = getenv("diskargs");
+	if(s != nil)
+		setdrvargs("disk", s);
+	free(s);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-Dd] [-s srv] [-m mnt] [dev...]\n", argv0);
+	threadexitsall("usage");
 }
 }
 
 
+extern void usbfsexits(int);
+
 void
 void
-detach(Hub *h, int port)
+threadmain(int argc, char **argv)
 {
 {
-	Port *p;
-	Device *d;
+	int i;
+	Dir *d;
+	int fd;
+	int nd;
+	char *err;
+	char *srv;
+	char *mnt;
 
 
-	p = &h->port[port-1];
-	if(p->hub != nil) {
-		freehub(p->hub);
-		p->hub = nil;
-	}
-	d = p->d;
-	d->state = Detached;	/* return i/o error on access */
-	closedev(d);
+	srv = "usb";
+	mnt = "/dev";
+	ARGBEGIN{
+	case 'D':
+		usbfsdebug++;
+		break;
+	case 'd':
+		usbdebug++;
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	case 'i':
+		pollms = atoi(EARGF(usage()));
+		break;
+	case 'm':
+		mnt = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+	if(access("/dev/usb", AEXIST) < 0 && bind("#u", "/dev", MBEFORE) < 0)
+		sysfatal("#u: %r");
+
+	args();
+
+	fmtinstall('U', Ufmt);
+	quotefmtinstall();
+	rfork(RFNOTEG);
+	portc = chancreate(sizeof(char *), 0);
+	if(portc == nil)
+		sysfatal("chancreate");
+	proccreate(work, portc, Stack);
+	if(argc == 0){
+		fd = open("/dev/usb", OREAD);
+		if(fd < 0)
+			sysfatal("/dev/usb: %r");
+		nd = dirreadall(fd, &d);
+		close(fd);
+		if(nd < 2)
+			sysfatal("/dev/usb: no hubs");
+		for(i = 0; i < nd; i++)
+			if(strcmp(d[i].name, "ctl") != 0)
+				sendp(portc, smprint("/dev/usb/%s", d[i].name));
+		free(d);
+	}else
+		for(i = 0; i < argc; i++)
+			sendp(portc, strdup(argv[i]));
+	sendp(portc, nil);
+	err = recvp(portc);
+	chanfree(portc);
+	usbfsexits(0);
+	usbfsinit(srv, mnt, &usbdirfs, MAFTER);
+	snprint(ctlfs.name, sizeof(ctlfs.name), "usbdctl");
+	usbfsadd(&ctlfs);
+	threadexits(err);
 }
 }

+ 127 - 0
sys/src/cmd/usb/usbd/usbd.h

@@ -0,0 +1,127 @@
+typedef struct Hub Hub;
+typedef struct Port Port;
+typedef struct DHub DHub;
+typedef struct Devtab Devtab;
+typedef struct Usbfs Usbfs;
+
+enum
+{
+	Stack	= 32*1024,
+
+	Dhub	= 0x29,		/* hub descriptor type */
+	Dhublen = 9,		/* hub descriptor length */
+
+	/* hub class feature selectors */
+	Fhublocalpower	= 0,
+	Fhubovercurrent	= 1,
+
+	Fportconnection	= 0,
+	Fportenable	= 1,
+	Fportsuspend	= 2,
+	Fportovercurrent = 3,
+	Fportreset	= 4,
+	Fportpower	= 8,
+	Fportlowspeed	= 9,
+	Fcportconnection	= 16,
+	Fcportenable	= 17,
+	Fcportsuspend	= 18,
+	Fcportovercurrent= 19,
+	Fcportreset	= 20,
+	Fportindicator	= 22,
+
+	/* Port status and status change bits
+	 * Constants at /sys/src/9/pc/usb.h starting with HP-
+	 * must have the same values or root hubs won't work.
+	 */
+	PSpresent	= 0x0001,
+	PSenable	= 0x0002,
+	PSsuspend	= 0x0004,
+	PSovercurrent	= 0x0008,
+	PSreset		= 0x0010,
+	PSpower		= 0x0100,
+	PSslow		= 0x0200,
+	PShigh		= 0x0400,
+
+	PSstatuschg	= 0x10000,	/* PSpresent changed */
+	PSchange	= 0x20000,	/* PSenable changed */
+
+
+	/* port/device state */
+	Pdisabled = 0,		/* must be 0 */
+	Pattached,
+	Pconfiged,
+
+	/* Delays, timeouts (ms) */
+	Spawndelay	= 1000,		/* how often may we re-spawn a driver */
+	Connectdelay	= 1000,		/* how much to wait after a connect */
+	Resetdelay	= 20,		/* how much to wait after a reset */
+	Enabledelay	= 20,		/* how much to wait after an enable */
+	Powerdelay	= 100,		/* after powering up ports */
+	Pollms		= 250, 		/* port poll interval */
+	Chgdelay	= 100,		/* waiting for port become stable */
+	Chgtmout	= 1000,		/* ...but at most this much */
+	
+	/*
+	 * device tab for embedded usb drivers.
+	 */
+	DCL = 0x01000000,		/* csp identifies just class */
+	DSC = 0x02000000,		/* csp identifies just subclass */
+	DPT = 0x04000000,		/* csp identifies just proto */
+
+};
+
+struct Hub
+{
+	uchar	pwrmode;
+	uchar	compound;
+	uchar	pwrms;		/* time to wait in ms */
+	uchar	maxcurrent;	/*    after powering port*/
+	int	leds;		/* has port indicators? */
+	int	maxpkt;
+	uchar	nport;
+	Port	*port;
+	int	failed;		/* I/O error while enumerating */
+	int	isroot;		/* set if root hub */
+	Dev	*dev;		/* for this hub */
+	Hub	*next;		/* in list of hubs */
+};
+
+struct Port
+{
+	int	state;		/* state of the device */
+	int	sts;		/* old port status */
+	uchar	removable;
+	uchar	pwrctl;
+	Dev	*dev;		/* attached device (if non-nil) */
+	Hub	*hub;		/* non-nil if hub attached */
+};
+
+
+/* USB HUB descriptor */
+struct DHub
+{
+	uchar	bLength;
+	uchar	bDescriptorType;
+	uchar	bNbrPorts;
+	uchar	wHubCharacteristics[2];
+	uchar	bPwrOn2PwrGood;
+	uchar	bHubContrCurrent;
+	uchar	DeviceRemovable[1];	/* variable length */
+};
+
+struct Devtab
+{
+	char	*name;
+	int	(*init)(Dev*, int, char**);	/* nil if external */
+ 	int	csps[4];
+	int	vid;
+	int	did;
+	char	*args;
+};
+
+
+Hub*	newhub(char *fn, Dev *d);
+int	startdev(Port *pp);
+void	threadmain(int argc, char **argv);
+
+extern Usbfs usbdfsops;

+ 6 - 0
sys/src/cmd/usb/usbd/usbdb

@@ -0,0 +1,6 @@
+# only kb and disk are prepared for embedding.
+# others are not yet converted to sit in the usbd device driver library
+embed
+	kb	csp=0x010103 csp=0x020103	args=
+	disk	class=storage	args=
+auto

+ 59 - 0
sys/src/cmd/usb/usbfat:

@@ -0,0 +1,59 @@
+#!/bin/rc
+# usbfat: [disk [mtpt]] - mount a USB disk's MS FAT file system
+rfork e
+disk = ()
+mtpt = /n/usb
+
+test -e /dev/usb || bind -a '#u' /dev || {
+	echo no '#u/usb' >[1=2]
+	exit nousb
+}
+test -e /dev/usbdctl || mount -a /srv/usb /dev || {
+	echo cannot mount /srv/usb >[1=2]
+	exit nousbd
+}
+
+disks=()
+mtpt=()
+switch ($#*) {
+case 0
+	;
+case 1
+	disks = $1
+case 2
+	disks = $1
+	mtpt = $2
+case *
+	echo usage: $0 ' [disk [mtpt]]' >[1=2]
+	exit usage
+}
+
+if (~ $#disks 0){
+	if(! test -e /dev/sdU*/data){
+		echo no usb disks >[1=2]
+		exit nodisk
+	}
+	disks = `{echo /dev/sdU*/data}
+}
+for(d in $disks){
+	if(~ $d sdU*.[0-9]*)
+		d=/dev/$d/data
+	if(test -e $d){
+		name=`{echo $d | sed 's/.*(sdU[0-9]+\.[0-9]+).*/\1/'}
+		if(~ $#mtpt 0)
+			mnt=/n/$name
+		if not
+			mnt=$mtpt
+		# don't mount it if it seems to be already mounted.
+		if(! test -e $mnt/*)
+		if(grep -s geometry /dev/$name/ctl){
+			blk = `{disk/fdisk -p $d | awk '/^part dos / {print $3}'}
+			if (! ~ $#blk 0 &&  ~ $blk [0-9]*)
+				d=$d:$blk
+			mount -c <{dossrv -sf $d >[2]/dev/null} $mnt && echo $mnt
+		}
+	}
+	if not
+		echo $d does not exist
+}
+exit ''

Some files were not shown because too many files changed in this diff