Browse Source

Plan 9 from Bell Labs 2009-05-29

David du Colombier 15 years ago
parent
commit
b50c5fc0ce

+ 52 - 0
sys/man/4/usb

@@ -6,6 +6,7 @@ ether,
 kb,
 print,
 probe,
+serial,
 usbfat:
 \- Universal Serial Bus device drivers
 .SH SYNOPSIS
@@ -72,6 +73,22 @@ usbfat:
 .I dev ...
 ]
 .PP
+.B usb/serial
+[
+.B -Dd
+]
+[
+.B -m
+.I mnt
+]
+[
+.B -s
+.I srv
+]
+[
+.I dev ...
+]
+.PP
 .B usb/print
 [
 .B -d
@@ -214,6 +231,35 @@ is the device name.
 When started manually, the file interface is mounted at
 .B /net
 as is customary.
+.
+.SS Serial ports
+.I Serial
+provides a file system (usually mounted at
+.BR /dev )
+that includes one directory per USB serial port, named
+.BI eiaU N.
+In this directory there are two files,
+.BI eiaU N /data ,
+similar to
+.BI eia N
+in
+.IR eia (3),
+and
+.BI eiaU N /ctl .
+.I Ctl
+admits writes in the same format as
+.BI eia N ctl
+in
+.IR eia (3).
+Reading from
+.I ctl
+gives the serial port's settings in the same format as
+.BI eia N status
+in
+.IR eia (3).
+Options are similar to those of
+.IR disk .
+.
 .SS Audio devices
 .I Usbaudio
 configures and manages a USB audio device.
@@ -317,6 +363,7 @@ Read and write operations of arbitrary size are allowed.
 .SH SOURCE
 .B /sys/src/cmd/usb
 .SH "SEE ALSO"
+.IR eia (3),
 .IR kbin (3),
 .IR mouse (3),
 .IR sd (3),
@@ -329,6 +376,11 @@ 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.
+The serial driver works only for the Prolific chip, and control of the
+.B dcd
+and
+.B dsr
+signals and some of the extra features are unimplemented.
 .PP
 Not heavily exercised yet.
 The entire set of drivers is new and therefore potentially unreliable.

+ 122 - 0
sys/src/cmd/usb/serial/main.c

@@ -0,0 +1,122 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "serial.h"
+#include "prolific.h"
+
+typedef struct Parg Parg;
+
+enum {
+	Arglen = 80,
+};
+
+Cinfo cinfo[] = {
+	{ PL2303Vid,	PL2303Did },
+	{ PL2303Vid,	PL2303DidRSAQ2 },
+	{ PL2303Vid,	PL2303DidDCU11 },
+	{ PL2303Vid,	PL2303DidRSAQ3 },
+	{ PL2303Vid,	PL2303DidPHAROS },
+	{ PL2303Vid,	PL2303DidALDIGA },
+	{ PL2303Vid,	PL2303DidMMX },
+	{ PL2303Vid,	PL2303DidGPRS },
+	{ IODATAVid,	IODATADid },
+	{ IODATAVid,	IODATADidRSAQ5 },
+	{ ATENVid,	ATENDid },
+	{ ATENVid2,	ATENDid },
+	{ ELCOMVid,	ELCOMDid },
+	{ ELCOMVid,	ELCOMDidUCSGT },
+	{ ITEGNOVid,	ITEGNODid },
+	{ ITEGNOVid,	ITEGNODid2080 },
+	{ MA620Vid,	MA620Did },
+	{ RATOCVid,	RATOCDid },
+	{ TRIPPVid,	TRIPPDid },
+	{ RADIOSHACKVid,RADIOSHACKDid },
+	{ DCU10Vid,	DCU10Did },
+	{ SITECOMVid,	SITECOMDid },
+	{ ALCATELVid,	ALCATELDid },
+	{ SAMSUNGVid,	SAMSUNGDid },
+	{ SIEMENSVid,	SIEMENSDidSX1 },
+	{ SIEMENSVid,	SIEMENSDidX65 },
+	{ SIEMENSVid,	SIEMENSDidX75 },
+	{ SIEMENSVid,	SIEMENSDidEF81 },
+	{ SYNTECHVid,	SYNTECHDid },
+	{ NOKIACA42Vid,	NOKIACA42Did },
+	{ CA42CA42Vid,	CA42CA42Did },
+	{ SAGEMVid,	SAGEMDid },
+	{ LEADTEKVid,	LEADTEK9531Did },
+	{ SPEEDDRAGONVid,SPEEDDRAGONDid },
+	{ DATAPILOTU2Vid,DATAPILOTU2Did },
+	{ BELKINVid,	BELKINDid },
+	{ ALCORVid,	ALCORDid },
+	{ WS002INVid,	WS002INDid },
+	{ COREGAVid,	COREGADid },
+	{ YCCABLEVid,	YCCABLEDid },
+	{ SUPERIALVid,	SUPERIALDid },
+	{ HPVid,	HPLD220Did },
+	{ 0,		0 },
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-d] [-a n] [dev...]\n", argv0);
+	threadexitsall("usage");
+}
+
+static int
+matchserial(char *info, void*)
+{
+	Cinfo *ip;
+	char buf[50];
+
+	for(ip = cinfo; ip->vid != 0; ip++){
+		snprint(buf, sizeof buf, "vid %#06x did %#06x",
+			ip->vid, ip->did);
+		dsprint(2, "serial: %s %s", buf, info);
+		if(strstr(info, buf) != nil)
+			return 0;
+	}
+	return -1;
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char *mnt, *srv, *as, *ae;
+	char args[Arglen];
+
+	mnt = "/dev";
+	srv = nil;
+
+	quotefmtinstall();
+	ae = args + sizeof args;
+	as = seprint(args, ae, "serial");
+	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());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND;
+
+	rfork(RFNOTEG);
+	fmtinstall('U', Ufmt);
+	threadsetgrp(threadid());
+
+	usbfsinit(srv, mnt, &usbdirfs, MAFTER|MCREATE);
+	startdevs(args, argv, argc, matchserial, nil, serialmain);
+	threadexits(nil);
+}

