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
-# 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
+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
 
 # 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]
 
 # set up any partitions

+ 42 - 25
rc/bin/usbfat:

@@ -1,42 +1,59 @@
 #!/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
-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
 
+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
-	disk = $1
+	disks = $1
 case 2
-	disk = $1
+	disks = $1
 	mtpt = $2
 case *
-	echo usage: $0 '[-fl] [disk [mtpt]]' >[1=2]
+	echo usage: $0 ' [disk [mtpt]]' >[1=2]
 	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
-		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
+# 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)
 		bind -a '#U' /dev
+
 	# /boot/boot may have started usbd, usb/kb or usb/disk
 	if (! ps | grep -s ' usbd$')
 		usb/usbd
 	usb/usbmouse -a 2
 	if (! ps | grep -s ' kb$')
 		usb/kb -k
-	usb/usbaudio -s usbaudio.$sysname # -V
+	usb/usbaudio -s usbaudio.$sysname -V
 	# usb/print
 }
 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
 usb \- USB Host Controller Interface
 .SH SYNOPSIS
 .nf
-.B bind -a #U /dev
+.B bind -a #u /dev
 .PP
 .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
 .SH DESCRIPTION
 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 .
+Hubs provide ports where USB devices (also hubs) can be attached.
 .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.
 .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.
 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
 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).
 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
-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
-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
-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
-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
-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
-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
-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
-.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
-.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
-.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
-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
-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
-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
-For isochronous devices only, an additional line is produced of the
-form:
+This may result from the read of an endpoint control file:
 .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
-.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
-.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
-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
-.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
 .B /sys/src/9/pc/usb.h
 .br
 .B /sys/src/9/pc/devusb.c
 .br
-.B /sys/src/9/pc/usb[ou]hci.c
+.B /sys/src/9/pc/usb?hci.c
 .SH "SEE ALSO"
+.IR usb (2),
 .IR usb (4),
 .IR usbd (4),
 .IR plan9.ini (8)
 .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
-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
-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
 .SH NAME
-usbmouse,
+audio,
+disk,
+ether,
 kb,
-usbaudio,
-print
-\- Universal Serial Bus user-level device drivers
+print,
+probe,
+usbfat:
+\- Universal Serial Bus device drivers
 .SH SYNOPSIS
-.B usb/usbmouse
+.B usb/kb
 [
-.B -fsv
+.B -dkm
 ] [
 .B -a
 .I accel
 ] [
-.I ctrlno
-.I n
+.I dev ...
 ]
 .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
-.B usb/usbaudio
+.B usb/audio
 [
-.B -pV
+.B -dpV
 ] [
 .B -m
-.I mountpoint
+.I mnt
 ] [
 .B -s
-.I srvname
+.I srv
 ] [
 .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
 .B usb/print
+[
+.B -d
+]
+[
+.I dev ...
+]
+.PP
+.B usb/probe
 .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)
-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
-below.
+and
+.I disk
+are started by
+.I usbd
+and other programs are started by hand.
 .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
 .I Kb
 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
 to let the kernel process them.
 Mouse events are sent to
 .B /dev/mousein
 in the same way.
 .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:
 .TF -k
 .TP
@@ -102,25 +155,69 @@ Accelerate the mouse to level
 .I n
 (similar to the kernel mouse driver acceleration).
 .TP
-.B \-d
-Activate debug diagnostics. Repeating the flag one or more times
-increases the verbosity.
-.TP
 .B \-k
 Serve just the keyboard (and not the mouse).
 .TP
 .B \-m
 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
 .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
 .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
 .BI /dev ,
 but this can be changed with the
@@ -135,11 +232,12 @@ The names
 .B volume
 and
 .B audio
-maintain backward compatibility with the soundblaster driver.
+maintain backward compatibility with the Soundblaster driver.
 .PP
 The
 .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
 .B \-s
 option specifies a name for a file descriptor to be posted in
@@ -153,10 +251,11 @@ Reading
 .B volume
 or
 .B audioctl
-yields the device's settings.  The data format of
+yields the device's settings.
+The data format of
 .B volume
-is compatible with the soundblaster and
-produces something like
+is compatible with the Soundblaster and produces output in this
+format:
 .IP
 .EX
 audio out 65
@@ -165,9 +264,11 @@ bass out 0
 speed out 44100
 .EE
 .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.
 .PP
 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.
 The resolution for
 .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
 When all values from
 .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
 The file
 .B audioctl
@@ -204,78 +309,27 @@ Audio data is written to
 .B audio
 and read from
 .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
 .B /sys/src/cmd/usb
 .SH "SEE ALSO"
+.IR kbin (3),
+.IR mouse (3),
+.IR sd (3),
 .IR usb (3),
-.IR usbd (4),
-.IR usbdisk (4)
+.IR usbd (4)
 .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
 .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
 .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:
+.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
-.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
 .B /sys/src/cmd/usb/usbd
 .SH "SEE ALSO"