+ 36 - 0
sys/src/cmd/usb/serial/mkfile

@@ -0,0 +1,36 @@
+</$objtype/mkfile
+
+TARG=serial
+OFILES=main.$O
+LIBDOFILES=serial.$O prolific.$O
+HFILES=\
+	../lib/usb.h\
+	prolific.h\
+	serial.h\
+
+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
+
+$LIBU:
+	cd ../lib
+	mk install
+	mk clean
+
+$LIBD:V: $LIBDOFILES
+	ar vu $LIBD $newprereq
+	rm $newprereq
+

+ 298 - 0
sys/src/cmd/usb/serial/prolific.c

@@ -0,0 +1,298 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "serial.h"
+#include "prolific.h"
+
+static void	statusreader(void *u);
+
+static void
+dumpbuf(uchar *buf, int bufsz)
+{
+	int i;
+
+	for(i=0; i<bufsz; i++)
+		print("buf[%d]=%#ux ", i, buf[i]);
+	print("\n");
+}
+
+static int
+vendorread(Serial *ser, int val, int index, uchar *buf)
+{
+	int res;
+
+	dsprint(2, "serial: vendorread val: 0x%x idx:%d buf:%p\n",
+		val, index, buf);
+	res = usbcmd(ser->dev,  Rd2h | Rvendor | Rdev, VendorReadReq,
+		val, index, buf, 1);
+	dsprint(2, "serial: vendorread res:%d\n", res);
+	return res;
+}
+
+static int
+vendorwrite(Serial *ser, int val, int index)
+{
+	int res;
+
+	dsprint(2, "serial: vendorwrite val: 0x%x idx:%d\n", val, index);
+	res = usbcmd(ser->dev, Rh2d | Rvendor | Rdev, VendorWriteReq,
+		val, index, nil, 0);
+	dsprint(2, "serial: vendorwrite res:%d\n", res);
+	return res;
+}
+
+static int
+plgetparam(Serial *ser)
+{
+	uchar buf[7];
+	int res;
+
+	res = usbcmd(ser->dev, Rd2h | Rclass | Riface, GetLineReq,
+		0, 0, buf, sizeof buf);
+	ser->baud = GET4(buf);
+
+	/*
+	 * with the Pl9 interface it is not possible to set `1.5' as stop bits
+	 * for the prologic:
+	 *	0 is 1 stop bit
+	 *	1 is 1.5 stop bits
+	 *	2 is 2 stop bits
+	 */
+	if(buf[4] == 1)
+		fprint(2, "warning, stop bit set to 1.5 unsupported");
+	else if(buf[4] == 0)
+		ser->stop = 1;
+	else if(buf[4] == 2)
+		ser->stop = 2;
+	ser->parity = buf[5];
+	ser->bits = buf[6];
+
+	dsprint(2, "serial: getparam: ");
+	if(serialdebug)
+		dumpbuf(buf, sizeof buf);
+	dsprint(2, "serial: getparam res: %d\n", res);
+	return res;
+}
+
+static void
+plmodemctl(Serial *ser, int set)
+{
+	if(set == 0){
+		ser->mctl = 0;
+		vendorwrite(ser, 0x0, 0x0);
+		return;
+	}
+
+	ser->mctl = 1;
+	if(ser->type == TypeHX)
+		vendorwrite(ser, 0x0, Dcr0InitX);
+	else
+		vendorwrite(ser, 0x0, Dcr0InitH);
+}
+
+static int
+plsetparam(Serial *ser)
+{
+	uchar buf[7];
+	int res;
+
+	PUT4(buf, ser->baud);
+
+	if(ser->stop == 1)
+		buf[4] = 0;
+	else if(ser->stop == 2)
+		buf[4] = 2; 			/* see comment in getparam */
+	buf[5] = ser->parity;
+	buf[6] = ser->bits;
+
+	dsprint(2, "serial: setparam: ");
+	if(serialdebug)
+		dumpbuf(buf, sizeof buf);
+	res = usbcmd(ser->dev, Rh2d | Rclass | Riface, SetLineReq,
+		0, 0, buf, sizeof buf);
+	plmodemctl(ser, ser->mctl);
+	plgetparam(ser);		/* make sure our state corresponds */
+
+	dsprint(2, "serial: setparam res: %d\n", res);
+	return res;
+}
+
+static int
+plinit(Serial *ser)
+{
+	ulong csp;
+	char *st;
+	uchar *buf;
+
+	buf = emallocz(VendorReqSize, 1);
+	qlock(ser);
+	serialreset(ser);
+	csp = ser->dev->usb->csp;
+
+	if(Class(csp) == 0x02)
+		ser->type = Type0;
+	else if(ser->dev->maxpkt == 0x40)
+		ser->type = TypeHX;
+	else if(Class(csp) == 0x00 || Class(csp) == 0xFF)
+		ser->type = Type1;
+
+	if(ser->type != ser->dev->usb->psid)
+		fprint(2, "serial: warning, heuristics: %#ux and psid: "
+			"%#ux, not a match\n", ser->type, ser->dev->usb->psid);
+	dsprint(2, "serial: type %d\n", ser->type);
+
+	vendorread(ser, 0x8484, 0, buf);
+	vendorwrite(ser, 0x0404, 0);
+	vendorread(ser, 0x8484, 0, buf);
+	vendorread(ser, 0x8383, 0, buf);
+	vendorread(ser, 0x8484, 0, buf);
+	vendorwrite(ser, 0x0404, 1);
+	vendorread(ser, 0x8484, 0, buf);
+	vendorread(ser, 0x8383, 0, buf);
+	vendorwrite(ser, 0, 1);
+	vendorwrite(ser, 1, 0);
+
+	if(ser->type == TypeHX)
+		vendorwrite(ser, 2, Dcr2InitX);
+	else
+		vendorwrite(ser, 2, Dcr2InitH);
+
+	plgetparam(ser);
+	qunlock(ser);
+	free(buf);
+	st = emallocz(255, 1);
+	qlock(ser);
+	if(serialdebug)
+		dumpstatus(ser, st, 255);
+	dsprint(2, st);
+	qunlock(ser);
+	free(st);
+	/* ser gets freed by closedev, the process has a reference */
+	incref(ser->dev);
+	proccreate(statusreader, ser, 8*1024);
+	return 0;
+}
+
+static int
+plsetbreak(Serial *ser, int val)
+{
+	return usbcmd(ser->dev, Rh2d | Rclass | Riface,
+		(val != 0? BreakOn: BreakOff), val, 0, nil, 0);
+}
+
+static void
+plclearpipes(Serial *ser)
+{
+	if(ser->type == TypeHX){
+		vendorwrite(ser, 8, 0x0);
+		vendorwrite(ser, 9, 0x0);
+	}else{
+		if(unstall(ser->dev, ser->epout, Eout) < 0)
+			dprint(2, "disk: unstall epout: %r\n");
+		if(unstall(ser->dev, ser->epin, Ein) < 0)
+			dprint(2, "disk: unstall epin: %r\n");
+		if(unstall(ser->dev, ser->epintr, Ein) < 0)
+			dprint(2, "disk: unstall epintr: %r\n");
+	}
+}
+
+static int
+setctlline(Serial *ser, uchar val)
+{
+	return usbcmd(ser->dev, Rh2d | Rclass | Riface, SetCtlReq,
+		val, 0, nil, 0);
+}
+
+static void
+composectl(Serial *ser)
+{
+	if(ser->rts)
+		ser->ctlstate |= CtlRTS;
+	else
+		ser->ctlstate &= ~CtlRTS;
+	if(ser->dtr)
+		ser->ctlstate |= CtlDTR;
+	else
+		ser->ctlstate &= ~CtlDTR;
+}
+
+void
+plsendlines(Serial *ser)
+{
+	int res;
+
+	dsprint(2, "serial: sendlines: %#2.2x\n", ser->ctlstate);
+	composectl(ser);
+	res = setctlline(ser, ser->ctlstate);
+	dsprint(2, "serial: getparam res: %d\n", res);
+}
+
+static int
+plreadstatus(Serial *ser)
+{
+	int nr, dfd;
+	char err[40];
+	uchar buf[10];
+
+	qlock(ser);
+	dsprint(2, "serial: reading from interrupt\n");
+	dfd = ser->epintr->dfd;
+
+	qunlock(ser);
+	nr = read(dfd, buf, sizeof buf);
+	qlock(ser);
+	snprint(err, sizeof err, "%r");
+	dsprint(2, "serial: interrupt read %d %r\n", nr);
+
+	if(nr < 0 && strstr(err, "timed out") != nil){
+		dsprint(2, "serial: need to recover, status read %d %r\n", nr);
+		if(serialrecover(ser, err) < 0){
+			qunlock(ser);
+			return -1;
+		}
+	}
+	if(nr < 0)
+		dsprint(2, "serial: reading status: %r");
+	else if(nr >= sizeof buf - 1){
+		ser->dcd = buf[8] & DcdStatus;
+		ser->dsr = buf[8] & DsrStatus;
+		ser->cts = buf[8] & BreakerrStatus;
+		ser->ring = buf[8] & RingStatus;
+		ser->cts = buf[8] & CtsStatus;
+		if (buf[8] & FrerrStatus)
+			ser->nframeerr++;
+		if (buf[8] & ParerrStatus)
+			ser->nparityerr++;
+		if (buf[8] & OvererrStatus)
+			ser->novererr++;
+	} else
+		dsprint(2, "serial: bad status read %d\n", nr);
+	dsprint(2, "serial: finished read from interrupt %d\n", nr);
+	qunlock(ser);
+	return 0;
+}
+
+static void
+statusreader(void *u)
+{
+	Serial *ser;
+
+	ser = u;
+	threadsetname("statusreaderproc");
+
+	while(plreadstatus(ser) > 0)
+		;
+	closedev(ser->dev);
+}
+
+Serialops plops = {
+	.init =		plinit,
+	.getparam =	plgetparam,
+	.setparam =	plsetparam,
+	.clearpipes =	plclearpipes,
+	.sendlines =	plsendlines,
+	.modemctl =	plmodemctl,
+	.setbreak =	plsetbreak,
+};