+.IR usb (2),
 .IR usb (3),
 .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)
 {
 	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

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
 	ether82557	pci
 	ether83815	pci
+	etherdp83820	pci
 	etherec2t	ether8390
 	etherelnk3	pci
 	etherga620	pci
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethersink
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
@@ -70,6 +72,7 @@ link
 	loopbackmedium
 	usbuhci
 	usbohci
+	usbehci
 
 misc
 	archmp		mp apic
@@ -115,16 +118,15 @@ ip
 	gre
 	ipmux
 	esp
-#	il
 
 port
 	int cpuserver = 0;
 
 boot
 	tcp
-#	il
 
 bootdir
 	bootpc.out boot
 	/386/bin/ip/ipconfig
 	/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
 	root
 	cons
@@ -28,14 +28,21 @@ dev
 	floppy		dma
 
 	uart
+	usb
+	kbin
 
 link
 	apm		apmjump
+	etherdp83820	pci
 	ether82557	pci
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethermedium
 	netdevmedium
 	loopbackmedium
+	usbuhci
+	usbohci
+	usbehci
 
 misc
 	realmode
@@ -50,6 +57,7 @@ misc
 ip
 	tcp
 	udp
+	rudp
 	ipifc
 	icmp
 	icmp6
@@ -70,4 +78,4 @@ bootdir
 	/386/bin/auth/factotum
 	/386/bin/fossil/fossil
 	/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
 	root
 	cons
@@ -60,6 +60,7 @@ link
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethersink
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
@@ -70,6 +71,7 @@ link
 	netdevmedium
 	usbuhci
 	usbohci
+	usbehci
 
 misc
 	archmp		mp apic
@@ -112,18 +114,16 @@ ip
 	ipifc
 	icmp
 	icmp6
-#	il
 
 port
 	int cpuserver = 0;
 
 boot boot #S/sdD0/data
 	tcp
-#	il
 	local
 
 bootdir
 	bootpccd.out boot
 	/386/bin/ip/ipconfig ipconfig
 	/386/bin/9660srv kfs
-
+	/386/bin/usb/usbd

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

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

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

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

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

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

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

@@ -59,6 +59,7 @@ link
 	etherigbe	pci ethermii
 	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
+	ethervt6105m	pci ethermii
 	ethersink
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
@@ -67,6 +68,8 @@ link
 	netdevmedium
 	loopbackmedium
 	usbuhci
+	usbohci
+	usbehci
 
 misc
 	archmp		mp apic
@@ -105,6 +108,7 @@ misc
 ip
 	tcp
 	udp
+	rudp
 	ipifc
 	icmp
 	icmp6
@@ -123,6 +127,6 @@ bootdir
 	bootpcf.out boot
 	/386/bin/ip/ipconfig
 	/386/bin/auth/factotum
-#	/386/bin/disk/kfs
 	/386/bin/fossil/fossil
 	/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
 	root
 	cons
@@ -30,7 +31,8 @@ dev
 	pccard
 	i82365		cis
 	uart
-	usb
+#	usb
+#	kbin
 
 link
 	devpccard
@@ -56,7 +58,9 @@ link
 	pcmciamodem
 	netdevmedium
 	loopbackmedium
-	usbuhci
+#	usbohci
+#	usbuhci
+#	usbehci
 
 misc
 	archmp		mp apic
@@ -100,6 +104,7 @@ port
 	int cpuserver = 0;
 
 boot boot #S/sdC0/
+	tcp
 	local
 
 bootdir
@@ -114,8 +119,8 @@ bootdir
 	/386/bin/auth/factotum
 	/386/bin/fossil/fossil
 	/386/bin/ip/ipconfig
+	/386/bin/usb/usbd
 	/386/bin/venti/venti
 	/sys/lib/sysconfig/fl/boot
 	/sys/lib/sysconfig/fl/flproto
 	/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
 	root
 	cons
+
 	arch
 	pnp		pci
 	env
@@ -32,6 +34,7 @@ dev
 	i82365		cis
 	uart
 #	usb
+#	kbin
 
 link
 	realmode
@@ -53,17 +56,22 @@ link
 	etherec2t	ether8390
 	etherelnk3	pci
 	etherga620	pci
-	ethervgbe	pci ethermii
 	etherigbe	pci ethermii
+	ethervgbe	pci ethermii
 	ethervt6102	pci ethermii
+#	ethervt6105m	pci ethermii
 #	ethersink
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	ethermedium
+#	etherm10g
+#	ether82598	pci
 	pcmciamodem
 	netdevmedium
 	loopbackmedium
 #	usbuhci
+#	usbohci
+#	usbehci
 
 misc
 #	archmp		mp apic
@@ -71,6 +79,7 @@ misc
 	sdata		pci sdscsi
 	sd53c8xx	pci sdscsi
 	sdmylex		pci sdscsi
+#	sdiahci		pci sdscsi
 
 	uarti8250
 #	uartpci		pci
@@ -104,7 +113,6 @@ ip
 	ipifc
 	icmp
 	icmp6
-#	il
 
 port
 	int cpuserver = 0;
@@ -116,3 +124,4 @@ bootdir
 	bootpcflop.out boot
 	/sys/lib/dist/bin/386/bzfs kfs
 	/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
 {
-	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 */
-	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,
-};
-
-#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	debugload;
 extern Apminfo	apm;
 extern char	*defaultpartition;
 extern int	iniread;
 extern int	pxe;
 extern int	vga;
+
 extern int	onlybios0;
 extern int	biosinited;
 extern int	biosload;

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

@@ -140,7 +140,8 @@ biosinit(void)
 		if (lba < 0) {
 			if (devid > 0)
 				continue;
-			print("bios call failed; bios loading disabled\n");	
+			if (debugload)
+				print("bios call failed; bios loading disabled\n");	
 			biosload = 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
+ * 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 <libc.h>
 #include <thread.h>
 #include "usb.h"
-#include "usbaudio.h"
-#include "usbaudioctl.h"
+#include "audio.h"
+#include "audioctl.h"
 
 #define STACKSIZE 16*1024
 
@@ -16,55 +23,48 @@ char * mntpt;
 
 Channel *controlchan;
 
-char audstr[]		= "Enabled 0x000101";	/* audio.control.0 */
-
+int verbose;
 int setrec = 0;
 int defaultspeed[2] = {44100, 44100};
+Dev *buttondev;
+Dev *epdev[2];
 
 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:
-		if (debug){
+		if(usbdebug){
 			fprint(2, "CS_ENDPOINT for attributes 0x%x, lockdelayunits %d, lockdelay %#ux, ",
 				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");
 			else
 				fprint(2, "does not have sampling-frequency control");
-			if (b[3] & 0x1<<1)
+			if(b[3] & 0x1<<1)
 				fprint(2, ", has pitch control");
 			else
 				fprint(2, ", does not have pitch control");
-			if (b[3] & 0x1<<7)
+			if(b[3] & 0x1<<7)
 				fprint(2, ", max packets only");
 			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;
 	case 0x02:
-		if (debug){
+		if(usbdebug){
 			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",
@@ -72,18 +72,14 @@ audio_endpoint(Device *d, int c, ulong csp, void *bb, int n)
 		}
 		break;
 	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 {
 	None,
 	Volumeset,
@@ -107,51 +103,49 @@ controlproc(void *)
 		int rec;
 
 		nf = tokenize(req, args, nelem(args));
-		if (nf < 3)
+		if(nf < 3)
 			sysfatal("controlproc: not enough arguments");
 		replchan = (Channel*)strtol(args[0], nil, 0);
-		if (strcmp(args[2], "playback") == 0)
+		if(strcmp(args[2], "playback") == 0)
 			rec = Play;
-		else if (strcmp(args[2], "record") == 0)
+		else if(strcmp(args[2], "record") == 0)
 			rec = Record;
 		else{
 			/* 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);
 			continue;
 		}
 		c = nil;
-		for (i = 0; i < Ncontrol; i++){
+		for(i = 0; i < Ncontrol; i++){
 			c = &controls[rec][i];
-			if (strcmp(args[1], c->name) == 0)
+			if(strcmp(args[1], c->name) == 0)
 				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]);
-		}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",
 					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]);
-		} 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]);
-			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]);
 			}else{
-				if (replchan) chanprint(replchan, "ok");
+				if(replchan) chanprint(replchan, "ok");
 			}
 			ctlevent();
 		}
@@ -163,111 +157,81 @@ void
 buttonproc(void *)
 {
 	int	i, fd, b;
-	char fname[64], err[32];
+	char err[32];
 	byte buf[1];
 	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];
 	for(;;){
 		if((b = read(fd, buf, 1)) < 0){
 			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;
 			}
-			sysfatal("read %s: %r", fname);
+			sysfatal("read %s/data: %r", buttondev->dir);
 		}
 		if(b == 0 || buf[0] == 0){
 			continue;
 		}else if(buf[0] == 1){
-			if (c->chans == 0)
+			if(c->chans == 0)
 				c->value[0] += c->step;
 			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;
 			chanprint(controlchan, "0 volume playback %A", c);
 		}else if(buf[0] == 2){
-			if (c->chans == 0)
+			if(c->chans == 0)
 				c->value[0] -= c->step;
 			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;
 			chanprint(controlchan, "0 volume playback %A", c);
-		}else if(debug & Dbginfo){
+		}else if(usbdebug){
 			fprint(2, "button");
-			for (i = 0; i < b; i++)
+			for(i = 0; i < b; i++)
 				fprint(2, " %#2.2x", buf[i]);
 			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
 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");
 }
 
 void
 threadmain(int argc, char **argv)
 {
-	int ctlrno, id, i, sfd;
+	char *devdir;
+	int i;
 	long value[8], volume[8];
 	Audiocontrol *c;
-	char buf[32], *p, line[256];
+	char *p;
 	extern int attachok;
+	Ep *ep;
+	int csps[] = { Audiocsp, 0};
 
-	ctlrno = id = -1;
+	devdir = nil;
 	volume[0] = Undef;
-	for (i = 0; i<8; i++)
+	for(i = 0; i<8; i++)
 		value[i] = 0;
 	fmtinstall('A', Aconv);
+	fmtinstall('U', Ufmt);
 	quotefmtinstall();
 
 	ARGBEGIN{
 	case 'd':
-		debug = strtol(EARGF(usage()), nil, 0);
-		if (debug == -1)
-			debugdebug++;
+		usbdebug++;
 		verbose++;
 		break;
 	case 'm':
@@ -290,48 +254,60 @@ threadmain(int argc, char **argv)
 	default:
 		usage();
 	}ARGEND
-
 	switch(argc){
 	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;
-	case 2:
-		ctlrno = atoi(argv[0]);
-		id = atoi(argv[1]);
+	case 1:
+		devdir = argv[0];
 		break;
 	default:
 		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);
 
-	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(verbose)
@@ -345,10 +321,10 @@ threadmain(int argc, char **argv)
 			defaultspeed[Play] = 48000;
 		}
 		value[0] = 2;
-		if (setcontrol(Play, "channels", value) == Undef)
+		if(setcontrol(Play, "channels", value) == Undef)
 			sysfatal("Can't set play channels");
 		value[0] = 16;
-		if (setcontrol(Play, "resolution", value) == Undef)
+		if(setcontrol(Play, "resolution", value) == Undef)
 			sysfatal("Can't set play resolution");
 	}
 
@@ -364,57 +340,57 @@ threadmain(int argc, char **argv)
 				fprint(2, "Warning, can't configure stereo "
 					"recording, configuring mono instead\n");
 				i = 1;
-			} else
+			}else
 				break;
-		if(findalt(Record, i, 16, 48000) < 0) {
+		if(findalt(Record, i, 16, 48000) < 0){
 			endpt[Record] = -1;	/* disable recording */
 			setrec = 0;
 			fprint(2, "Warning, can't configure record for %d Hz or %d Hz\n",
 				defaultspeed[Record], 48000);
-		} else
+		}else
 			fprint(2, "Warning, can't configure record for %d Hz, "
 				"configuring for %d Hz instead\n",
 				defaultspeed[Record], 48000);
 		defaultspeed[Record] = 48000;
-		if (setrec) {
+		if(setrec){
 			value[0] = i;
-			if (setcontrol(Record, "channels", value) == Undef)
+			if(setcontrol(Record, "channels", value) == Undef)
 				sysfatal("Can't set record channels");
 			value[0] = 16;
-			if (setcontrol(Record, "resolution", value) == Undef)
+			if(setcontrol(Record, "resolution", value) == Undef)
 				sysfatal("Can't set record resolution");
 		}
 	}
 
 	getcontrols();	/* Get the initial value of all controls */
 	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");
 	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);
 	value[0] = 0;
 	setcontrol(Play, "mute", value);
 
-	if (volume[0] != Undef){
+	if(volume[0] != Undef){
 		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;
-		if (c->settable)
+		if(c->settable)
 			setcontrol(Play, "volume", volume);
 		c = &controls[Record][Volume_control];
-		if (c->settable && setrec)
+		if(c->settable && setrec)
 			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(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,
 
 	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 */
 };
 
+typedef uchar byte;
+
 extern int setrec;
+extern int verbose;
 extern int defaultspeed[2];
-extern Device *ad;
+extern Dev *ad;
+extern Dev *buttondev;
 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	setcontrol(int rec, char *name, 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 <thread.h>
 #include "usb.h"
-#include "usbaudio.h"
-#include "usbaudioctl.h"
+#include "audio.h"
+#include "audioctl.h"
 
 int endpt[2] =		{-1, -1};
 int interface[2] =	{-1, -1};
@@ -14,7 +14,7 @@ int curalt[2] =		{-1, -1};
 int buttonendpt =	-1;
 
 int id;
-Device *ad;
+Dev *ad;
 
 Audiocontrol controls[2][Ncontrol] = {
 	{
@@ -53,12 +53,11 @@ Audiocontrol controls[2][Ncontrol] = {
 int
 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;
-	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;
 }
@@ -66,9 +65,9 @@ setaudioalt(int rec, Audiocontrol *c, int control)
 int
 findalt(int rec, int nchan, int res, int speed)
 {
-	Endpt *ep;
+	Ep *ep;
 	Audioalt *a;
-	Dalt *da;
+	Altc *da;
 	int i, j, k, retval;
 
 	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].max = 0;
 	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;
-		if(ep->csp != CSP(CL_AUDIO, 2, 0))
-			continue;
-		if (ep->iface == nil) {
+		if(ep->iface == nil){
 			fprint(2, "\tno interface\n");
 			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))
 			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;
-			if (a->nchan < controls[rec][Channel_control].min)
+			if(a->nchan < controls[rec][Channel_control].min)
 				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;
-			if (a->res < controls[rec][Resolution_control].min)
+			if(a->res < controls[rec][Resolution_control].min)
 				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][Channel_control].settable = 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][Speed_control].settable = 1;
 			controls[rec][Speed_control].readable = 1;
-			if (a->nchan == nchan && a->res == res){
+			if(a->nchan == nchan && a->res == res){
 				if(speed == Undef)
 					retval = j;
 				else if(a->caps & (has_discfreq|onefreq)){
@@ -117,16 +116,15 @@ findalt(int rec, int nchan, int res, int speed)
 							break;
 						}
 					}
-				} else {
+				}else{
 					if(speed >= a->minfreq && speed <= a->maxfreq)
 						retval = j;
 				}
 			}
 		}
 	}
-	if ((debug & Dbgcontrol) && retval < 0){
+	if(usbdebug && retval < 0)
 		fprint(2, "findalt(%d, %d, %d, %d) failed\n", rec, nchan, res, speed);
-	}
 	return retval;
 }
 
@@ -135,100 +133,87 @@ setspeed(int rec, int speed)
 {
 	int ps, n, no, dist, i;
 	Audioalt *a;
-	Dalt *da;
-	Endpt *ep;
-	char cmdbuf[32];
+	Altc *da;
+	Ep *ep;
 	uchar buf[3];
 
-	if (rec == Record && !setrec)
+	if(rec == Record && !setrec)
 		return Undef;
-	if (curalt[rec] < 0){
+	if(curalt[rec] < 0){
 		fprint(2, "Must set channels and resolution before speed\n");
 		return Undef;
 	}
-	if (endpt[rec] < 0)
+	if(endpt[rec] < 0)
 		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");
-	if (curalt[rec] < 0)
+	if(curalt[rec] < 0)
 		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 = 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;
-		else if (speed > a->maxfreq)
+		else if(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);
-	}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;
 		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);
 				no = i;
 			}
-		if (no == -1){
-			if (debug & Dbgcontrol)
-				fprint(2, "no = -1\n");
+		if(no == -1){
+			dprint(2, "no = -1\n");
 			return Undef;
 		}
 		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);
 	}else{
-		if (debug & Dbgcontrol)
-			fprint(2, "can't happen\n?");
+		dprint(2, "can't happen\n?");
 		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[1] = speed >> 8;
 		buf[2] = speed >> 16;
 		n = endpt[rec];
-		if (rec)
+		if(rec)
 			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");
 			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");
 			return Undef;
 		}
-		n = setupreply(ad->ep[0], buf, 3);
-		if (n != 3)
+		if(n != 3)
 			fprint(2, "Error in setupreply: %d\n", n);
 		else{
 			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 */
-				if (debug & Dbgcontrol)
-					fprint(2, " reported as %d (iMic bug?);", n);
+				dprint(2, " reported as %d (iMic bug?);", n);
 			}else
 				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)
 		* controls[rec][Channel_control].value[0]
@@ -239,18 +224,21 @@ setspeed(int rec, int speed)
 			argv0, rec, speed, ps, ep->maxpkt);
 		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);
-	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;
 }
 
@@ -259,81 +247,74 @@ getspeed(int rec, int which)
 {
 	int i, n;
 	Audioalt *a;
-	Dalt *da;
-	Endpt *ep;
+	Altc *da;
+	Ep *ep;
 	uchar buf[3];
+	int r;
 
-	if (curalt[rec] < 0){
+	if(curalt[rec] < 0){
 		fprint(2, "Must set channels and resolution before getspeed\n");
 		return Undef;
 	}
-	if (endpt[rec] < 0)
+	if(endpt[rec] < 0)
 		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");
-	if (curalt[rec] < 0)
+	if(curalt[rec] < 0)
 		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 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];
-		if (rec)
+		if(rec)
 			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;
-		n = setupreply(ad->ep[0], buf, 3);
 		if(n == 3){
 			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 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];
-		if (which == GET_MIN)
+		if(which == Rgetmin)
 			return a->minfreq;
-		if (which == GET_MAX)
+		if(which == Rgetmax)
 			return a->maxfreq;
-		if (which == GET_RES)
+		if(which == Rgetres)
 			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];
-		if (which == GET_MIN)
+		if(which == Rgetmin)
 			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];
-		if (which == GET_RES)
+		if(which == Rgetres)
 			return Undef;
 	}
-	if (debug & Dbgcontrol)
-		fprint(2, "can't happen\n?");
+	dprint(2, "can't happen\n?");
 	return Undef;
 }
 
@@ -346,55 +327,55 @@ setcontrol(int rec, char *name, long *value)
 	Audiocontrol *c;
 
 	c = nil;
-	for (ctl = 0; ctl < Ncontrol; ctl++){
+	for(ctl = 0; ctl < Ncontrol; ctl++){
 		c = &controls[rec][ctl];
-		if (strcmp(name, c->name) == 0)
+		if(strcmp(name, c->name) == 0)
 			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;
 	}
-	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 0;
 		}
-		if (c->value[0] != value[0])
+		if(c->value[0] != value[0])
 			return -1;
 		return 0;
 	}
-	if (c->chans){
+	if(c->chans){
 		value[0] = 0;	// set to average
 		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;
-				if (c->max != Undef && value[i] > c->max)
+				if(c->max != Undef && value[i] > c->max)
 					value[i] = c->max;
 				value[0] += value[i];
 				m++;
-			} else
+			}else
 				value[i] = Undef;