+ 167 - 0
sys/src/cmd/usb/serial/prolific.h

@@ -0,0 +1,167 @@
+enum {
+	/* flavours of the device */
+	Type0,
+	Type1,
+	TypeHX,
+
+	/* usbcmd parameters */
+	SetLineReq = 0x20,
+
+	SetCtlReq = 0x22,
+	CtlDTR = 0x01,
+	CtlRTS = 0x02,
+
+	BreakReq = 0x23,
+	BreakOn = 0xffff,
+	BreakOff = 0x0000,
+
+	GetLineReq = 0x21,
+
+	VendorWriteReq = 0x01,	/* BUG: is this a standard request? */
+	VendorReadReq = 0x01,
+
+	VendorReqSize = 10,
+
+	/* status read from interrupt endpoint */
+	DcdStatus =	0x01,
+	DsrStatus =	0x02,
+	BreakerrStatus=	0x04,
+	RingStatus =	0x08,
+	FrerrStatus =	0x10,
+	ParerrStatus =	0x20,
+	OvererrStatus =	0x40,
+	CtsStatus =	0x80,
+
+	/*
+	 * flow control bits, Dcr0InitH
+	 * I think, composed this list from various drivers and specs.
+	 * FlowOutCts =	0x0001,
+	 * FlowOutDsr =	0x0002,
+	 * FlowInDsr =	0x0004,
+	 * FlowInDtr =	0x0008,
+	 * FlowInRts =	0x0010,
+	 * FlowOutRts =	0x0020,
+	 * FlowOutXon =	0x0080,
+	 * FlowInXon =	0x0100,
+	 */
+
+	Dcr0InitH = 0x0041,
+	Dcr0InitX = 0x0061,
+	Dcr1InitH = 0x0080,
+	Dcr1InitX = 0x0000,
+	Dcr2InitH = 0x0024,
+	Dcr2InitX = 0x0044,
+};
+
+enum {
+	PL2303Vid =	0x067b,
+	PL2303Did =	0x2303,
+	PL2303DidRSAQ2=	0x04bb,
+	PL2303DidDCU11=	0x1234,
+	PL2303DidPHAROS=0xaaa0,
+	PL2303DidRSAQ3=	0xaaa2,
+	PL2303DidALDIGA=0x0611,
+	PL2303DidMMX =	0x0612,
+	PL2303DidGPRS =	0x0609,
+
+	ATENVid =	0x0557,
+	ATENVid2 =	0x0547,
+	ATENDid =	0x2008,
+
+	IODATAVid =	0x04bb,
+	IODATADid =	0x0a03,
+	IODATADidRSAQ5=	0x0a0e,
+
+	ELCOMVid =	0x056e,
+	ELCOMDid =	0x5003,
+	ELCOMDidUCSGT =	0x5004,
+
+	ITEGNOVid =	0x0eba,
+	ITEGNODid =	0x1080,
+	ITEGNODid2080 =	0x2080,
+
+	MA620Vid =	0x0df7,
+	MA620Did =	0x0620,
+
+	RATOCVid =	0x0584,
+	RATOCDid =	0xb000,
+
+	TRIPPVid =	0x2478,
+	TRIPPDid =	0x2008,
+
+	RADIOSHACKVid =	0x1453,
+	RADIOSHACKDid =	0x4026,
+
+	DCU10Vid =	0x0731,
+	DCU10Did =	0x0528,
+
+	SITECOMVid =	0x6189,
+	SITECOMDid =	0x2068,
+
+	 /* Alcatel OT535/735 USB cable */
+	ALCATELVid =	0x11f7,
+	ALCATELDid =	0x02df,
+
+	/* Samsung I330 phone cradle */
+	SAMSUNGVid =	0x04e8,
+	SAMSUNGDid =	0x8001,
+
+	SIEMENSVid =	0x11f5,
+	SIEMENSDidSX1 =	0x0001,
+	SIEMENSDidX65 =	0x0003,
+	SIEMENSDidX75 =	0x0004,
+	SIEMENSDidEF81=	0x0005,
+
+	SYNTECHVid =	0x0745,
+	SYNTECHDid =	0x0001,
+
+	/* Nokia CA-42 Cable */
+	NOKIACA42Vid =	0x078b,
+	NOKIACA42Did =	0x1234,
+
+	/* CA-42 CLONE Cable www.ca-42.com chipset: Prolific Technology Inc */
+	CA42CA42Vid =	0x10b5,
+	CA42CA42Did =	0xac70,
+
+	SAGEMVid =	0x079b,
+	SAGEMDid =	0x0027,
+
+	/* Leadtek GPS 9531 (ID 0413:2101) */
+	LEADTEKVid =	0x0413,
+	LEADTEK9531Did=	0x2101,
+
+	 /* USB GSM cable from Speed Dragon Multimedia, Ltd */
+	SPEEDDRAGONVid=	0x0e55,
+	SPEEDDRAGONDid=	0x110b,
+
+	/* DATAPILOT Universal-2 Phone Cable */
+	BELKINVid =	0x050d,
+	BELKINDid =	0x0257,
+
+	/* Belkin "F5U257" Serial Adapter */
+	DATAPILOTU2Vid=	0x0731,
+	DATAPILOTU2Did=	0x2003,
+
+	ALCORVid =	0x058F,
+	ALCORDid =	0x9720,
+
+	/* Willcom WS002IN Data Driver (by NetIndex Inc.) */,
+	WS002INVid =	0x11f6,
+	WS002INDid =	0x2001,
+
+	/* Corega CG-USBRS232R Serial Adapter */,
+	COREGAVid =	0x07aa,
+	COREGADid =	0x002a,
+
+	/* Y.C. Cable U.S.A., Inc - USB to RS-232 */,
+	YCCABLEVid =	0x05ad,
+	YCCABLEDid =	0x0fba,
+
+	/* "Superial" USB - Serial */,
+	SUPERIALVid =	0x5372,
+	SUPERIALDid =	0x2303,
+
+	/* Hewlett-Packard LD220-HP POS Pole Display */,
+	HPVid =		0x03f0,
+	HPLD220Did =	0x3524,
+};