-		if (m) value[0] /= m;
+		if(m) value[0] /= m;
 	}else{
-		if (c->min != Undef && value[0] < c->min)
+		if(c->min != Undef && 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;
 	}
-	req = SET_CUR;
+	req = Rsetcur;
 	count = 1;
 	switch(ctl){
 	default:
-		if (debug & Dbgcontrol) fprint(2, "setcontrol: can't happen\n");
+		dprint(2, "setcontrol: can't happen\n");
 		return -1;
 	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;
 		c->value[0] = value[0];
 		return 0;
@@ -404,8 +385,7 @@ setcontrol(int rec, char *name, long *value)
 	case Resolution_control:
 		control = findalt(rec, controls[rec][Channel_control].value[0], value[0], defaultspeed[rec]);
 		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;
 		}
 		c->value[0] = value[0];
@@ -422,20 +402,19 @@ setcontrol(int rec, char *name, long *value)
 	case Agc_control:
 	case Bassboost_control:
 	case Loudness_control:
-		type = RH2D|Rclass|Rinterface;
+		type = Rh2d|Rclass|Riface;
 		control = ctl<<8;
 		index = featureid[rec]<<8;
 		break;
 	case Selector_control:
-		type = RH2D|Rclass|Rinterface;
+		type = Rh2d|Rclass|Riface;
 		control = 0;
 		index = selectorid[rec]<<8;
 		break;
 	case Channel_control:
 		control = findalt(rec, value[0], controls[rec][Resolution_control].value[0], defaultspeed[rec]);
 		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;
 		}
 		c->value[0] = value[0];
@@ -443,16 +422,16 @@ setcontrol(int rec, char *name, long *value)
 		return 0;
 	}
 	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){
 				case 2:
 					buf[1] = value[i] >> 8;
 				case 1:
 					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);
 					return -1;
 				}
@@ -465,9 +444,8 @@ setcontrol(int rec, char *name, long *value)
 		case 1:
 			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;
 		}
 	}
@@ -493,13 +471,13 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 		return 0;
 	case Channel_control:
 	case Resolution_control:
-		if (req == GET_MIN)
+		if(req == Rgetmin)
 			value[0] = controls[rec][ctl].min;
-		if (req == GET_MAX)
+		if(req == Rgetmax)
 			value[0] = controls[rec][ctl].max;
-		if (req == GET_RES)
+		if(req == Rgetres)
 			value[0] = controls[rec][ctl].step;
-		if (req == GET_CUR)
+		if(req == Rgetcur)
 			value[0] = controls[rec][ctl].value[0];
 		return 0;
 	case Volume_control:
@@ -511,12 +489,12 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 	case Treble_control:
 	case Equalizer_control:
 		signedbyte = 1;
-		type = RD2H|Rclass|Rinterface;
+		type = Rd2h|Rclass|Riface;
 		control = ctl<<8;
 		index = featureid[rec]<<8;
 		break;
 	case Selector_control:
-		type = RD2H|Rclass|Rinterface;
+		type = Rd2h|Rclass|Riface;
 		control = 0;
 		index = selectorid[rec]<<8;
 		break;
@@ -524,28 +502,28 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 	case Agc_control:
 	case Bassboost_control:
 	case Loudness_control:
-		if (req != GET_CUR)
+		if(req != Rgetcur)
 			return Undef;
-		type = RD2H|Rclass|Rinterface;
+		type = Rd2h|Rclass|Riface;
 		control = ctl<<8;
 		index = featureid[rec]<<8;
 		break;
 	}
-	if (controls[rec][ctl].chans){
+	if(controls[rec][ctl].chans){
 		m = 0;
 		value[0] = 0; // set to average
-		for (i = 1; i < 8; i++){
+		for(i = 1; i < 8; i++){
 			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;
-				n = setupreply(ad->ep[0], buf, count);
-				if (n != count)
+				if(n != count)
 					return -1;
-				switch (count) {
+				switch (count){
 				case 2:
 					svalue = buf[1] << 8 | buf[0];
-					if (req == GET_CUR){
+					if(req == Rgetcur){
 						value[i] = svalue;
 						value[0] += svalue;
 						m++;
@@ -554,9 +532,9 @@ getspecialcontrol(int rec, int ctl, int req, long *value)
 					break;
 				case 1:
 					svalue = buf[0];
-					if (signedbyte && (svalue&0x80))
+					if(signedbyte && (svalue&0x80))
 						svalue |= 0xFF00;
-					if (req == GET_CUR){
+					if(req == Rgetcur){
 						value[i] = svalue;
 						value[0] += svalue;
 						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;
 	}
 	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;
-	switch (count) {
+	switch (count){
 	case 2:
 		svalue = buf[1] << 8 | buf[0];
 		value[0] = svalue;
 		break;
 	case 1:
 		svalue = buf[0];
-		if (signedbyte && (svalue&0x80))
+		if(signedbyte && (svalue&0x80))
 			svalue |= 0xFF00;
 		value[0] = svalue;
 	}
@@ -593,15 +568,15 @@ getcontrol(int rec, char *name, long *value)
 {
 	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;
 	}
-	if (i == Ncontrol)
+	if(i == Ncontrol)
 		return -1;
-	if (controls[rec][i].readable == 0)
+	if(controls[rec][i].readable == 0)
 		return -1;
-	if(getspecialcontrol(rec, i, GET_CUR, value) < 0)
+	if(getspecialcontrol(rec, i, Rgetcur, value) < 0)
 		return -1;
 	memmove(controls[rec][i].value, value, sizeof controls[rec][i].value);
 	return 0;
@@ -614,45 +589,44 @@ getcontrols(void)
 	Audiocontrol *c;
 	long v[8];
 
-	for (rec = 0; rec < 2; rec++) {
-		if (rec == Record && !setrec)
+	for(rec = 0; rec < 2; rec++){
+		if(rec == Record && !setrec)
 			continue;
-		for (ctl = 0; ctl < Ncontrol; ctl++){
+		for(ctl = 0; ctl < Ncontrol; ctl++){
 			c = &controls[rec][ctl];
-			if (c->readable){
-				if (verbose)
+			if(c->readable){
+				if(verbose)
 					fprint(2, "%s %s control",
 						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);
-				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);
-				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);
-				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");
-							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]);
 						}else
 							fprint(2, ", value %ld", c->value[0]);
 					}
 				}
-				if (verbose)
+				if(verbose)
 					fprint(2, "\n");
-			} else {
+			}else{
 				c->min = Undef;
 				c->max = Undef;
 				c->step = 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;
 
 	nf = tokenize(s, vals, nelem(vals));
-	if (nf <= 0)
+	if(nf <= 0)
 		return -1;
-	if (c->chans){
+	if(c->chans){
 		j = 0;
 		m = 0;
 		SET(val);
 		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);
-					if (val == 0 && *p != '\0' && *p != '%')
+					if(val == 0 && *p != '\0' && *p != '%')
 						return -1;
-					if (*p == '%' && c->min != Undef)
+					if(*p == '%' && c->min != Undef)
 						val = (val*c->max + (100-val)*c->min)/100;
 					j++;
 				}
 				v[i] = val;
 				v[0] += val;
 				m++;
-			} else
+			}else
 				v[i] = Undef;
-		if (m) v[0] /= m;
-	} else {
+		if(m) v[0] /= m;
+	}else{
 		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;
 		v[0] = val;
 	}
@@ -709,15 +683,15 @@ Aconv(Fmt *fp)
 
 	c = va_arg(fp->args, Audiocontrol*);
 	p = str;
-	if (c->chans) {
+	if(c->chans){
 		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]);
 				fst = 0;
 			}
 		seprint(p, str+sizeof str, "'");
-	} else
+	}else
 		seprint(p, str+sizeof str, "%ld", c->value[0]);
 	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 <libsec.h>
 #include "usb.h"
-#include "usbaudio.h"
-#include "usbaudioctl.h"
+#include "audio.h"
+#include "audioctl.h"
 
 int attachok;
 
@@ -68,19 +68,14 @@ enum {
 	Qvolume,
 	Qaudioctl,
 	Qaudiostat,
-	Qaudio,
-	Qaudioin,
 	Nqid,
 };
 
 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},
 [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},
-[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;
@@ -166,12 +161,19 @@ post(char *name, char *envname, int srvfd)
 	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
 serve(void *)
 {
 	int i;
 	ulong t;
-	Dir *dir;
 
 	if(pipe(p) < 0)
 		sysfatal("pipe failed");
@@ -181,7 +183,7 @@ serve(void *)
 	atnotify(notifyf, 1);
 	strcpy(user, getuser());
 	t = time(nil);
-	for (i = 0; i < Nqid; i++){
+	for(i = 0; i < Nqid; i++){
 		dirs[i].uid = user;
 		dirs[i].gid = user;
 		dirs[i].muid = user;
@@ -193,7 +195,7 @@ serve(void *)
 		mntpt = mntdir;
 	}
 
-	if(debug)
+	if(usbdebug)
 		fmtinstall('F', fcallfmt);
 
 	procrfork(io, nil, STACKSIZE, RFFDG|RFNAMEG);
@@ -206,25 +208,10 @@ serve(void *)
 	}
 	if(mount(p[1], -1, mntpt, MBEFORE, "") < 0)
 		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);
 }
 
@@ -262,16 +249,15 @@ rflush(Fid *)
 
 	do {
 		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++;
 				nbsendul(w->eventc, thdr.oldtag << 16 | Flush);
 			}
-		if (waitflush)
+		if(waitflush)
 			sleep(50);
 	} 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;
 }
 
@@ -305,19 +291,15 @@ dowalk(Fid *f, char *name)
 {
 	int t;
 
-	if (strcmp(name, ".") == 0)
+	if(strcmp(name, ".") == 0)
 		return nil;
-	if (strcmp(name, "..") == 0){
+	if(strcmp(name, "..") == 0){
 		f->dir = &dirs[Qdir];
 		return nil;
 	}
 	if(f->dir != &dirs[Qdir])
 		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){
 			f->dir = &dirs[t];
 			return nil;
@@ -381,7 +363,7 @@ allocaudioctldata(void)
 	Audioctldata *a;
 
 	a = emallocz(sizeof(Audioctldata), 1);
-	for (i = 0; i < 2; i++)
+	for(i = 0; i < 2; i++)
 		for(j=0; j < Ncontrol; j++)
 			for(k=0; k < 8; k++)
 				a->values[i][j][k] = Undef;
@@ -394,8 +376,6 @@ ropen(Fid *f)
 	if(f->flags & Open)
 		return Eisopen;
 
-	if(f->dir == &dirs[Qaudio] || f->dir == &dirs[Qaudioin])
-		return Eperm;
 	if(thdr.mode != OREAD && (f->dir->mode & 0x2) == 0)
 		return Eperm;
 	qlock(f);
@@ -422,11 +402,7 @@ readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
 
 	n = 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);
 		if(off <= pos){
 			if(m <= BIT16SZ || m > cnt)
@@ -450,32 +426,32 @@ makeaudioctldata(Fid *f)
 	Audiocontrol *c;
 	Audioctldata *a;
 
-	if ((a = f->fiddata) == nil)
+	if((a = f->fiddata) == nil)
 		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* */
-	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];
 			actls = a->values[rec][ctl];
 			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])
 						diff = 1;
 			}else
-				if (c->value[0] != actls[0])
+				if(c->value[0] != actls[0])
 					diff = 1;