+ 615 - 0
sys/src/cmd/usb/serial/serial.c

@@ -0,0 +1,615 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "serial.h"
+
+int serialdebug;
+
+enum {
+	/* Qids. Maintain order (relative to dirtabs structs) */
+	Qroot	= 0,
+	Qctl,
+	Qdata,
+	Qmax,
+};
+
+typedef struct Dirtab Dirtab;
+struct Dirtab {
+	char	*name;
+	int	mode;
+};
+
+static Dirtab dirtab[] = {
+	[Qroot]	"/",	DMDIR|0555,
+	[Qctl]	"ctl",	0444,
+	[Qdata]	"data",	0640,
+};
+
+static int sdebug;
+
+static void
+serialfatal(Serial *ser)
+{
+	dsprint(2, "serial: fatal error, detaching\n");
+	devctl(ser->dev, "detach");
+	usbfsdel(&ser->fs);
+}
+
+/* I sleep with the lock... only way to drain */
+static void
+serialdrain(Serial *ser)
+{
+	uint baud;
+
+	baud = ser->baud;
+	/* wait for the 256-byte pipe to clear */
+	sleep(10 + 256/((1 + baud)*1000));
+	ser->clearpipes(ser);
+}
+
+int
+serialreset(Serial *ser)
+{
+	/* cmd for reset */
+	dsprint(2, "serial: error, resetting\n");
+	serialdrain(ser);
+	return 0;
+}
+
+/*call this if something goes wrong */
+int
+serialrecover(Serial *ser, char *err)
+{
+	static int recover = 0;
+
+	if(strstr(err, "detached") != nil)
+		return -1;
+	if(recover > 1)
+		serialfatal(ser);
+	recover++;
+	if(serialreset(ser) < 0)
+		return -1;
+	recover = 0;
+	return 0;
+}
+
+static int
+serialctl(Serial *p, char *cmd)
+{
+	int c, i, n, nf, nop, nw, par, drain, set, lines;
+	char *f[16];
+	uchar x;
+
+	drain = set = lines = 0;
+	nf = tokenize(cmd, f, nelem(f));
+	for(i = 0; i < nf; i++){
+		if(strncmp(f[i], "break", 5) == 0){
+			p->setbreak(p, 1);
+			continue;
+		}
+
+		nop = 0;
+		n = atoi(f[i]+1);
+		c = *f[i];
+		if (isascii(c) && isupper(c))
+			c = tolower(c);
+		switch(c){
+		case 'b':
+			drain++;
+			p->baud = n;
+			set++;
+			break;
+		case 'c':
+			p->dcd = n;
+			// lines++;
+			++nop;
+			break;
+		case 'd':
+			p->dtr = n;
+			lines++;
+			break;
+		case 'e':
+			p->dsr = n;
+			// lines++;
+			++nop;
+			break;
+		case 'f':		/* flush the pipes */
+			drain++;
+			break;
+		case 'h':		/* hangup?? */
+			p->rts = p->dtr = 0;
+			lines++;
+			fprint(2, "serial: %c, unsure ctl\n", c);
+			break;
+		case 'i':
+			++nop;
+			break;
+		case 'k':
+			drain++;
+			p->setbreak(p, 1);
+			sleep(n);
+			p->setbreak(p, 0);
+			break;
+		case 'l':
+			drain++;
+			p->bits = n;
+			set++;
+			break;
+		case 'm':
+			drain++;
+			p->modemctl(p, n);
+			if(n == 0)
+				p->cts = 0;
+			break;
+		case 'n':
+			p->blocked = n;
+			++nop;
+			break;
+		case 'p':		/* extended... */
+			if(strlen(f[i]) != 2)
+				return -1;
+			drain++;
+			par = f[i][2];
+			if(par == 'n')
+				p->parity = 0;
+			else if(par == 'o')
+				p->parity = 1;
+			else if(par == 'e')
+				p->parity = 2;
+			else if(par == 'm')	/* mark parity */
+				p->parity = 3;
+			else if(par == 's')	/* space parity */
+				p->parity = 4;
+			else
+				return -1;
+			set++;
+			break;
+		case 'q':
+			// drain++;
+			p->limit = n;
+			++nop;
+			break;
+		case 'r':
+			drain++;
+			p->rts = n;
+			lines++;
+			break;
+		case 's':
+			drain++;
+			p->stop = n;
+			set++;
+			break;
+		case 'w':
+			/* ?? how do I put this */
+			p->timer = n * 100000LL;
+			++nop;
+			break;
+		case 'x':
+			if(n == 0)
+				x = CTLS;
+			else
+				x = CTLQ;
+
+			nw = write(p->epout->dfd, &x, 1);
+			if(nw != 1){
+				serialrecover(p, "");
+				return -1;
+			}
+			break;
+		}
+		if (nop)
+			fprint(2, "serial: %c, unsupported nop ctl\n", c);
+	}
+	if(drain)
+		serialdrain(p);
+	if(lines && !set)
+		p->sendlines(p);
+	else if(set && p->setparam(p) < 0)
+		return -1;
+	return 0;
+}
+
+char *pformat = "noems";
+
+char *
+dumpstatus(Serial *ser, char *buf, int bufsz)
+{
+	char *e, *s;
+
+	e = buf + bufsz;
+	s = seprint(buf, e, "b%d ", ser->baud);
+	s = seprint(s, e, "c%d ", ser->dcd);	/* unimplemented */
+	s = seprint(s, e, "d%d ", ser->dtr);
+	s = seprint(s, e, "e%d ", ser->dsr);	/* unimplemented */
+	s = seprint(s, e, "l%d ", ser->bits);
+	s = seprint(s, e, "m%d ", ser->mctl);
+	if(ser->parity >= 0 || ser->parity < strlen(pformat))
+		s = seprint(s, e, "p%c ", pformat[ser->parity]);
+	else
+		s = seprint(s, e, "p%c ", '?');
+	s = seprint(s, e, "r%d ", ser->rts);
+	s = seprint(s, e, "s%d ", ser->stop);
+	s = seprint(s, e, "i%d ", ser->fifo);
+	s = seprint(s, e, "\ndev(%d) ", 0);
+	s = seprint(s, e, "type(%d)  ", ser->type);
+	s = seprint(s, e, "framing(%d) ", ser->nframeerr);
+	s = seprint(s, e, "overruns(%d) ", ser->novererr);
+	s = seprint(s, e, "berr(%d) ", ser->nbreakerr);
+	s = seprint(s, e, " serr(%d) ", ser->nparityerr);
+	return s;
+}
+
+static int
+serinit(Serial *ser)
+{
+	int res;
+
+	res = ser->init(ser);
+	ser->nframeerr = ser->nparityerr = ser->nbreakerr = ser->novererr = 0;
+	return res;
+}
+
+static int
+dwalk(Usbfs *fs, Fid *fid, char *name)
+{
+	int i;
+	char *dname;
+	Qid qid;
+	Serial *ser;
+
+	qid = fid->qid;
+	if((qid.type & QTDIR) == 0){
+		werrstr("walk in non-directory");
+		return -1;
+	}
+
+	if(strcmp(name, "..") == 0){
+		/* must be /eiaU%d; i.e. our root dir. */
+		fid->qid.path = Qroot | fs->qid;
+		fid->qid.vers = 0;
+		fid->qid.type = QTDIR;
+		return 0;
+	}
+
+	ser = fs->aux;
+	for(i = 1; i < nelem(dirtab); i++){
+		dname = smprint(dirtab[i].name, ser->fs.name);
+		if(strcmp(name, dname) == 0){
+			qid.path = i | fs->qid;
+			qid.vers = 0;
+			qid.type = dirtab[i].mode >> 24;
+			fid->qid = qid;
+			free(dname);
+			return 0;
+		} else
+			free(dname);
+	}
+	werrstr(Enotfound);
+	return -1;
+}
+
+static void
+dostat(Usbfs *fs, int path, Dir *d)
+{
+	Dirtab *t;
+	Serial *ser;
+
+	t = &dirtab[path];
+	d->qid.path = path;
+	d->qid.type = t->mode >> 24;
+	d->mode = t->mode;
+	ser = fs->aux;
+
+	if(strcmp(t->name, "/") == 0)
+		d->name = t->name;
+	else
+		snprint(d->name, Namesz, t->name, ser->fs.name);
+
+	d->length = 0;
+}
+
+static int
+dstat(Usbfs *fs, Qid qid, Dir *d)
+{
+	int path;
+
+	path = qid.path & ~fs->qid;
+	dostat(fs, path, d);
+	d->qid.path |= fs->qid;
+	return 0;
+}
+
+static int
+dopen(Usbfs *fs, Fid *fid, int)
+{
+	ulong path;
+	// Serial *ser;
+
+	path = fid->qid.path & ~fs->qid;
+	// ser = fs->aux;
+	switch(path){		/* BUG: unneeded? */
+	case Qdata:
+		dsprint(2, "serial, opened data");
+		break;
+	case Qctl:
+		dsprint(2, "serial, opened ctl");
+		break;
+	}
+	return 0;
+}
+
+
+static void
+filldir(Usbfs *fs, Dir *d, Dirtab *tab, int i)
+{
+	d->qid.path = i | fs->qid;
+	d->mode = tab->mode;
+	if((d->mode & DMDIR) != 0)
+		d->qid.type = QTDIR;
+	else
+		d->qid.type = QTFILE;
+	d->name = tab->name;
+}
+
+static int
+dirgen(Usbfs *fs, Qid, int i, Dir *d, void *)
+{
+	Dirtab *tab;
+
+	i++;				/* skip root */
+	if(i < nelem(dirtab))
+		tab = &dirtab[i];
+	else
+		return -1;
+	filldir(fs, d, tab, i);
+	return 0;
+}
+
+
+static long
+dread(Usbfs *fs, Fid *fid, void *data, long count, vlong offset)
+{
+	int dfd;
+	long rcount;
+	ulong path;
+	char *buf, *err;	/* change */
+	char *e;
+	Qid q;
+	Serial *ser;
+
+	q = fid->qid;
+	path = fid->qid.path & ~fs->qid;
+	ser = fs->aux;
+
+	buf = emallocz(255, 1);
+	err = emallocz(255, 1);
+	qlock(ser);
+	switch(path){
+	case Qroot:
+		count = usbdirread(fs, q, data, count, offset, dirgen, nil);
+		break;
+	case Qdata:
+		do {
+			dsprint(2, "serial: reading from data\n");
+			dfd = ser->epin->dfd;
+			qunlock(ser);
+			rcount = read(dfd, data, count);
+			qlock(ser);
+			snprint(err, 255, "%r");
+			dsprint(2, "serial: reading data read %ld %r\n", count);
+		} while(rcount < 0 && strstr(err, "timed out") != nil);
+
+		if(rcount < 0){
+			dsprint(2, "serial: need to recover, data read %ld %r\n",
+				count);
+			serialrecover(ser, err);
+		}
+		dsprint(2, "serial: read from bulk %ld\n", rcount);
+		count = rcount;
+		break;
+	case Qctl:
+		if(offset != 0){
+			count = 0;
+			break;
+		}
+		e = dumpstatus(ser, buf, 255);
+		count = usbreadbuf(data, count, 0, buf, e - buf);
+		break;
+	}
+	qunlock(ser);
+	free(err);
+	free(buf);
+	return count;
+}
+
+static long
+dwrite(Usbfs *fs, Fid *fid, void *buf, long count, vlong)
+{
+	int nw;
+	ulong path;
+	char *cmd;
+	char err[40];
+	Serial *ser;
+
+	ser = fs->aux;
+	path = fid->qid.path & ~fs->qid;
+
+	qlock(ser);
+	switch(path){
+	case Qdata:
+		nw = write(ser->epout->dfd, buf, count);
+		if(nw != count){
+			dsprint(2, "serial: need to recover, status read %d %r\n",
+				nw);
+			snprint(err, sizeof err, "%r");
+			serialrecover(ser, err);
+		}
+		count = nw;
+		break;
+	case Qctl:
+		cmd = emallocz(count+1, 1);
+		memmove(cmd, buf, count);
+		cmd[count] = 0;
+		if(serialctl(ser, cmd) < 0){
+			qunlock(ser);
+			werrstr(Ebadctl);
+			free(cmd);
+			return -1;
+		}
+		free(cmd);
+		break;
+	default:
+		qunlock(ser);
+		werrstr(Eperm);
+		return -1;
+	}
+	qunlock(ser);
+	return count;
+}
+
+static int
+openeps(Serial *ser, int epin, int epout, int epintr)
+{
+	ser->epin = openep(ser->dev, epin);
+	if(ser->epin == nil){
+		fprint(2, "serial: openep %d: %r\n", epin);
+		return -1;
+	}
+	ser->epout = openep(ser->dev, epout);
+	if(ser->epout == nil){
+		fprint(2, "serial: openep %d: %r\n", epout);
+		closedev(ser->epin);
+		return -1;
+	}
+	ser->epintr = openep(ser->dev, epintr);
+	if(ser->epintr == nil){
+		fprint(2, "serial: openep %d: %r\n", epintr);
+		closedev(ser->epin);
+		closedev(ser->epout);
+		return -1;
+	}
+
+	opendevdata(ser->epin, OREAD);
+	opendevdata(ser->epout, OWRITE);
+	opendevdata(ser->epintr, OREAD);
+	if(ser->epin->dfd < 0 || ser->epout->dfd < 0 || ser->epintr->dfd < 0){
+		fprint(2, "serial: open i/o ep data: %r\n");
+		closedev(ser->epin);
+		closedev(ser->epout);
+		closedev(ser->epintr);
+		return -1;
+	}
+	return 0;
+}
+
+static int
+findendpoints(Serial *ser)
+{
+	int i, epin, epout, epintr;
+	Ep *ep;
+	Usbdev *ud;
+
+	epintr = epin = epout = -1;
+	ud = ser->dev->usb;
+	for(i = 0; i < nelem(ud->ep); i++){
+		if((ep = ud->ep[i]) == nil)
+			continue;
+		if(ep->type == Eintr && ep->dir == Ein && epintr == -1)
+			epintr = ep->id;
+		if(ep->type == Ebulk){
+			if(ep->dir == Ein && epin == -1)
+				epin = ep->id;
+			if(ep->dir == Eout && epout == -1)
+				epout = ep->id;
+		}
+	}
+	dprint(2, "serial: ep ids: in %d out %d intr %d\n", epin, epout, epintr);
+	if(epin == -1 || epout == -1 || epintr == -1)
+		return -1;
+	if(openeps(ser, epin, epout, epintr) < 0)
+		return -1;
+
+	dprint(2, "disk: ep in %s out %s intr %s\n",
+		ser->epin->dir, ser->epout->dir, ser->epintr->dir);
+
+	if(usbdebug > 1 || serialdebug > 2){
+		devctl(ser->epin, "debug 1");
+		devctl(ser->epout, "debug 1");
+		devctl(ser->epintr, "debug 1");
+		devctl(ser->dev, "debug 1");
+	}
+	return 0;
+}
+
+static int
+usage(void)
+{
+	werrstr("usage: usb/serial [-dkmn] [-a n]");
+	return -1;
+}
+
+static void
+serdevfree(void *a)
+{
+	Serial *ser = a;
+
+	if(ser == nil)
+		return;
+	closedev(ser->epintr);
+	closedev(ser->epin);
+	closedev(ser->epout);
+	ser->epintr = ser->epin = ser->epout = nil;
+	free(ser);
+}
+
+static Usbfs serialfs = {
+	.walk =	dwalk,
+	.open =	dopen,
+	.read =	dread,
+	.write=	dwrite,
+	.stat =	dstat,
+};
+
+int
+serialmain(Dev *dev, int argc, char* argv[])
+{
+	Serial *ser;
+
+	ARGBEGIN{
+	case 'd':
+		serialdebug++;
+		break;
+	default:
+		return usage();
+	}ARGEND
+	if(argc != 0)
+		return usage();
+
+	ser = dev->aux = emallocz(sizeof(Serial), 1);
+	ser->dev = dev;
+	dev->free = serdevfree;
+	if(findendpoints(ser) < 0){
+		werrstr("serial: endpoints not found");
+		return -1;
+	}
+
+	ser->fs = serialfs;
+	ser->Serialops = plops;	/* for the moment, only one flavour */
+
+	if(serinit(ser) < 0){
+		dprint(2, "serial: serinit: %r\n");
+		return -1;
+	}
+
+	snprint(ser->fs.name, sizeof(ser->fs.name), "eiaU%d", dev->id);
+	ser->fs.dev = dev;
+	incref(dev);
+	ser->fs.aux = ser;
+	usbfsadd(&ser->fs);
+
+	closedev(dev);
+	return 0;
+}