-			if (diff){
+			if(diff){
 				p = seprint(p, e, "%s %s %A", c->name,
 					rec? "in": "out", c);
 				memmove(actls, c->value, sizeof c->value);
-				if (c->min != Undef){
+				if(c->min != Undef){
 					p = seprint(p, e, " %ld %ld", c->min,
 						c->max);
-					if (c->step != Undef)
+					if(c->step != Undef)
 						p = seprint(p, e, " %ld",
 							c->step);
 				}
@@ -500,9 +476,9 @@ readproc(void *x)
 	Worker *w;
 
 	w = x;
-	mdata = emalloc(8*1024+IOHDRSZ);
+	mdata = emallocz(8*1024+IOHDRSZ, 0);
 	while(event = recvul(w->eventc)){
-		if (event != Work)
+		if(event != Work)
 			continue;
 		f = w->fid;
 		rhdr = w->rhdr;
@@ -515,38 +491,35 @@ readproc(void *x)
 			qunlock(f);
 			event = recvul(w->eventc);
 			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);
-			switch (event & 0xffff) {
+			switch (event & 0xffff){
 			case Work:
 				sysfatal("readproc phase error");
 			case Check:
-				if (f->fiddata && makeaudioctldata(f) == 0)
+				if(f->fiddata && makeaudioctldata(f) == 0)
 					continue;
 				break;
 			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;
 				}
 				continue;
 			}
-			if (f->fiddata){
+			if(f->fiddata){
 				rhdr->data = a->s;
 				rhdr->count = a->ns;
 				break;
 			}
 			yield();
 		}
-		if (rhdr->count > cnt)
+		if(rhdr->count > cnt)
 			rhdr->count = cnt;
-		if (rhdr->count)
+		if(rhdr->count)
 			f->flags &= ~Eof;
-		if(debug & (Dbgproc|Dbgfs))
-			fprint(2, "readproc:->%F\n", rhdr);
+		ddprint(2, "readproc:->%F\n", rhdr);
 		n = convS2M(rhdr, mdata, messagesize);
 		if(write(mfd[1], mdata, n) != n)
 			sysfatal("mount write");
@@ -591,9 +564,9 @@ rread(Fid *f)
 	if(f->dir == &dirs[Qvolume]){
 		p = buf;
 		n = sizeof buf;
-		for (rec = 0; rec < 2; rec++){
+		for(rec = 0; rec < 2; rec++){
 			c = &controls[rec][Volume_control];
-			if (c->readable){
+			if(c->readable){
 				div = c->max - c->min;
 				i = snprint(p, n, "audio %s %ld\n",
 					rec? "in": "out", (c->min != Undef?
@@ -603,7 +576,7 @@ rread(Fid *f)
 				n -= i;
 			}
 			c = &controls[rec][Treble_control];
-			if (c->readable){
+			if(c->readable){
 				div = c->max - c->min;
 				i = snprint(p, n, "treb %s %ld\n",
 					rec? "in": "out", (c->min != Undef?
@@ -613,7 +586,7 @@ rread(Fid *f)
 				n -= i;
 			}
 			c = &controls[rec][Bass_control];
-			if (c->readable){
+			if(c->readable){
 				div = c->max - c->min;
 				i = snprint(p, n, "bass %s %ld\n",
 					rec? "in": "out", (c->min != Undef?
@@ -623,7 +596,7 @@ rread(Fid *f)
 				n -= i;
 			}
 			c = &controls[rec][Speed_control];
-			if (c->readable){
+			if(c->readable){
 				i = snprint(p, n, "speed %s %ld\n",
 					rec? "in": "out", c->value[0]);
 				p += i;
@@ -631,12 +604,12 @@ rread(Fid *f)
 			}
 		}
 		n = sizeof buf - n;
-		if (off > n)
+		if(off > n)
 			rhdr.count = 0;
 		else{
 			rhdr.data = buf + off;
 			rhdr.count = n - off;
-			if (rhdr.count > cnt)
+			if(rhdr.count > cnt)
 				rhdr.count = cnt;
 		}
 		return nil;
@@ -647,41 +620,41 @@ rread(Fid *f)
 
 		qlock(f);
 		a = f->fiddata;
-		if (off - a->offoff < 0){
+		if(off - a->offoff < 0){
 			/* there was a seek */
 			a->offoff = off;
 			a->ns = 0;
 		}
 		do {
-			if (off - a->offoff < a->ns){
+			if(off - a->offoff < a->ns){
 				rhdr.data = a->s + (off - a->offoff);
 				rhdr.count = a->ns - (off - a->offoff);
-				if (rhdr.count > cnt)
+				if(rhdr.count > cnt)
 					rhdr.count = cnt;
 				qunlock(f);
 				return nil;
 			}
-			if (a->offoff != off){
+			if(a->offoff != off){
 				a->ns = 0;
 				a->offoff = off;
 				rhdr.count = 0;
 				qunlock(f);
 				return nil;
 			}
-		} while (makeaudioctldata(f) != 0);
+		} while(makeaudioctldata(f) != 0);
 
 		assert(a->offoff == off);
 		/* Wait for data off line */
 		f->readers++;
 		w = nbrecvp(procchan);
-		if (w == nil){
+		if(w == nil){
 			w = emallocz(sizeof(Worker), 1);
 			w->eventc = chancreate(sizeof(ulong), 1);
 			w->next = workers;
 			workers = w;
 			proccreate(readproc, w, 4096);
 		}
-		hdr = emalloc(sizeof(Fcall));
+		hdr = emallocz(sizeof(Fcall), 0);
 		w->fid = f;
 		w->tag = thdr.tag;
 		assert(w->rhdr == nil);
@@ -719,67 +692,61 @@ rwrite(Fid *f)
 		thdr.data[cnt] = '\0';
 		nlines = getfields(thdr.data, lines, 2*Ncontrol, 1, "\n");
 		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);
-			if (nf == 0)
+			if(nf == 0)
 				continue;
-			if (nf == 3)
-				if (strcmp(fields[1], "in") == 0 ||
+			if(nf == 3)
+				if(strcmp(fields[1], "in") == 0 ||
 				    strcmp(fields[1], "record") == 0)
 					rec = 1;
-				else if (strcmp(fields[1], "out") == 0 ||
+				else if(strcmp(fields[1], "out") == 0 ||
 				    strcmp(fields[1], "playback") == 0)
 					rec = 0;
-				else {
-					if (debug)
-						fprint(2, "bad1\n");
+				else{
+					dprint(2, "bad1\n");
 					return Ebadctl;
 				}
-			else if (nf == 2)
+			else if(nf == 2)
 				rec = 0;
-			else {
-				if (debug)
-					fprint(2, "bad2 %d\n", nf);
+			else{
+				dprint(2, "bad2 %d\n", nf);
 				return Ebadctl;
 			}
 			c = nil;
-			if (strcmp(fields[0], "audio") == 0) /* special case */
+			if(strcmp(fields[0], "audio") == 0) /* special case */
 				fields[0] = "volume";
-			for (ctl = 0; ctl < Ncontrol; ctl++){
+			for(ctl = 0; ctl < Ncontrol; ctl++){
 				c = &controls[rec][ctl];
-				if (strcmp(fields[0], c->name) == 0)
+				if(strcmp(fields[0], c->name) == 0)
 					break;
 			}
-			if (ctl == Ncontrol){
-				if (debug)
-					fprint(2, "bad3\n");
+			if(ctl == Ncontrol){
+				dprint(2, "bad3\n");
 				return Ebadctl;
 			}
-			if (f->dir == &dirs[Qvolume] && ctl != Speed_control &&
+			if(f->dir == &dirs[Qvolume] && ctl != Speed_control &&
 			    c->min != Undef && c->max != Undef){
 				nnf = tokenize(fields[nf-1], 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;
 				}
 				p = buf;
-				for (i = 0; i < nnf; i++){
+				for(i = 0; i < nnf; i++){
 					value = strtol(subfields[i], nil, 0);
 					value = ((100 - value)*c->min +
 						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?
 								"record":
 								"playback",
 								value);
-						else
-							fprint(2, " %ld", value);
-					if (p == buf)
+					}else
+						dprint(2, " %ld", value);
+					if(p == buf)
 						p = seprint(p, buf+sizeof buf,
 							"0x%p %s %s '%ld",
 							replchan, c->name, rec?
@@ -789,13 +756,11 @@ rwrite(Fid *f)
 						p = seprint(p, buf+sizeof buf,
 							" %ld", value);
 				}
-				if (debug)
-					fprint(2, "'\n");
+				dprint(2, "'\n");
 				seprint(p, buf+sizeof buf-1, "'");
 				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",
 						fields[nf-1]);
 				chanprint(controlchan, "0x%p %s %s %q",
@@ -803,16 +768,16 @@ rwrite(Fid *f)
 					"playback", fields[nf-1]);
 			}
 			p = recvp(replchan);
-			if (p){
-				if (strcmp(p, "ok") == 0){
+			if(p){
+				if(strcmp(p, "ok") == 0){
 					free(p);
 					p = nil;
 				}
-				if (err == nil)
+				if(err == nil)
 					err = p;
 			}
 		}
-		for (w = workers; w; w = w->next)
+		for(w = workers; w; w = w->next)
 			nbsendul(w->eventc, Qaudioctl << 16 | Check);
 		rhdr.count = thdr.count;
 		return err;
@@ -828,9 +793,9 @@ rclunk(Fid *f)
 	qlock(f);
 	f->flags &= ~(Open|Busy);
 	assert(f->readers ==0);
-	if (f->fiddata){
+	if(f->fiddata){
 		a = f->fiddata;
-		if (a->s)
+		if(a->s)
 			free(a->s);
 		free(a);
 		f->fiddata = nil;
@@ -850,16 +815,12 @@ rstat(Fid *f)
 {
 	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);
-		if (f->fiddata == nil)
+		if(f->fiddata == nil)
 			f->fiddata = allocaudioctldata();
 		a = f->fiddata;
-		if (a->ns == 0)
+		if(a->ns == 0)
 			makeaudioctldata(f);
 		f->dir->length = a->offoff + a->ns;
 		qunlock(f);
@@ -921,9 +882,8 @@ io(void *)
 			continue;
 		if(n < 0){
 			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;
 			}
 			return;
@@ -931,15 +891,14 @@ io(void *)
 		if(convM2S(mdata, n, &thdr) == 0)
 			continue;
 
-		if(debug & Dbgfs)
-			fprint(2, "io:<-%F\n", &thdr);
+		ddprint(2, "io:<-%F\n", &thdr);
 
 		rhdr.data = (char*)mdata + messagesize;
 		if(!fcalls[thdr.type])
 			err = "bad fcall type";
 		else
 			err = (*fcalls[thdr.type])(newfid(thdr.fid));
-		if (err == (char*)~0)
+		if(err == (char*)~0)
 			continue;	/* handled off line */
 		if(err){
 			rhdr.type = Rerror;
@@ -949,8 +908,7 @@ io(void *)
 			rhdr.fid = thdr.fid;
 		}
 		rhdr.tag = thdr.tag;
-		if(debug & Dbgfs)
-			fprint(2, "io:->%F\n", &rhdr);
+		ddprint(2, "io:->%F\n", &rhdr);
 		n = convS2M(&rhdr, mdata, messagesize);
 		if(write(mfd[1], mdata, n) != n)
 			sysfatal("mount write");
@@ -976,6 +934,6 @@ ctlevent(void)
 {
 	Worker *w;
 
-	for (w = workers; w; w = w->next)
+	for(w = workers; w; w = w->next)
 		nbsendul(w->eventc, Qaudioctl << 16 | Check);
 }

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

@@ -2,8 +2,16 @@
 #include <libc.h>
 #include <thread.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[] = {
 	{	0x100, "USB Terminal, undefined type"},
@@ -18,111 +26,124 @@ Namelist terminal_types[] = {
 units[2][8];	/* rec and play units */
 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
 findunit(int nr)
 {
 	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 -1;
 }
 
 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;
 	byte *p;
-	int class, subclass, ifc, dalt;
+	int class, subclass;
 	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
-		switch (b[2]) {
+		switch (b[2]){
 		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[3]>>4)&0xf), '0'+(b[3]&0xf),
 					b[5]|(b[6]<<8), b[7], b[8]);
-			}
 			break;
 		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),
 					namefor(terminal_types, b[4]|(b[5]<<8)),
 					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]);
 				/* USB streaming input: play interface */
 				units[Play][nunits[Play]++] = b[3];
 			}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)));
 				/* Non-USB input: record interface */
 				units[Record][nunits[Record]++] = b[3];
 			}
 			break;
 		case 0x03:	// output
-			if (debug & Dbginfo){
+			if(usbdebug){
 				fprint(2, "Audio Output Terminal Descriptor\n");
 				fprint(2, "\tbTerminalId %d, wTerminalType 0x%x (%s), bAssocTerminal %d bSourceId %d, iTerminal %d\n",
 					b[3], b[4]|(b[5]<<8),
 					namefor(terminal_types, b[4]|(b[5]<<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]);
 				/* USB streaming output: record interface */
 				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)));
 				/* Non-USB output: play interface */
 				units[Play][nunits[Play]++] = b[3];
 			}
 			break;
 		case 0x04:
-			if (verbose)
+			if(verbose)
 				fprint(2, "Audio Mixer Unit %d\n", b[3]);
-			if (debug & Dbginfo){
+			if(usbdebug){
 				fprint(2, "\t%d bytes:", nb);
 				for(ctl = 0; ctl < nb; ctl++)
 					fprint(2, " 0x%2.2x", b[ctl]);
 				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]);
-				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]);
-					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];
-					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];
 				}
-				if (debug & Dbginfo){
+				if(usbdebug){
 					fprint(2, "Channels %d, config %d, ",
 						b[ctl+5], b[ctl+5+1] | b[ctl+5+2] << 8);
 					x = b[ctl+5] * b[4];
@@ -134,25 +155,25 @@ audio_interface(Device *d, int n, ulong csp, void *bb, int nb) {
 			}
 			break;
 		case 0x05:
-			if (verbose)
+			if(verbose)
 				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]);
-				if (debug & Dbginfo) fprint(2, ", baSourceIDs: %s [%d",
+				dprint(2, ", baSourceIDs: %s [%d",
 					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]);
-					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];
-					if (selectorid[u] >= 0)
+					if(selectorid[u] >= 0)
 						fprint(2, "Second selector (%d, %d) on %s\n", selectorid[u], b[3], u?"record":"playback");
 					selectorid[u] = b[3];
 					controls[u][Selector_control].readable = 1;
@@ -162,39 +183,41 @@ audio_interface(Device *d, int n, ulong csp, void *bb, int nb) {
 			}
 			break;
 		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]);
-			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];
-				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];
 			}else
-				if (verbose) fprint(2, ", not known what for\n");
+				if(verbose) fprint(2, ", not known what for\n");
 			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",
 							controls[0][ctl].name);
-					if (u >= 0){
+					if(u >= 0){
 						controls[u][ctl].readable = 1;
 						controls[u][ctl].settable = 1;
 						controls[u][ctl].chans = 0;
 					}
 				}
 			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].settable = 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;
 		default:
-			pcs_raw("audio control unknown", bb, nb);
+			hd = hexstr(bb, nb);
+			fprint(2, "audio control unknown: %s\n", hd);
+			free(hd);
 		}
 		break;
 	case 2: // stream
-		switch (b[2]) {
+		switch (b[2]){
 		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;
 		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);
-				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
-					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 (verbose)
+				if(verbose)
 					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);
 				aa->minfreq = b[8] | b[9]<<8 | b[10]<<16;
 				aa->maxfreq = b[11] | b[12]<<8 | b[13]<<16;
 				aa->caps |= has_contfreq;
 			}else{
-				if (verbose)
+				if(verbose)
 					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;
-					if (verbose)
+					if(verbose)
 						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;
-				if (verbose)
+				if(verbose)
 					fprint(2, "\n");
-				if (ch > 1)
+				if(ch > 1)
 					aa->caps |= has_discfreq;	/* more than one frequency */
 				else
 					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];
 			break;
 		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;
 	case 3: // midi
 	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
 
-TARG=usbaudio
+TARG=audio
 OFILES=\
 	audiofs.$O\
 	audiosub.$O\
-	usbaudio.$O\
-	usbaudioctl.$O\
+	audio.$O\
+	audioctl.$O\
 
 HFILES=\
-	usbaudio.h\
-	usbaudioctl.h\
+	audio.h\
+	audioctl.h\
 	../lib/usb.h\
 
 UPDATE=\

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

@@ -1,1019 +1,712 @@
 /*
  * 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 <libc.h>
 #include <ctype.h>
-#include <bio.h>
 #include <fcall.h>
 #include <thread.h>
-#include <9p.h>
 #include "scsireq.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,
 	Qctl,
-	Qn,
 	Qraw,
 	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;
-struct Dirtab {
+struct Dirtab
+{
 	char	*name;
 	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 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;
-	}
+	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;
 }
 
-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;
-		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 = ums;
+		lun->umsc = lun;
 		lun->lun = i;
-		lun->umsc = lun;			/* pointer to self */
 		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;
 }
 
-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
 umsrequest(Umsc *umsc, ScsiPtr *cmd, ScsiPtr *data, int *status)
 {
 	Cbw cbw;
 	Csw csw;
 	int n;
-	static int seq = 0;
+	Ums *ums;
+
+	ums = umsc->ums;
 
 	memcpy(cbw.signature, "USBC", 4);
-	cbw.tag = ++seq;
+	cbw.tag = ++ums->seq;
 	cbw.datalen = data->count;
 	cbw.flags = data->write? CbwDataOut: CbwDataIn;
 	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;
+	assert(cmd->count <= sizeof(cbw.command));
 	memcpy(cbw.command, cmd->p, 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, " 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)
-			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){
-		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){
-		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",
 			csw.status, csw.dataresidue);
-		if(cbw.command[0] == ScmdRsense) {
+		if(cbw.command[0] == ScmdRsense){
 			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, "\n");
 		}
 	}
-
-	if(csw.status == CswOk)
+	switch(csw.status){
+	case CswOk:
 		*status = STok;
-	else
+		break;
+	case CswFailed:
 		*status = STcheck;
+		break;
+	default:
+		dprint(2, "disk: phase error\n");
+		goto Fail;
+	}
+	ums->nerrs = 0;
 	return data->count - csw.dataresidue;
 
-reset:
-	umsreset(&ums, 0);
+Fail:
 	*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;
 }
 
-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
-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;
-	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)
-		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;
+	Umsc *lun;
 
-	memset(d, 0, sizeof(*d));
-	d->uid = estrdup9p(owner);
-	d->gid = estrdup9p(owner);
+	t = &dirtab[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->mode = t->mode;
+	d->name = t->name;
+	lun = fs->aux;
+	if(path == Qdata)
+		d->length = lun->capacity;
+	else
+		d->length = 0;
 }
 
 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 -1;
 }
 
 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;
+	Umsc *lun;
 
-	path = r->fid->qid.path;
-	switch(TYPE(path)) {
+	path = fid->qid.path & ~fs->qid;
+	lun = fs->aux;
+	switch(path){
 	case Qraw:
-		ums.lun[NUM(path)].phase = Pcmd;
+		lun->phase = Pcmd;
 		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;
-	uchar i;
-	char buf[8192], *p;
+	char buf[1024], *p;
+	char *s;
+	char *e;
 	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:
-		dirread9p(r, dirgen, 0);
-		break;
-	case Qn:
-		dirread9p(r, lungen, &path);
+		count = usbdirread(fs, q, data, count, offset, dirgen, nil);
 		break;
 	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;
 	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:
-			respond(r, "phase error");
-			return;
+			qunlock(ums);
+			werrstr("phase error");
+			return -1;
 		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;
-			n = umsrequest(lun, &lun->cmd, &lun->data, &lun->status);
+			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
 			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;
 		case Pstatus:
 			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;
 			break;
 		}
 		break;
 	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)
 			nb = lun->blocks - bno;
-		if(bno >= lun->blocks || nb == 0) {
-			r->ofcall.count = 0;
+		if(bno >= lun->blocks || nb == 0){
+			count = 0;
 			break;
 		}
 		if(nb * lun->lbsize > maxiosize)
 			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);
-		if(n == -1) {
+		if(n < 0){
 			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);
 		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;
 	char *p;
-	Cmdbuf *cb;
-	Cmdtab *ct;
+	Ums *ums;
 	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:
-		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:
-			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.count = n;
+			lun->cmd.count = count;
 			lun->cmd.write = 1;
 			lun->phase = Pdata;
 			break;
 		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;
-			n = umsrequest(lun, &lun->cmd, &lun->data, &lun->status);
+			count = umsrequest(lun,&lun->cmd,&lun->data,&lun->status);
 			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;
 		case Pstatus:
 			lun->phase = Pcmd;
-			respond(r, "phase error");
-			return;
+			qunlock(ums);
+			werrstr("phase error");
+			return -1;
 		}
 		break;
 	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)
 			nb = lun->blocks - bno;
-		if(bno >= lun->blocks || nb == 0) {
-			r->ofcall.count = 0;
+		if(bno >= lun->blocks || nb == 0){
+			count = 0;
 			break;
 		}
 		if(nb * lun->lbsize > maxiosize)
 			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);