+ 80 - 0
sys/src/cmd/usb/serial/serial.h

@@ -0,0 +1,80 @@
+typedef struct Serialops Serialops;
+typedef struct Serial Serial;
+
+struct Serialops {
+	int	(*init)(Serial*);
+	int	(*getparam)(Serial*);
+	int	(*setparam)(Serial*);
+	void	(*clearpipes)(Serial*);
+	void	(*sendlines)(Serial*);
+	void	(*modemctl)(Serial*, int);
+	int	(*setbreak)(Serial*, int);
+	void	(*readstatus)(Serial*);
+};
+
+struct Serial {
+	QLock;
+	Dev	*dev;		/* usb device*/
+	Dev	*ep;		/* endpoint to get events */
+	Dev	*epintr;
+	Dev	*epin;
+	Dev	*epout;
+	Usbfs	fs;
+	int	type;
+
+	uchar	ctlstate;
+	/* serial parameters */
+	uint	baud;
+	int	stop;
+	int	mctl;
+	int	parity;
+	int	bits;
+	int	fifo;
+	int	limit;
+	int	rts;
+	int	cts;
+	int	dsr;
+	int	dcd;
+	int	dtr;
+	vlong	timer;
+	int	blocked;	/* for sw flow ctl. BUG: not implemented yet */
+	int	nbreakerr;
+	int	ring;
+	int	nframeerr;
+	int	nparityerr;
+	int	novererr;
+	int	enabled;
+
+	Serialops;
+};
+
+extern Serialops plops;
+
+enum {
+	/* soft flow control chars */
+	CTLS = 023,
+	CTLQ = 021,
+};
+
+/*
+ * !hget http://lxr.linux.no/source/drivers/usb/serial/pl2303.h|htmlfmt
+ * !hget http://lxr.linux.no/source/drivers/usb/serial/pl2303.c|htmlfmt
+ */
+
+int serialmain(Dev *d, int argc, char *argv[]);
+
+typedef struct Cinfo Cinfo;
+struct Cinfo {
+	int	vid;		/* usb vendor id */
+	int	did;		/* usb device/product id */
+	int	cid;		/* controller id assigned by us */
+};
+
+extern Cinfo cinfo[];
+extern int serialdebug;
+
+#define	dsprint	if(serialdebug)fprint
+
+int	serialrecover(Serial *ser, char *err);
+int	serialreset(Serial *ser);
+char	*dumpstatus(Serial *ser, char *buf, int bufsz);