-				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);
-			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);
 		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)
 {
-	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{
 	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;
 	default:
-		usage();
+		return usage();
 	}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
 
 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
 
 </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 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
  */
 enum {
+
+	Stack = 32 * 1024,
+
 	/* HID class subclass protocol ids */
 	PtrCSP		= 0x020103,	/* mouse.boot.hid */
 	KbdCSP		= 0x010103,	/* keyboard.boot.hid */
 
 	/* Requests */
-	SET_PROTO	= 0x0b,
+	Getproto	= 0x03,
+	Setproto	= 0x0b,
 
 	/* protocols for SET_PROTO request */
-	BOOT_PROTO	= 0,
-	REPORT_PROTO	= 1,
+	Bootproto	= 0,
+	Reportproto	= 1,
 };
 
 enum {
 	/* 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] */
-	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 */
 	PtrMask= 0xf,			/* 4 buttons: should allow for more. */
 
-	Awakemsg=0xdeaddead,
 };
 
 /*
@@ -44,14 +47,14 @@ enum {
  */
 enum {
 	/* 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 "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.
  *	{ ptr ->mouse}
@@ -34,7 +62,7 @@
  * key code to scan code; for the page table used by
  * the logitech bluetooth keyboard.
  */
-char sctab[256] = 
+static char sctab[256] = 
 {
 [0x00]	0x0,	0x0,	0x0,	0x0,	0x1e,	0x30,	0x2e,	0x20,
 [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,
 };
 
-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
-scale(int x)
+scale(KDev *f, int x)
 {
 	int sign = 1;
 
@@ -139,10 +151,10 @@ scale(int x)
 	case 3:
 		break;
 	case 4:
-		x = 6 + (accel>>2);
+		x = 6 + (f->accel>>2);
 		break;
 	case 5:
-		x = 9 + (accel>>1);
+		x = 9 + (f->accel>>1);
 		break;
 	default:
 		x *= MaxAcc;
@@ -151,44 +163,54 @@ scale(int 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)
 {
-	int x, y, b, c, mfd, ptrfd;
-	char	buf[32];
-	Dev*	f = a;
 	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(;;){
 		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)
-			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)
 			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];
 			y = buf[2];
 		}
@@ -197,27 +219,28 @@ ptrwork(void* a)
 			b |= 0x08;
 		if(c > 3 && buf[3] == -1)	/* down */
 			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
-stoprepeat(void)
+stoprepeat(KDev *f)
 {
-	sendul(repeatc, Awakemsg);
+	sendul(f->repeatc, Awakemsg);
 }
 
 static void
-startrepeat(uchar esc1, uchar sc)
+startrepeat(KDev *f, uchar esc1, uchar sc)
 {
 	ulong c;
 
@@ -225,76 +248,84 @@ startrepeat(uchar esc1, uchar sc)
 		c = SCesc1 << 8 | (sc & 0xff);
 	else
 		c = sc;
-	sendul(repeatc, c);
+	sendul(f->repeatc, c);
 }
 
-static int kbinfd = -1;
-
 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){
-		hdebug = 1;
+		kbdebug++;
 		return;
 	}
 	if(sc == 0x42){
-		hdebug = 0;
+		kbdebug = 0;
 		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);
-	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
-repeatproc(void*)
+repeatproc(void* a)
 {
-	ulong l;
+	KDev *f;
+	Channel *repeatc;
+	int kbdinfd;
+	ulong l, t, i;
 	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(;;){
-		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))
 
 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))
-		putscan(esc, sc);
+		putscan(fd, esc, sc);
 	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
  * 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;
-	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 */
 	for(i = 2; i < n; i++){
@@ -348,21 +356,28 @@ putkeys(uchar buf[], uchar obuf[], int n)
 			if(buf[i] == obuf[j])
 			 	break;
 		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 */
+	uk = 0;
 	for(i = 2; i < n; i++){
 		for(j = 2; j < n; j++)
 			if(obuf[i] == buf[j])
 				break;
 		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
@@ -376,262 +391,178 @@ kbdbusy(uchar* buf, int n)
 	return 1;
 }
 
-void
+static void
 kbdwork(void *a)
 {
 	int c, i, kbdfd;
 	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);
-	for(;;) {
+	dk = 0;
+	for(;;){
 		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)
 			continue;
 		if(kbdbusy(buf + 2, c - 2))
 			continue;
-		if(hdebug > 1){
+		if(usbdebug > 1 || kbdebug > 1){
 			fprint(2, "kbd mod %x: ", buf[0]);
 			for(i = 2; i < c; i++)
 				fprint(2, "kc %x ", buf[i]);
 			fprint(2, "\n");
 		}
-		putkeys(buf, lbuf, f->msz);
+		dk = putkeys(f, buf, lbuf, f->ep->maxpkt, dk);
 		memmove(lbuf, buf, c);
 	}
 }
 
 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
-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
-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;
 	}
-	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;
 	}
-	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)
 {
-	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{
 	case 'a':
-		accel = strtol(EARGF(usage()), nil, 0);
+		as = ARGF();
+		if(as == nil)
+			return usage();
+		accel = strtol(as, nil, 0);
 		break;
 	case 'd':
-		hdebug++;
-		usbdebug++;
+		kbdebug++;
 		break;
 	case 'k':
-		kf.enabled = 1;
-		pf.enabled = 0;
+		kena = 1;
+		pena = 0;
 		break;
 	case 'm':
-		kf.enabled = 0;
-		pf.enabled = 1;
-		break;
-	case 'n':
-		dryrun = 1;
+		kena = 0;
+		pena = 1;
 		break;
 	default:
-		usage();
+		return usage();
 	}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
 
 TARG=kb
-
-OFILES=\
-	kb.$O\
-
-
+OFILES=main.$O
+LIBDOFILES=kb.$O
 HFILES=\
 	../lib/usb.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
 
+
 UPDATE=\
 	mkfile\
 	$HFILES\
 	${OFILES:%.$O=%.c}\
 
 </sys/src/cmd/mkone
-
 CFLAGS=-I../lib $CFLAGS
 
-$LIB:
+$LIBU:
 	cd ../lib
 	mk install
 	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 "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{
-		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
-			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;
-		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;
+		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;
-
-	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;
-		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
 OFILES=\
-	device.$O\
+	dev.$O\
+	devs.$O\
 	dump.$O\
-	fmt.$O\
-	setup.$O\
-	util.$O\
+	parse.$O\
+	fs.$O\
+	fsdir.$O\
 
 HFILES=\
 	usb.h\
+	usbfs.h\
+
 
 UPDATE=\
 	$HFILES\
@@ -20,3 +23,7 @@ UPDATE=\
 
 install:V:	$LIB
 	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
 {
+	Uctries	= 4,		/* nb. of tries for usbcmd */
+	Ucdelay = 50,		/* delay before retrying */
+
 	/* 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 */
-	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 */
-	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 */
-	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 */
-	Nendpt =	16,
+	Nep = 16,
+	Niface = 16,
+	Naltc = 16,
+	Nddesc = 32,
+	Nconf = 16,
 
 	/* device state */
 	Detached = 0,
@@ -155,11 +96,6 @@ enum
 	Assigned,
 	Configured,
 
-	/* classes */
-	Noclass = 0,
-	Hubclass,
-	Otherclass,
-
 	/* endpoint direction */
 	Ein = 0,
 	Eout,
@@ -176,221 +112,249 @@ enum
 	Easync = 1,
 	Eadapt = 2,
 	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	type;		/* Econtrol, Eiso, Ebulk, Eintr */
-	uchar	isotype;	/* Eunknown, Easync, Eadapt, Esync */
+	uchar	isotype;		/* Eunknown, Easync, Eadapt, Esync */
 	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	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 */
-	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	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
  */
-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 Subclass(csp)	(((csp)>>8)&0xff)
 #define Proto(csp)	(((csp)>>16)&0xff)
 #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=\
 	lib\
-	audio\
 	disk\
 	kb\
-	misc\
+	audio\
 	usbd\
+	ether\
+	print\
+#	knx\
 
 UPDATE=\
 	mkfile\
 
+default:V: all
+
 none:VQ:
 	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) @{
 		cd $i
 		mk $target
 	}
+	cp usbfat: /rc/bin
+	cp probe /$objtype/bin/usb/probe
 
 update:V:
 	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
 OFILES=\
-	hub.$O\
-	setup.$O\
 	usbd.$O\
+	dev.$O\
+	devtab.$O\
 
 HFILES=\
-	dat.h\
-	fns.h\
+	usbd.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=\
 	$HFILES\
@@ -23,8 +27,13 @@ BIN=/$objtype/bin/usb
 </sys/src/cmd/mkone
 
 CFLAGS=-I../lib $CFLAGS
+CLEANFILES=devtab.c
 
-$LIB:
+$LIBU:
 	cd ../lib
 	mk install
 	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 <libc.h>
 #include <thread.h>
+#include <fcall.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;
-	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;
+	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
-		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(;;){
-		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
-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