shithub: riscv

Download patch

ref: 9f4184892cb4d95c39064906d2c7630f30352215
parent: 2ba1b4c476c986ae788e3b0869fc799e5516a2c2
author: aiju <[email protected]>
date: Sat Jul 30 15:14:18 EDT 2011

added nusb/serial

--- a/sys/src/cmd/nusb/mkfile
+++ b/sys/src/cmd/nusb/mkfile
@@ -5,6 +5,8 @@
 	kb\
 	audio\
 	usbd\
+	disk\
+	serial\
 
 UPDATE=\
 	mkfile\
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/ftdi.c
@@ -1,0 +1,960 @@
+/* Future Technology Devices International serial ports */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "usb.h"
+#include "serial.h"
+#include "ftdi.h"
+
+/*
+ * BUG: This keeps growing, there has to be a better way, but without
+ * devices to try it...  We can probably simply look for FTDI in the
+ * string, or use regular expressions somehow.
+ */
+Cinfo ftinfo[] = {
+	{ FTVid, FTACTZWAVEDid },
+	{ FTSheevaVid, FTSheevaDid },
+	{ FTVid, FTOpenRDUltDid},
+	{ FTVid, FTIRTRANSDid },
+	{ FTVid, FTIPLUSDid },
+	{ FTVid, FTSIODid },
+	{ FTVid, FT8U232AMDid },
+	{ FTVid, FT8U232AMALTDid },
+	{ FTVid, FT8U2232CDid },
+	{ FTVid, FTRELAISDid },
+	{ INTERBIOMVid, INTERBIOMIOBRDDid },
+	{ INTERBIOMVid, INTERBIOMMINIIOBRDDid },
+	{ FTVid, FTXF632Did },
+	{ FTVid, FTXF634Did },
+	{ FTVid, FTXF547Did },
+	{ FTVid, FTXF633Did },
+	{ FTVid, FTXF631Did },
+	{ FTVid, FTXF635Did },
+	{ FTVid, FTXF640Did },
+	{ FTVid, FTXF642Did },
+	{ FTVid, FTDSS20Did },
+	{ FTNFRICVid, FTNFRICDid },
+	{ FTVid, FTVNHCPCUSBDDid },
+	{ FTVid, FTMTXORB0Did },
+	{ FTVid, FTMTXORB1Did },
+	{ FTVid, FTMTXORB2Did },
+	{ FTVid, FTMTXORB3Did },
+	{ FTVid, FTMTXORB4Did },
+	{ FTVid, FTMTXORB5Did },
+	{ FTVid, FTMTXORB6Did },
+	{ FTVid, FTPERLEULTRAPORTDid },
+	{ FTVid, FTPIEGROUPDid },
+	{ SEALEVELVid, SEALEVEL2101Did },
+	{ SEALEVELVid, SEALEVEL2102Did },
+	{ SEALEVELVid, SEALEVEL2103Did },
+	{ SEALEVELVid, SEALEVEL2104Did },
+	{ SEALEVELVid, SEALEVEL22011Did },
+	{ SEALEVELVid, SEALEVEL22012Did },
+	{ SEALEVELVid, SEALEVEL22021Did },
+	{ SEALEVELVid, SEALEVEL22022Did },
+	{ SEALEVELVid, SEALEVEL22031Did },
+	{ SEALEVELVid, SEALEVEL22032Did },
+	{ SEALEVELVid, SEALEVEL24011Did },
+	{ SEALEVELVid, SEALEVEL24012Did },
+	{ SEALEVELVid, SEALEVEL24013Did },
+	{ SEALEVELVid, SEALEVEL24014Did },
+	{ SEALEVELVid, SEALEVEL24021Did },
+	{ SEALEVELVid, SEALEVEL24022Did },
+	{ SEALEVELVid, SEALEVEL24023Did },
+	{ SEALEVELVid, SEALEVEL24024Did },
+	{ SEALEVELVid, SEALEVEL24031Did },
+	{ SEALEVELVid, SEALEVEL24032Did },
+	{ SEALEVELVid, SEALEVEL24033Did },
+	{ SEALEVELVid, SEALEVEL24034Did },
+	{ SEALEVELVid, SEALEVEL28011Did },
+	{ SEALEVELVid, SEALEVEL28012Did },
+	{ SEALEVELVid, SEALEVEL28013Did },
+	{ SEALEVELVid, SEALEVEL28014Did },
+	{ SEALEVELVid, SEALEVEL28015Did },
+	{ SEALEVELVid, SEALEVEL28016Did },
+	{ SEALEVELVid, SEALEVEL28017Did },
+	{ SEALEVELVid, SEALEVEL28018Did },
+	{ SEALEVELVid, SEALEVEL28021Did },
+	{ SEALEVELVid, SEALEVEL28022Did },
+	{ SEALEVELVid, SEALEVEL28023Did },
+	{ SEALEVELVid, SEALEVEL28024Did },
+	{ SEALEVELVid, SEALEVEL28025Did },
+	{ SEALEVELVid, SEALEVEL28026Did },
+	{ SEALEVELVid, SEALEVEL28027Did },
+	{ SEALEVELVid, SEALEVEL28028Did },
+	{ SEALEVELVid, SEALEVEL28031Did },
+	{ SEALEVELVid, SEALEVEL28032Did },
+	{ SEALEVELVid, SEALEVEL28033Did },
+	{ SEALEVELVid, SEALEVEL28034Did },
+	{ SEALEVELVid, SEALEVEL28035Did },
+	{ SEALEVELVid, SEALEVEL28036Did },
+	{ SEALEVELVid, SEALEVEL28037Did },
+	{ SEALEVELVid, SEALEVEL28038Did },
+	{ IDTECHVid, IDTECHIDT1221UDid },
+	{ OCTVid, OCTUS101Did },
+	{ FTVid, FTHETIRA1Did }, /* special quirk div = 240 baud = B38400 rtscts = 1 */
+	{ FTVid, FTUSBUIRTDid }, /* special quirk div = 77, baud = B38400 */
+	{ FTVid, PROTEGOSPECIAL1 },
+	{ FTVid, PROTEGOR2X0 },
+	{ FTVid, PROTEGOSPECIAL3 },
+	{ FTVid, PROTEGOSPECIAL4 },
+	{ FTVid, FTGUDEADSE808Did },
+	{ FTVid, FTGUDEADSE809Did },
+	{ FTVid, FTGUDEADSE80ADid },
+	{ FTVid, FTGUDEADSE80BDid },
+	{ FTVid, FTGUDEADSE80CDid },
+	{ FTVid, FTGUDEADSE80DDid },
+	{ FTVid, FTGUDEADSE80EDid },
+	{ FTVid, FTGUDEADSE80FDid },
+	{ FTVid, FTGUDEADSE888Did },
+	{ FTVid, FTGUDEADSE889Did },
+	{ FTVid, FTGUDEADSE88ADid },
+	{ FTVid, FTGUDEADSE88BDid },
+	{ FTVid, FTGUDEADSE88CDid },
+	{ FTVid, FTGUDEADSE88DDid },
+	{ FTVid, FTGUDEADSE88EDid },
+	{ FTVid, FTGUDEADSE88FDid },
+	{ FTVid, FTELVUO100Did },
+	{ FTVid, FTELVUM100Did },
+	{ FTVid, FTELVUR100Did },
+	{ FTVid, FTELVALC8500Did },
+	{ FTVid, FTPYRAMIDDid },
+	{ FTVid, FTELVFHZ1000PCDid },
+	{ FTVid, FTELVCLI7000Did },
+	{ FTVid, FTELVPPS7330Did },
+	{ FTVid, FTELVTFM100Did },
+	{ FTVid, FTELVUDF77Did },
+	{ FTVid, FTELVUIO88Did },
+	{ FTVid, FTELVUAD8Did },
+	{ FTVid, FTELVUDA7Did },
+	{ FTVid, FTELVUSI2Did },
+	{ FTVid, FTELVT1100Did },
+	{ FTVid, FTELVPCD200Did },
+	{ FTVid, FTELVULA200Did },
+	{ FTVid, FTELVCSI8Did },
+	{ FTVid, FTELVEM1000DLDid },
+	{ FTVid, FTELVPCK100Did },
+	{ FTVid, FTELVRFP500Did },
+	{ FTVid, FTELVFS20SIGDid },
+	{ FTVid, FTELVWS300PCDid },
+	{ FTVid, FTELVFHZ1300PCDid },
+	{ FTVid, FTELVWS500Did },
+	{ FTVid, LINXSDMUSBQSSDid },
+	{ FTVid, LINXMASTERDEVEL2Did },
+	{ FTVid, LINXFUTURE0Did },
+	{ FTVid, LINXFUTURE1Did },
+	{ FTVid, LINXFUTURE2Did },
+	{ FTVid, FTCCSICDU200Did },
+	{ FTVid, FTCCSICDU401Did },
+	{ FTVid, INSIDEACCESSO },
+	{ INTREDidVid, INTREDidVALUECANDid },
+	{ INTREDidVid, INTREDidNEOVIDid },
+	{ FALCOMVid, FALCOMTWISTDid },
+	{ FALCOMVid, FALCOMSAMBADid },
+	{ FTVid, FTSUUNTOSPORTSDid },
+	{ FTVid, FTRMCANVIEWDid },
+	{ BANDBVid, BANDBUSOTL4Did },
+	{ BANDBVid, BANDBUSTL4Did },
+	{ BANDBVid, BANDBUSO9ML2Did },
+	{ FTVid, EVERECOPROCDSDid },
+	{ FTVid, FT4NGALAXYDE0Did },
+	{ FTVid, FT4NGALAXYDE1Did },
+	{ FTVid, FT4NGALAXYDE2Did },
+	{ FTVid, XSENSCONVERTER0Did },
+	{ FTVid, XSENSCONVERTER1Did },
+	{ FTVid, XSENSCONVERTER2Did },
+	{ FTVid, XSENSCONVERTER3Did },
+	{ FTVid, XSENSCONVERTER4Did },
+	{ FTVid, XSENSCONVERTER5Did },
+	{ FTVid, XSENSCONVERTER6Did },
+	{ FTVid, XSENSCONVERTER7Did },
+	{ MOBILITYVid, MOBILITYUSBSERIALDid },
+	{ FTVid, FTACTIVEROBOTSDid },
+	{ FTVid, FTMHAMKWDid },
+	{ FTVid, FTMHAMYSDid },
+	{ FTVid, FTMHAMY6Did },
+	{ FTVid, FTMHAMY8Did },
+	{ FTVid, FTMHAMICDid },
+	{ FTVid, FTMHAMDB9Did },
+	{ FTVid, FTMHAMRS232Did },
+	{ FTVid, FTMHAMY9Did },
+	{ FTVid, FTTERATRONIKVCPDid },
+	{ FTVid, FTTERATRONIKD2XXDid },
+	{ EVOLUTIONVid, EVOLUTIONER1Did },
+	{ FTVid, FTARTEMISDid },
+	{ FTVid, FTATIKATK16Did },
+	{ FTVid, FTATIKATK16CDid },
+	{ FTVid, FTATIKATK16HRDid },
+	{ FTVid, FTATIKATK16HRCDid },
+	{ KOBILVid, KOBILCONVB1Did },
+	{ KOBILVid, KOBILCONVKAANDid },
+	{ POSIFLEXVid, POSIFLEXPP7000Did },
+	{ FTVid, FTTTUSBDid },
+	{ FTVid, FTECLOCOM1WIREDid },
+	{ FTVid, FTWESTREXMODEL777Did },
+	{ FTVid, FTWESTREXMODEL8900FDid },
+	{ FTVid, FTPCDJDAC2Did },
+	{ FTVid, FTRRCIRKITSLOCOBUFFERDid },
+	{ FTVid, FTASKRDR400Did },
+	{ ICOMID1Vid, ICOMID1Did },
+	{ PAPOUCHVid, PAPOUCHTMUDid },
+	{ FTVid, FTACGHFDUALDid },
+	{ FT8U232AMDid, FT4232HDid },
+	{ 0,	0 },
+};
+
+enum {
+	Packsz		= 64,		/* default size */
+	Maxpacksz	= 512,
+	Bufsiz		= 4 * 1024,
+};
+
+static int
+ftdiread(Serialport *p, int index, int req, uchar *buf, int len)
+{
+	int res;
+	Serial *ser;
+
+	ser = p->s;
+
+	if(req != FTGETE2READ)
+		index |= p->interfc + 1;
+	dsprint(2, "serial: ftdiread %#p [%d] req: %#x val: %#x idx:%d buf:%p len:%d\n",
+		p, p->interfc, req, 0, index, buf, len);
+	res = usbcmd(ser->dev,  Rd2h | Rftdireq | Rdev, req, 0, index, buf, len);
+	dsprint(2, "serial: ftdiread res:%d\n", res);
+	return res;
+}
+
+static int
+ftdiwrite(Serialport *p, int val, int index, int req)
+{
+	int res;
+	Serial *ser;
+
+	ser = p->s;
+
+	if(req != FTGETE2READ || req != FTSETE2ERASE || req != FTSETBAUDRATE)
+		index |= p->interfc + 1;
+	dsprint(2, "serial: ftdiwrite %#p [%d] req: %#x val: %#x idx:%d\n",
+		p, p->interfc, req, val, index);
+	res = usbcmd(ser->dev, Rh2d | Rftdireq | Rdev, req, val, index, nil, 0);
+	dsprint(2, "serial: ftdiwrite res:%d\n", res);
+	return res;
+}
+
+static int
+ftmodemctl(Serialport *p, int set)
+{
+	if(set == 0){
+		p->mctl = 0;
+		ftdiwrite(p, 0, 0, FTSETMODEMCTRL);
+		return 0;
+	}
+	p->mctl = 1;
+	ftdiwrite(p, 0, FTRTSCTSHS, FTSETFLOWCTRL);
+	return 0;
+}
+
+static ushort
+ft232ambaudbase2div(int baud, int base)
+{
+	int divisor3;
+	ushort divisor;
+
+	divisor3 = (base / 2) / baud;
+	if((divisor3 & 7) == 7)
+		divisor3++;			/* round x.7/8 up to x+1 */
+	divisor = divisor3 >> 3;
+	divisor3 &= 7;
+
+	if(divisor3 == 1)
+		divisor |= 0xc000;		/*	0.125 */
+	else if(divisor3 >= 4)
+		divisor |= 0x4000;		/*	0.5	*/
+	else if(divisor3 != 0)
+		divisor |= 0x8000;		/*	0.25	*/
+	if( divisor == 1)
+		divisor = 0;		/* special case for maximum baud rate */
+	return divisor;
+}
+
+enum{
+	ClockNew	= 48000000,
+	ClockOld	= 12000000 / 16,
+	HetiraDiv	= 240,
+	UirtDiv		= 77,
+};
+
+static ushort
+ft232ambaud2div(int baud)
+{
+	return ft232ambaudbase2div(baud, ClockNew);
+}
+
+static ulong divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7};
+
+static ulong
+ft232bmbaudbase2div(int baud, int base)
+{
+	int divisor3;
+	u32int divisor;
+
+	divisor3 = (base / 2) / baud;
+	divisor = divisor3 >> 3 | divfrac[divisor3 & 7] << 14;
+
+	/* Deal with special cases for highest baud rates. */
+	if( divisor == 1)
+		divisor = 0;			/* 1.0 */
+	else if( divisor == 0x4001)
+		divisor = 1;			/* 1.5 */
+	return divisor;
+}
+
+static ulong
+ft232bmbaud2div (int baud)
+{
+	return ft232bmbaudbase2div (baud, ClockNew);
+}
+
+static int
+customdiv(Serial *ser)
+{
+	if(ser->dev->usb->vid == FTVid && ser->dev->usb->did == FTHETIRA1Did)
+		return HetiraDiv;
+	else if(ser->dev->usb->vid == FTVid && ser->dev->usb->did == FTUSBUIRTDid)
+		return UirtDiv;
+
+	fprint(2, "serial: weird custom divisor\n");
+	return 0;		/* shouldn't happen, break as much as I can */
+}
+
+static ulong
+ftbaudcalcdiv(Serial *ser, int baud)
+{
+	int cusdiv;
+	ulong divval;
+
+	if(baud == 38400 && (cusdiv = customdiv(ser)) != 0)
+		baud = ser->baudbase / cusdiv;
+
+	if(baud == 0)
+		baud = 9600;
+
+	switch(ser->type) {
+	case SIO:
+		switch(baud) {
+		case 300:
+			divval = FTb300;
+			break;
+		case 600:
+			divval = FTb600;
+			break;
+		case 1200:
+			divval = FTb1200;
+			break;
+		case 2400:
+			divval = FTb2400;
+			break;
+		case 4800:
+			divval = FTb4800;
+			break;
+		case 9600:
+			divval = FTb9600;
+			break;
+		case 19200:
+			divval = FTb19200;
+			break;
+		case 38400:
+			divval = FTb38400;
+			break;
+		case 57600:
+			divval = FTb57600;
+			break;
+		case 115200:
+			divval = FTb115200;
+			break;
+		default:
+			divval = FTb9600;
+			break;
+		}
+		break;
+	case FT8U232AM:
+		if(baud <= 3000000)
+			divval = ft232ambaud2div(baud);
+		else
+			divval = ft232ambaud2div(9600);
+		break;
+	case FT232BM:
+	case FT2232C:
+	case FTKINDR:
+	case FT2232H:
+	case FT4232H:
+		if(baud <= 3000000)
+			divval = ft232bmbaud2div(baud);
+		else
+			divval = ft232bmbaud2div(9600);
+		break;
+	default:
+		divval = ft232bmbaud2div(9600);
+		break;
+	}
+	return divval;
+}
+
+static int
+ftsetparam(Serialport *p)
+{
+	int res;
+	ushort val;
+	ulong bauddiv;
+
+	val = 0;
+	if(p->stop == 1)
+		val |= FTSETDATASTOPBITS1;
+	else if(p->stop == 2)
+		val |= FTSETDATASTOPBITS2;
+	else if(p->stop == 15)
+		val |= FTSETDATASTOPBITS15;
+	switch(p->parity){
+	case 0:
+		val |= FTSETDATAParNONE;
+		break;
+	case 1:
+		val |= FTSETDATAParODD;
+		break;
+	case 2:
+		val |= FTSETDATAParEVEN;
+		break;
+	case 3:
+		val |= FTSETDATAParMARK;
+		break;
+	case 4:
+		val |= FTSETDATAParSPACE;
+		break;
+	};
+
+	dsprint(2, "serial: setparam\n");
+
+	res = ftdiwrite(p, val, 0, FTSETDATA);
+	if(res < 0)
+		return res;
+
+	res = ftmodemctl(p, p->mctl);
+	if(res < 0)
+		return res;
+
+	bauddiv = ftbaudcalcdiv(p->s, p->baud);
+	res = ftdiwrite(p, bauddiv, (bauddiv>>16) & 1, FTSETBAUDRATE);
+
+	dsprint(2, "serial: setparam res: %d\n", res);
+	return res;
+}
+
+static int
+hasjtag(Usbdev *udev)
+{
+	/* no string, for now, by default we detect no jtag */
+	if(udev->product != nil && cistrstr(udev->product, "jtag") != nil)
+		return 1;
+	return 0;
+}
+
+/* ser locked */
+static void
+ftgettype(Serial *ser)
+{
+	int i, outhdrsz, dno, pksz;
+	ulong baudbase;
+	Conf *cnf;
+
+	pksz = Packsz;
+	/* Assume it is not the original SIO device for now. */
+	baudbase = ClockNew / 2;
+	outhdrsz = 0;
+	dno = ser->dev->usb->dno;
+	cnf = ser->dev->usb->conf[0];
+	ser->nifcs = 0;
+	for(i = 0; i < Niface; i++)
+		if(cnf->iface[i] != nil)
+			ser->nifcs++;
+	if(ser->nifcs > 1) {
+		/*
+		 * Multiple interfaces.  default assume FT2232C,
+		 */
+		if(dno == 0x500)
+			ser->type = FT2232C;
+		else if(dno == 0x600)
+			ser->type = FTKINDR;
+		else if(dno == 0x700){
+			ser->type = FT2232H;
+			pksz = Maxpacksz;
+		} else if(dno == 0x800){
+			ser->type = FT4232H;
+			pksz = Maxpacksz;
+		} else
+			ser->type = FT2232C;
+
+		if(hasjtag(ser->dev->usb))
+			ser->jtag = 0;
+
+		/*
+		 * BM-type devices have a bug where dno gets set
+		 * to 0x200 when serial is 0.
+		 */
+		if(dno < 0x500)
+			fprint(2, "serial: warning: dno %d too low for "
+				"multi-interface device\n", dno);
+	} else if(dno < 0x200) {
+		/* Old device.  Assume it is the original SIO. */
+		ser->type = SIO;
+		baudbase = ClockOld/16;
+		outhdrsz = 1;
+	} else if(dno < 0x400)
+		/*
+		 * Assume its an FT8U232AM (or FT8U245AM)
+		 * (It might be a BM because of the iSerialNumber bug,
+		 * but it will still work as an AM device.)
+		 */
+		ser->type = FT8U232AM;
+	else			/* Assume it is an FT232BM (or FT245BM) */
+		ser->type = FT232BM;
+
+	ser->maxrtrans = ser->maxwtrans = pksz;
+	ser->baudbase = baudbase;
+	ser->outhdrsz = outhdrsz;
+	ser->inhdrsz = 2;
+
+	dsprint (2, "serial: detected type: %#x\n", ser->type);
+}
+
+int
+ftmatch(Serial *ser, char *info)
+{
+	Cinfo *ip;
+	char buf[50];
+
+	for(ip = ftinfo; ip->vid != 0; ip++){
+		snprint(buf, sizeof buf, "vid %#06x did %#06x", ip->vid, ip->did);
+		dsprint(2, "serial: %s %s\n", buf, info);
+		if(strstr(info, buf) != nil){
+			if(ser != nil){
+				qlock(ser);
+				ftgettype(ser);
+				qunlock(ser);
+			}
+			return 0;
+		}
+	}
+	return -1;
+}
+
+static int
+ftuseinhdr(Serialport *p, uchar *b)
+{
+	if(b[0] & FTICTS)
+		p->cts = 1;
+	else
+		p->cts = 0;
+	if(b[0] & FTIDSR)
+		p->dsr = 1;
+	else
+		p->dsr = 0;
+	if(b[0] & FTIRI)
+		p->ring = 1;
+	else
+		p->ring = 0;
+	if(b[0] & FTIRLSD)
+		p->rlsd = 1;
+	else
+		p->rlsd = 0;
+
+	if(b[1] & FTIOE)
+		p->novererr++;
+	if(b[1] & FTIPE)
+		p->nparityerr++;
+	if(b[1] & FTIFE)
+		p->nframeerr++;
+	if(b[1] & FTIBI)
+		p->nbreakerr++;
+	return 0;
+}
+
+static int
+ftsetouthdr(Serialport *p, uchar *b, int len)
+{
+	if(p->s->outhdrsz != 0)
+		b[0] = FTOPORT | (FTOLENMSK & len);
+	return p->s->outhdrsz;
+}
+
+static int
+wait4data(Serialport *p, uchar *data, int count)
+{
+	int d;
+	Serial *ser;
+
+	ser = p->s;
+
+	qunlock(ser);
+	d = sendul(p->w4data, 1);
+	qlock(ser);
+	if(d <= 0)
+		return -1;
+	if(p->ndata >= count)
+		p->ndata -= count;
+	else{
+		count = p->ndata;
+		p->ndata = 0;
+	}
+	assert(count >= 0);
+	assert(p->ndata >= 0);
+	memmove(data, p->data, count);
+	if(p->ndata != 0)
+		memmove(p->data, p->data+count, p->ndata);
+
+	recvul(p->gotdata);
+	return count;
+}
+
+static int
+wait4write(Serialport *p, uchar *data, int count)
+{
+	int off, fd;
+	uchar *b;
+	Serial *ser;
+
+	ser = p->s;
+
+	b = emallocz(count+ser->outhdrsz, 1);
+	off = ftsetouthdr(p, b, count);
+	memmove(b+off, data, count);
+
+	fd = p->epout->dfd;
+	qunlock(ser);
+	count = write(fd, b, count+off);
+	qlock(ser);
+	free(b);
+	return count;
+}
+
+typedef struct Packser Packser;
+struct Packser{
+	int	nb;
+	uchar	b[Bufsiz];
+};
+
+typedef struct Areader Areader;
+struct Areader{
+	Serialport	*p;
+	Channel	*c;
+};
+
+static void
+shutdownchan(Channel *c)
+{
+	Packser *bp;
+
+	while((bp=nbrecvp(c)) != nil)
+		free(bp);
+	chanfree(c);
+}
+
+int
+cpdata(Serial *ser, Serialport *port, uchar *out, uchar *in, int sz)
+{
+	int i, ncp, ntotcp, pksz;
+
+	pksz = ser->maxrtrans;
+	ntotcp = 0;
+
+	for(i = 0; i < sz; i+= pksz){
+		ftuseinhdr(port, in + i);
+		if(sz - i > pksz)
+			ncp = pksz - ser->inhdrsz;
+		else
+			ncp = sz - i - ser->inhdrsz;
+		memmove(out, in + i + ser->inhdrsz, ncp);
+		out += ncp;
+		ntotcp += ncp;
+	}
+	return ntotcp;
+}
+
+static void
+epreader(void *u)
+{
+	int dfd, rcount, cl, ntries, recov;
+	char err[40];
+	Areader *a;
+	Channel *c;
+	Packser *pk;
+	Serial *ser;
+	Serialport *p;
+
+	threadsetname("epreader proc");
+	a = u;
+	p = a->p;
+	ser = p->s;
+	c = a->c;
+	free(a);
+
+	qlock(ser);	/* this makes the reader wait end of initialization too */
+	dfd = p->epin->dfd;
+	qunlock(ser);
+
+	ntries = 0;
+	pk = nil;
+	do {
+		if (pk == nil)
+			pk = emallocz(sizeof(Packser), 1);
+Eagain:
+		rcount = read(dfd, pk->b, sizeof pk->b);
+		if(serialdebug > 5)
+			dsprint(2, "%d %#ux%#ux ", rcount, p->data[0],
+				p->data[1]);
+
+		if(rcount < 0){
+			if(ntries++ > 100)
+				break;
+			qlock(ser);
+			recov = serialrecover(ser, p, nil, "epreader: bulkin error");
+			qunlock(ser);
+			if(recov >= 0)
+				goto Eagain;
+		}
+		if(rcount == 0)
+			continue;
+		if(rcount >= ser->inhdrsz){
+			rcount = cpdata(ser, p, pk->b, pk->b, rcount);
+			if(rcount != 0){
+				pk->nb = rcount;
+				cl = sendp(c, pk);
+				if(cl < 0){
+					/*
+					 * if it was a time-out, I don't want
+					 * to give back an error.
+					 */
+					rcount = 0;
+					break;
+				}
+			}else
+				free(pk);
+			qlock(ser);
+			ser->recover = 0;
+			qunlock(ser);
+			ntries = 0;
+			pk = nil;
+		}
+	} while(rcount >= 0 || (rcount < 0 && strstr(err, "timed out") != nil));
+
+	if(rcount < 0)
+		fprint(2, "%s: error reading %s: %r\n", argv0, p->name);
+	free(pk);
+	nbsendp(c, nil);
+	if(p->w4data != nil)
+		chanclose(p->w4data);
+	if(p->gotdata != nil)
+		chanclose(p->gotdata);
+	devctl(ser->dev, "detach");
+	closedev(ser->dev);
+}
+
+static void
+statusreader(void *u)
+{
+	Areader *a;
+	Channel *c;
+	Packser *pk;
+	Serialport *p;
+	Serial *ser;
+	int cl;
+
+	p = u;
+	ser = p->s;
+	threadsetname("statusreader thread");
+	/* big buffering, fewer bytes lost */
+	c = chancreate(sizeof(Packser *), 128);
+	a = emallocz(sizeof(Areader), 1);
+	a->p = p;
+	a->c = c;
+	incref(ser->dev);
+	proccreate(epreader, a, 16*1024);
+
+	while((pk = recvp(c)) != nil){
+		memmove(p->data, pk->b, pk->nb);
+		p->ndata = pk->nb;
+		free(pk);
+		dsprint(2, "serial %p: status reader %d \n", p, p->ndata);
+		/* consume it all */
+		while(p->ndata != 0){
+			dsprint(2, "serial %p: status reader to consume: %d\n",
+				p, p->ndata);
+			cl = recvul(p->w4data);
+			if(cl  < 0)
+				break;
+			cl = sendul(p->gotdata, 1);
+			if(cl  < 0)
+				break;
+		}
+	}
+
+	shutdownchan(c);
+	devctl(ser->dev, "detach");
+	closedev(ser->dev);
+}
+
+static int
+ftreset(Serial *ser, Serialport *p)
+{
+	int i;
+
+	if(p != nil){
+		ftdiwrite(p, FTRESETCTLVAL, 0, FTRESET);
+		return 0;
+	}
+	p = ser->p;
+	for(i = 0; i < Maxifc; i++)
+		if(p[i].s != nil)
+			ftdiwrite(&p[i], FTRESETCTLVAL, 0, FTRESET);
+	return 0;
+}
+
+static int
+ftinit(Serialport *p)
+{
+	Serial *ser;
+	uint timerval;
+	int res;
+
+	ser = p->s;
+	if(p->isjtag){
+		res = ftdiwrite(p, FTSETFLOWCTRL, 0, FTDISABLEFLOWCTRL);
+		if(res < 0)
+			return -1;
+		res = ftdiread(p, FTSETLATENCYTIMER, 0, (uchar *)&timerval,
+			FTLATENCYTIMERSZ);
+		if(res < 0)
+			return -1;
+		dsprint(2, "serial: jtag latency timer is %d\n", timerval);
+		timerval = 2;
+		ftdiwrite(p, FTLATENCYDEFAULT, 0, FTSETLATENCYTIMER);
+		res = ftdiread(p, FTSETLATENCYTIMER, 0, (uchar *)&timerval,
+			FTLATENCYTIMERSZ);
+		if(res < 0)
+			return -1;
+
+		dsprint(2, "serial: jtag latency timer set to %d\n", timerval);
+		/* may be unnecessary */
+		devctl(p->epin,  "timeout 5000");
+		devctl(p->epout, "timeout 5000");
+		/* 0xb is the mask for lines. plug dependant? */
+		ftdiwrite(p, BMMPSSE|0x0b, 0, FTSETBITMODE);
+	}
+	incref(ser->dev);
+	threadcreate(statusreader, p, 8*1024);
+	return 0;
+}
+
+static int
+ftsetbreak(Serialport *p, int val)
+{
+	return ftdiwrite(p, (val != 0? FTSETBREAK: 0), 0, FTSETDATA);
+}
+
+static int
+ftclearpipes(Serialport *p)
+{
+	/* maybe can be done in one... */
+	ftdiwrite(p, FTRESETCTLVALPURGETX, 0, FTRESET);
+	ftdiwrite(p, FTRESETCTLVALPURGERX, 0, FTRESET);
+	return 0;
+}
+
+static int
+setctlline(Serialport *p, uchar val)
+{
+	return ftdiwrite(p, val | (val << 8), 0, FTSETMODEMCTRL);
+}
+
+static void
+updatectlst(Serialport *p, int val)
+{
+	if(p->rts)
+		p->ctlstate |= val;
+	else
+		p->ctlstate &= ~val;
+}
+
+static int
+setctl(Serialport *p)
+{
+	int res;
+	Serial *ser;
+
+	ser = p->s;
+
+	if(ser->dev->usb->vid == FTVid && ser->dev->usb->did ==  FTHETIRA1Did){
+		fprint(2, "serial: cannot set lines for this device\n");
+		updatectlst(p, CtlRTS|CtlDTR);
+		p->rts = p->dtr = 1;
+		return -1;
+	}
+
+	/* NB: you can not set DTR and RTS with one control message */
+	updatectlst(p, CtlRTS);
+	res = setctlline(p, (CtlRTS<<8)|p->ctlstate);
+	if(res < 0)
+		return res;
+
+	updatectlst(p, CtlDTR);
+	res = setctlline(p, (CtlDTR<<8)|p->ctlstate);
+	if(res < 0)
+		return res;
+
+	return 0;
+}
+
+static int
+ftsendlines(Serialport *p)
+{
+	int res;
+
+	dsprint(2, "serial: sendlines: %#2.2x\n", p->ctlstate);
+	res = setctl(p);
+	dsprint(2, "serial: sendlines res: %d\n", res);
+	return 0;
+}
+
+static int
+ftseteps(Serialport *p)
+{
+	char *s;
+	Serial *ser;
+
+	ser = p->s;
+
+	s = smprint("maxpkt %d", ser->maxrtrans);
+	devctl(p->epin, s);
+	free(s);
+
+	s = smprint("maxpkt %d", ser->maxwtrans);
+	devctl(p->epout, s);
+	free(s);
+	return 0;
+}
+
+Serialops ftops = {
+	.init		= ftinit,
+	.seteps		= ftseteps,
+	.setparam	= ftsetparam,
+	.clearpipes	= ftclearpipes,
+	.reset		= ftreset,
+	.sendlines	= ftsendlines,
+	.modemctl	= ftmodemctl,
+	.setbreak	= ftsetbreak,
+	.wait4data	= wait4data,
+	.wait4write	= wait4write,
+};
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/ftdi.h
@@ -1,0 +1,632 @@
+enum {
+	/* used by devices which don't provide their own Vid */
+	FTVid		= 0x0403,
+
+	FTSheevaVid	= 0x9E88,
+	FTSheevaDid	= 0x9E8F,
+	FTOpenRDUltDid	= 0x9E90,
+
+	FTSIODid	= 0x8372,	/* Product Id SIO appl'n of 8U100AX */
+	FT8U232AMDid	= 0x6001,	/* Similar device to SIO above */
+	FT8U232AMALTDid	= 0x6006,	/* FT's alternate Did for above*/
+	FT8U2232CDid	= 0x6010,	/* Dual channel device */
+	FTRELAISDid	= 0xFA10,	/* Relais device */
+
+	/* NF reader */
+	FTNFRICVid	= 0x0DCD,
+	FTNFRICDid	= 0x0001,
+
+	FTACTZWAVEDid	= 0xF2D0,		/* www.irtrans.de device */
+
+	/*
+	 * ACT Solutions HomePro ZWave interface
+	 * http://www.act-solutions.com/HomePro.htm)
+	 */
+	FTIRTRANSDid	= 0xFC60,
+
+	/*
+	 * www.thoughttechnology.com/ TT-USB
+	 */
+	FTTTUSBDid	= 0xFF20,
+
+	/* iPlus device */
+	FTIPLUSDid	= 0xD070,
+
+	/* www.crystalfontz.com devices */
+	FTXF632Did = 0xFC08,	/* 632: 16x2 Character Display */
+	FTXF634Did = 0xFC09,	/* 634: 20x4 Character Display */
+	FTXF547Did = 0xFC0A,	/* 547: Two line Display */
+	FTXF633Did = 0xFC0B,	/* 633: 16x2 Character Display with Keys */
+	FTXF631Did = 0xFC0C,	/* 631: 20x2 Character Display */
+	FTXF635Did = 0xFC0D,	/* 635: 20x4 Character Display */
+	FTXF640Did = 0xFC0E,	/* 640: Two line Display */
+	FTXF642Did = 0xFC0F,	/* 642: Two line Display */
+
+	/*
+	 * Video Networks Limited / Homechoice in the UK
+	 * use an ftdi-based device for their 1Mb broadband
+	 */
+	FTVNHCPCUSBDDid	= 0xfe38,
+
+	/*
+	 * PCDJ use ftdi based dj-controllers
+	 * DAC-2 device http://www.pcdjhardware.com/DAC2.asp
+	 */
+	FTPCDJDAC2Did = 0xFA88,
+
+	/*
+	 * Matrix Orbital LCD displays,
+	 * which are the FT232BM (similar to the 8U232AM)
+	 */
+	FTMTXORB0Did = 0xFA00,
+	FTMTXORB1Did = 0xFA01,
+	FTMTXORB2Did = 0xFA02,
+	FTMTXORB3Did = 0xFA03,
+	FTMTXORB4Did = 0xFA04,
+	FTMTXORB5Did = 0xFA05,
+	FTMTXORB6Did = 0xFA06,
+
+	/* Interbiometrics USB I/O Board */
+	INTERBIOMVid		= 0x1209,
+	INTERBIOMIOBRDDid	= 0x1002,
+	INTERBIOMMINIIOBRDDid	= 0x1006,
+
+	/*
+	 * The following are the values for the Perle Systems
+	 * UltraPort USB serial converters
+	 */
+	FTPERLEULTRAPORTDid = 0xF0C0,
+
+	/*
+	 * Sealevel SeaLINK+ adapters.
+	 */
+
+	SEALEVELVid = 0x0c52,
+
+	SEALEVEL2101Did = 0x2101,	/* SeaLINK+232 (2101/2105) */
+	SEALEVEL2102Did = 0x2102,	/* SeaLINK+485 (2102) */
+	SEALEVEL2103Did = 0x2103,	/* SeaLINK+232I (2103) */
+	SEALEVEL2104Did = 0x2104,	/* SeaLINK+485I (2104) */
+	SEALEVEL22011Did = 0x2211,	/* SeaPORT+2/232 (2201) Port 1 */
+	SEALEVEL22012Did = 0x2221,	/* SeaPORT+2/232 (2201) Port 2 */
+	SEALEVEL22021Did = 0x2212,	/* SeaPORT+2/485 (2202) Port 1 */
+	SEALEVEL22022Did = 0x2222,	/* SeaPORT+2/485 (2202) Port 2 */
+	SEALEVEL22031Did = 0x2213,	/* SeaPORT+2 (2203) Port 1 */
+	SEALEVEL22032Did = 0x2223,	/* SeaPORT+2 (2203) Port 2 */
+	SEALEVEL24011Did = 0x2411,	/* SeaPORT+4/232 (2401) Port 1 */
+	SEALEVEL24012Did = 0x2421,	/* SeaPORT+4/232 (2401) Port 2 */
+	SEALEVEL24013Did = 0x2431,	/* SeaPORT+4/232 (2401) Port 3 */
+	SEALEVEL24014Did = 0x2441,	/* SeaPORT+4/232 (2401) Port 4 */
+	SEALEVEL24021Did = 0x2412,	/* SeaPORT+4/485 (2402) Port 1 */
+	SEALEVEL24022Did = 0x2422,	/* SeaPORT+4/485 (2402) Port 2 */
+	SEALEVEL24023Did = 0x2432,	/* SeaPORT+4/485 (2402) Port 3 */
+	SEALEVEL24024Did = 0x2442,	/* SeaPORT+4/485 (2402) Port 4 */
+	SEALEVEL24031Did = 0x2413,	/* SeaPORT+4 (2403) Port 1 */
+	SEALEVEL24032Did = 0x2423,	/* SeaPORT+4 (2403) Port 2 */
+	SEALEVEL24033Did = 0x2433,	/* SeaPORT+4 (2403) Port 3 */
+	SEALEVEL24034Did = 0x2443,	/* SeaPORT+4 (2403) Port 4 */
+	SEALEVEL28011Did = 0x2811,	/* SeaLINK+8/232 (2801) Port 1 */
+	SEALEVEL28012Did = 0x2821,	/* SeaLINK+8/232 (2801) Port 2 */
+	SEALEVEL28013Did = 0x2831,	/* SeaLINK+8/232 (2801) Port 3 */
+	SEALEVEL28014Did = 0x2841,	/* SeaLINK+8/232 (2801) Port 4 */
+	SEALEVEL28015Did = 0x2851,	/* SeaLINK+8/232 (2801) Port 5 */
+	SEALEVEL28016Did = 0x2861,	/* SeaLINK+8/232 (2801) Port 6 */
+	SEALEVEL28017Did = 0x2871,	/* SeaLINK+8/232 (2801) Port 7 */
+	SEALEVEL28018Did = 0x2881,	/* SeaLINK+8/232 (2801) Port 8 */
+	SEALEVEL28021Did = 0x2812,	/* SeaLINK+8/485 (2802) Port 1 */
+	SEALEVEL28022Did = 0x2822,	/* SeaLINK+8/485 (2802) Port 2 */
+	SEALEVEL28023Did = 0x2832,	/* SeaLINK+8/485 (2802) Port 3 */
+	SEALEVEL28024Did = 0x2842,	/* SeaLINK+8/485 (2802) Port 4 */
+	SEALEVEL28025Did = 0x2852,	/* SeaLINK+8/485 (2802) Port 5 */
+	SEALEVEL28026Did = 0x2862,	/* SeaLINK+8/485 (2802) Port 6 */
+	SEALEVEL28027Did = 0x2872,	/* SeaLINK+8/485 (2802) Port 7 */
+	SEALEVEL28028Did = 0x2882,	/* SeaLINK+8/485 (2802) Port 8 */
+	SEALEVEL28031Did = 0x2813,	/* SeaLINK+8 (2803) Port 1 */
+	SEALEVEL28032Did = 0x2823,	/* SeaLINK+8 (2803) Port 2 */
+	SEALEVEL28033Did = 0x2833,	/* SeaLINK+8 (2803) Port 3 */
+	SEALEVEL28034Did = 0x2843,	/* SeaLINK+8 (2803) Port 4 */
+	SEALEVEL28035Did = 0x2853,	/* SeaLINK+8 (2803) Port 5 */
+	SEALEVEL28036Did = 0x2863,	/* SeaLINK+8 (2803) Port 6 */
+	SEALEVEL28037Did = 0x2873,	/* SeaLINK+8 (2803) Port 7 */
+	SEALEVEL28038Did = 0x2883,	/* SeaLINK+8 (2803) Port 8 */
+
+	/* KOBIL Vendor ID chipcard terminals */
+	KOBILVid	= 0x0d46,
+	KOBILCONVB1Did	= 0x2020,	/* KOBIL Konverter for B1 */
+	KOBILCONVKAANDid = 0x2021,	/* KOBILKonverter for KAAN */
+
+	/* Icom ID-1 digital transceiver */
+	ICOMID1Vid	= 0x0C26,
+	ICOMID1Did	= 0x0004,
+
+	FTASKRDR400Did	= 0xC991,	/* ASK RDR 400 series card reader */
+	FTDSS20Did	= 0xFC82,	/* DSS-20 Sync Station for Sony Ericsson P800 */
+
+	/*
+	 * Home Electronics (www.home-electro.com) USB gadgets
+	 */
+	FTHETIRA1Did	= 0xFA78,	/* Tira-1 IR transceiver */
+
+	/*
+	 * An infrared receiver and transmitter using the 8U232AM chip
+	 * http://www.usbuirt.com
+	 */
+	FTUSBUIRTDid	= 0xF850,
+
+	FTELVUR100Did	= 0xFB58,	/* USB-RS232-Umsetzer (UR 100) */
+	FTELVUM100Did	= 0xFB5A,	/* USB-Modul UM 100 */
+	FTELVUO100Did	= 0xFB5B,	/* USB-Modul UO 100 */
+	FTELVALC8500Did	= 0xF06E,	/* ALC 8500 Expert */
+	FTELVCLI7000Did	= 0xFB59,	/* Computer-Light-Interface */
+	FTELVPPS7330Did	= 0xFB5C,	/* Processor-Power-Supply (PPS 7330) */
+	FTELVTFM100Did	= 0xFB5D,	/* Temperartur-Feuchte Messgeraet (TFM 100) */
+	FTELVUDF77Did	= 0xFB5E,	/* USB DCF Funkurh (UDF 77) */
+	FTELVUIO88Did	= 0xFB5F,	/* USB-I/O Interface (UIO 88) */
+	FTELVUAD8Did	= 0xF068,	/* USB-AD-Wandler (UAD 8) */
+	FTELVUDA7Did	= 0xF069,	/* USB-DA-Wandler (UDA 7) */
+	FTELVUSI2Did	= 0xF06A,	/* USB-Schrittmotoren-Interface (USI 2) */
+	FTELVT1100Did	= 0xF06B,	/* Thermometer (T 1100) */
+	FTELVPCD200Did	= 0xF06C,	/* PC-Datenlogger (PCD 200) */
+	FTELVULA200Did	= 0xF06D,	/* USB-LCD-Ansteuerung (ULA 200) */
+	FTELVFHZ1000PCDid= 0xF06F,	/* FHZ 1000 PC */
+	FTELVCSI8Did	= 0xE0F0,	/* Computer-Schalt-Interface (CSI 8) */
+	FTELVEM1000DLDid= 0xE0F1,	/* PC-Datenlogger fuer Energiemonitor (EM 1000 DL) */
+	FTELVPCK100Did	= 0xE0F2,	/* PC-Kabeltester (PCK 100) */
+	FTELVRFP500Did	= 0xE0F3,	/* HF-Leistungsmesser (RFP 500) */
+	FTELVFS20SIGDid	= 0xE0F4,	/* Signalgeber (FS 20 SIG) */
+	FTELVWS300PCDid	= 0xE0F6,	/* PC-Wetterstation (WS 300 PC) */
+	FTELVFHZ1300PCDid= 0xE0E8,	/* FHZ 1300 PC */
+	FTELVWS500Did	= 0xE0E9,	/* PC-Wetterstation (WS 500) */
+
+	/*
+	 * Definitions for ID TECH (http://www.idt-net.com) devices
+	 */
+	IDTECHVid	= 0x0ACD,	/* ID TECH Vendor ID */
+	IDTECHIDT1221UDid= 0x0300,	/* IDT1221U USB to RS-232 */
+
+	/*
+	 * Definitions for Omnidirectional Control Technology, Inc. devices
+	 */
+	OCTVid		= 0x0B39,	/* OCT vendor ID */
+
+	/*
+	 * Note: OCT US101 is also rebadged as Dick Smith Electronics
+	 * (NZ) XH6381, Dick Smith Electronics (Aus) XH6451, and SIIG
+	 * Inc. model US2308 hardware version 1.
+	 */
+	OCTUS101Did	= 0x0421,	/* OCT US101 USB to RS-232 */
+
+	/*
+	 *	infrared receiver for access control with IR tags
+	 */
+	FTPIEGROUPDid	= 0xF208,
+
+	/*
+	 * Definitions for Artemis astronomical USB based cameras
+	 * http://www.artemisccd.co.uk/
+	 */
+
+	FTARTEMISDid	= 0xDF28,	/* All Artemis Cameras */
+
+	FTATIKATK16Did	= 0xDF30,	/* ATIK ATK-16 Grayscale Camera */
+	FTATIKATK16CDid = 0xDF32,	/* ATIK ATK-16C Colour Camera */
+	FTATIKATK16HRDid= 0xDF31,	/* ATIK ATK-16HR Grayscale */
+	FTATIKATK16HRCDid= 0xDF33,	/* ATIK ATK-16HRC Colour Camera */
+
+	/*
+	 * Protego products
+	 */
+	PROTEGOSPECIAL1	= 0xFC70,	/* special/unknown device */
+	PROTEGOR2X0	= 0xFC71,	/* R200-USB TRNG unit (R210, R220, and R230) */
+	PROTEGOSPECIAL3	= 0xFC72,	/* special/unknown device */
+	PROTEGOSPECIAL4	= 0xFC73,	/* special/unknown device */
+
+	/*
+	 * Gude Analog- und Digitalsysteme GmbH
+	 */
+	FTGUDEADSE808Did = 0xE808,
+	FTGUDEADSE809Did = 0xE809,
+	FTGUDEADSE80ADid = 0xE80A,
+	FTGUDEADSE80BDid = 0xE80B,
+	FTGUDEADSE80CDid = 0xE80C,
+	FTGUDEADSE80DDid = 0xE80D,
+	FTGUDEADSE80EDid = 0xE80E,
+	FTGUDEADSE80FDid = 0xE80F,
+	FTGUDEADSE888Did = 0xE888,	/* Expert ISDN Control USB */
+	FTGUDEADSE889Did = 0xE889,	/* USB RS-232 OptoBridge */
+	FTGUDEADSE88ADid = 0xE88A,
+	FTGUDEADSE88BDid = 0xE88B,
+	FTGUDEADSE88CDid = 0xE88C,
+	FTGUDEADSE88DDid = 0xE88D,
+	FTGUDEADSE88EDid = 0xE88E,
+	FTGUDEADSE88FDid = 0xE88F,
+
+	/*
+	 * Linx Technologies
+	 */
+	LINXSDMUSBQSSDid= 0xF448,	/* Linx SDM-USB-QS-S */
+	LINXMASTERDEVEL2Did= 0xF449,	/* Linx Master Development.0 */
+	LINXFUTURE0Did	= 0xF44A,	/* Linx future device */
+	LINXFUTURE1Did	= 0xF44B,	/* Linx future device */
+	LINXFUTURE2Did	= 0xF44C,	/* Linx future device */
+
+	/*
+	 * CCS Inc. ICDU/ICDU40 - the FT232BM used in a in-circuit-debugger
+	 * unit for PIC16's/PIC18's
+	 */
+	FTCCSICDU200Did	= 0xF9D0,
+	FTCCSICDU401Did	= 0xF9D1,
+
+	/* Inside Accesso contactless reader (http://www.insidefr.com) */
+	INSIDEACCESSO	= 0xFAD0,
+
+	/*
+	 * Intrepid Control Systems (http://www.intrepidcs.com/)
+	 * ValueCAN and NeoVI
+	 */
+	INTREDidVid	= 0x093C,
+	INTREDidVALUECANDid= 0x0601,
+	INTREDidNEOVIDid= 0x0701,
+
+	/*
+	 * Falcom Wireless Communications GmbH
+	 */
+	FALCOMVid	= 0x0F94,
+	FALCOMTWISTDid	= 0x0001,	/* Falcom Twist USB GPRS modem */
+	FALCOMSAMBADid	= 0x0005,	/* Falcom Samba USB GPRS modem */
+
+	/*
+	 * SUUNTO
+	 */
+	FTSUUNTOSPORTSDid= 0xF680,	/* Suunto Sports instrument */
+
+	/*
+	 * B&B Electronics
+	 */
+	BANDBVid	= 0x0856,	/* B&B Electronics Vendor ID */
+	BANDBUSOTL4Did	= 0xAC01,	/* USOTL4 Isolated RS-485 */
+	BANDBUSTL4Did	= 0xAC02,	/* USTL4 RS-485 Converter */
+	BANDBUSO9ML2Did	= 0xAC03,	/* USO9ML2 Isolated RS-232 */
+
+	/*
+	 * RM Michaelides CANview USB (http://www.rmcan.com)
+	 * CAN fieldbus interface adapter
+	 */
+	FTRMCANVIEWDid	= 0xfd60,
+
+	/*
+	 * EVER Eco Pro UPS (http://www.ever.com.pl/)
+	 */
+	EVERECOPROCDSDid = 0xe520,	/* RS-232 converter */
+
+	/*
+	 * 4N-GALAXY.DE PIDs for CAN-USB, USB-RS232, USB-RS422, USB-RS485,
+	 * USB-TTY activ, USB-TTY passiv. Some PIDs are used by several devices
+	 */
+	FT4NGALAXYDE0Did = 0x8372,
+	FT4NGALAXYDE1Did = 0xF3C0,
+	FT4NGALAXYDE2Did = 0xF3C1,
+
+	/*
+	 * Mobility Electronics products.
+	 */
+	MOBILITYVid	= 0x1342,
+	MOBILITYUSBSERIALDid= 0x0202,	/* EasiDock USB 200 serial */
+
+	/*
+	 * microHAM product IDs (http://www.microham.com)
+	 */
+	FTMHAMKWDid	= 0xEEE8,	/* USB-KW interface */
+	FTMHAMYSDid	= 0xEEE9,	/* USB-YS interface */
+	FTMHAMY6Did	= 0xEEEA,	/* USB-Y6 interface */
+	FTMHAMY8Did	= 0xEEEB,	/* USB-Y8 interface */
+	FTMHAMICDid	= 0xEEEC,	/* USB-IC interface */
+	FTMHAMDB9Did	= 0xEEED,	/* USB-DB9 interface */
+	FTMHAMRS232Did	= 0xEEEE,	/* USB-RS232 interface */
+	FTMHAMY9Did	= 0xEEEF,	/* USB-Y9 interface */
+
+	/*
+	 * Active Robots product ids.
+	 */
+	FTACTIVEROBOTSDid	= 0xE548,	/* USB comms board */
+	XSENSCONVERTER0Did	= 0xD388,
+	XSENSCONVERTER1Did	= 0xD389,
+	XSENSCONVERTER2Did	= 0xD38A,
+	XSENSCONVERTER3Did	= 0xD38B,
+	XSENSCONVERTER4Did	= 0xD38C,
+	XSENSCONVERTER5Did	= 0xD38D,
+	XSENSCONVERTER6Did	= 0xD38E,
+	XSENSCONVERTER7Did	= 0xD38F,
+
+	/*
+	 * Xsens Technologies BV products (http://www.xsens.com).
+	 */
+	FTTERATRONIKVCPDid	= 0xEC88,	/* Teratronik device */
+	FTTERATRONIKD2XXDid	= 0xEC89,	/* Teratronik device */
+
+	/*
+	 * Evolution Robotics products (http://www.evolution.com/).
+	 */
+	EVOLUTIONVid	= 0xDEEE,
+	EVOLUTIONER1Did	= 0x0300,		/* ER1 Control Module */
+
+	/* Pyramid Computer GmbH */
+	FTPYRAMIDDid	= 0xE6C8,		/* Pyramid Appliance Display */
+
+	/*
+	 * Posiflex inc retail equipment (http://www.posiflex.com.tw)
+	 */
+	POSIFLEXVid	= 0x0d3a,
+	POSIFLEXPP7000Did= 0x0300,		/* PP-7000II thermal printer */
+
+	/*
+	 * Westrex International devices
+	 */
+	FTWESTREXMODEL777Did	= 0xDC00,	/* Model 777 */
+	FTWESTREXMODEL8900FDid	= 0xDC01,	/* Model 8900F */
+
+	/*
+	 * RR-CirKits LocoBuffer USB (http://www.rr-cirkits.com)
+	 */
+	FTRRCIRKITSLOCOBUFFERDid= 0xc7d0,	/* LocoBuffer USB */
+	FTECLOCOM1WIREDid	= 0xEA90,	/* COM to 1-Wire USB */
+
+	/*
+	 * Papouch products (http://www.papouch.com/)
+	 */
+	PAPOUCHVid	= 0x5050,
+	PAPOUCHTMUDid	= 0x0400,		/* TMU USB Thermometer */
+
+	/*
+	 * ACG Identification Technologies GmbH products http://www.acg.de/
+	 */
+	FTACGHFDUALDid	= 0xDD20,		/* HF Dual ISO Reader (RFID) */
+	/*
+	 * new high speed devices
+	 */
+	FT4232HDid	= 0x6011,		/* FTDI FT4232H based device */
+
+};
+
+/* Commands */
+enum {
+	FTRESET		= 0,		/* Reset the port */
+	FTSETMODEMCTRL,			/* Set the modem control register */
+	FTSETFLOWCTRL,			/* Set flow control register */
+	FTSETBAUDRATE,			/* Set baud rate */
+	FTSETDATA,			/* Set the parameters, parity */
+	FTGETMODEMSTATUS,		/* Retrieve current value of modem ctl */
+	FTSETEVENTCHAR,			/* Set the event character */
+	FTSETERRORCHAR,			/* Set the error character */
+	FTUNKNOWN,
+	FTSETLATENCYTIMER,		/* Set the latency timer */
+	FTGETLATENCYTIMER,		/* Get the latency timer */
+	FTSETBITMODE,			/* Set bit mode */
+	FTGETPINS,			/* Read pins state */
+	FTGETE2READ	= 0x90,		/* Read address from 128-byte I2C EEPROM */
+	FTSETE2WRITE,			/* Write to address on 128-byte I2C EEPROM */
+	FTSETE2ERASE,			/* Erase address on 128-byte I2C EEPROM */
+};
+
+/* Port Identifier Table, index for interfaces */
+enum {
+	PITDEFAULT = 0,		/* SIOA */
+	PITA,			/* SIOA jtag if there is one */
+};
+
+enum {
+	Rftdireq = 1<<6,		/* bit for type of request */
+};
+
+/*
+ * Commands Data size
+ * Sets have wLength = 0
+ * Gets have wValue = 0
+ */
+enum {
+	FTMODEMSTATUSSZ	= 1,
+	FTLATENCYTIMERSZ= 1,
+	FTPINSSZ	= 1,
+	FTE2READSZ	= 2,
+};
+
+/*
+ * bRequest: FTGETE2READ
+ * wIndex: Address of word to read
+ * Data: Will return a word (2 bytes) of data from E2Address
+ * Results put in the I2C 128 byte EEPROM string eeprom+(2*index)
+ */
+
+/*
+ * bRequest: FTSETE2WRITE
+ * wIndex: Address of word to read
+ * wValue: Value of the word
+ * Data: Will return a word (2 bytes) of data from E2Address
+ */
+
+/*
+ * bRequest: FTSETE2ERASE
+ * Erases the EEPROM
+ * wIndex: 0
+ */
+
+/*
+ * bRequest: FTRESET
+ * wValue: Ctl Val
+ * wIndex: Port
+ */
+enum {
+	FTRESETCTLVAL		= 0,
+	FTRESETCTLVALPURGERX	= 1,
+	FTRESETCTLVALPURGETX	= 2,
+};
+
+/*
+ * BmRequestType: SET
+ * bRequest: FTSETBAUDRATE
+ * wValue: BaudDivisor value - see below
+ * Bits 15 to 0 of the 17-bit divisor are placed in the request value.
+ * Bit 16 is placed in bit 0 of the request index.
+ */
+
+/* chip type */
+enum {
+	SIO		= 1,
+	FT8U232AM	= 2,
+	FT232BM		= 3,
+	FT2232C		= 4,
+	FTKINDR		= 5,
+	FT2232H		= 6,
+	FT4232H		= 7,
+};
+
+enum {
+	 FTb300		= 0,
+	 FTb600		= 1,
+	 FTb1200	= 2,
+	 FTb2400	= 3,
+	 FTb4800	= 4,
+	 FTb9600	= 5,
+	 FTb19200	= 6,
+	 FTb38400	= 7,
+	 FTb57600	= 8,
+	 FTb115200	= 9,
+};
+
+/*
+ * bRequest: FTSETDATA
+ * wValue: Data characteristics
+ *	bits 0-7 number of data bits
+ * wIndex: Port
+ */
+enum {
+	FTSETDATAParNONE	= 0 << 8,
+	FTSETDATAParODD		= 1 << 8,
+	FTSETDATAParEVEN	= 2 << 8,
+	FTSETDATAParMARK	= 3 << 8,
+	FTSETDATAParSPACE	= 4 << 8,
+	FTSETDATASTOPBITS1	= 0 << 11,
+	FTSETDATASTOPBITS15	= 1 << 11,
+	FTSETDATASTOPBITS2	= 2 << 11,
+	FTSETBREAK		= 1 << 14,
+};
+
+/*
+ * bRequest: FTSETMODEMCTRL
+ * wValue: ControlValue (see below)
+ * wIndex: Port
+ */
+
+/*
+ * bRequest: FTSETFLOWCTRL
+ * wValue: Xoff/Xon
+ * wIndex: Protocol/Port - hIndex is protocol; lIndex is port
+ */
+enum {
+	FTDISABLEFLOWCTRL= 0,
+	FTRTSCTSHS	= 1 << 8,
+	FTDTRDSRHS	= 2 << 8,
+	FTXONXOFFHS	= 4 << 8,
+};
+
+/*
+ * bRequest: FTGETLATENCYTIMER
+ * wIndex: Port
+ * wLength: 0
+ * Data: latency (on return)
+ */
+
+/*
+ * bRequest: FTSETBITMODE
+ * wIndex: Port
+ * either it is big bang mode, in which case
+ * wValue: 1 byte L is the big bang mode BIG*
+ *	or BM is
+ * wValue: 1 byte bitbang mode H, 1 byte bitmask for lines L
+ */
+enum {
+	BMSERIAL	= 0,		/* reset, turn off bit-bang mode */
+
+	BIGBMNORMAL	= 1,		/* normal bit-bang mode */
+	BIGBMSPI	= 2,		/* spi bit-bang mode */
+
+	BMABM		= 1<<8,		/* async mode */
+	BMMPSSE		= 2<<8,
+	BMSYNCBB	= 4<<8,		/* sync bit-bang -- 2232x and R-type */
+	BMMCU		= 8<<8,		/* MCU Host Bus -- 2232x */
+	BMOPTO		= 0x10<<8,	/* opto-isolated<<8, 2232x */
+	BMCBUS		= 0x20<<8,	/* CBUS pins of R-type chips */
+	BMSYNCFF	= 0x40<<8,	/* Single Channel Sync FIFO, 2232H only */
+};
+
+/*
+ * bRequest: FTSETLATENCYTIMER
+ * wValue: Latency (milliseconds 1-255)
+ * wIndex: Port
+ */
+enum {
+	FTLATENCYDEFAULT = 2,
+};
+
+/*
+ * BmRequestType: SET
+ * bRequest: FTSETEVENTCHAR
+ * wValue: EventChar
+ * wIndex: Port
+ * 0-7 lower bits event char
+ * 8 enable
+ */
+enum {
+	FTEVCHARENAB = 1<<8,
+};
+
+/*
+ * BmRequestType: SET
+ * bRequest: FTSETERRORCHAR
+ * wValue: Error Char
+ * wIndex: Port
+ * 0-7 lower bits event char
+ * 8 enable
+ */
+enum {
+	FTERRCHARENAB = 1<<8,
+};
+/*
+ * BmRequestType: GET
+ * bRequest: FTGETMODEMSTATUS
+ * wIndex: Port
+ * wLength: 1
+ * Data: Status
+ */
+enum {
+	FTCTSMASK	= 0x10,
+	FTDSRMASK	= 0x20,
+	FTRIMASK	= 0x40,
+	FTRLSDMASK	= 0x80,
+};
+
+enum {
+	/* byte 0 of in data hdr */
+	FTICTS	= 1 << 4,
+	FTIDSR	= 1 << 5,
+	FTIRI	= 1 << 6,
+	FTIRLSD	= 1 << 7,	/* receive line signal detect */
+
+	/* byte 1 of in data hdr */
+	FTIDR	= 1<<0,		/* data ready */
+	FTIOE	= 1<<1,		/* overrun error */
+	FTIPE	= 1<<2,		/* parity error */
+	FTIFE	= 1<<3,		/* framing error */
+	FTIBI	= 1<<4,		/* break interrupt */
+	FTITHRE	= 1<<5,		/* xmitter holding register */
+	FTITEMT	= 1<<6,		/* xmitter empty */
+	FTIFIFO	= 1<<7,		/* error in rcv fifo */
+
+	/* byte 0 of out data hdr len does not include byte 0 */
+	FTOLENMSK= 0x3F,
+	FTOPORT	= 0x80,		/* must be set */
+};
+
+extern Serialops ftops;
+
+int	ftmatch(Serial *ser, char *info);
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/mkfile
@@ -1,0 +1,23 @@
+</$objtype/mkfile
+
+TARG=serial
+OFILES=ftdi.$O serial.$O prolific.$O ucons.$O
+HFILES=\
+	../lib/usb.h\
+	ftdi.h\
+	prolific.h\
+	serial.h\
+	ucons.h\
+
+LIB=../lib/usb.a$O
+
+BIN=/$objtype/bin/nusb
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+CFLAGS=-I../lib $CFLAGS
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/prolific.c
@@ -1,0 +1,439 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "usb.h"
+#include "serial.h"
+#include "prolific.h"
+
+Cinfo plinfo[] = {
+	{ 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 },
+};
+
+int
+plmatch(char *info)
+{
+	Cinfo *ip;
+	char buf[50];
+
+	for(ip = plinfo; ip->vid != 0; ip++){
+		snprint(buf, sizeof buf, "vid %#06x did %#06x",
+			ip->vid, ip->did);
+		dsprint(2, "serial: %s %s\n", buf, info);
+		if(strstr(info, buf) != nil)
+			return 0;
+	}
+	return -1;
+}
+
+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(Serialport *p, int val, int index, uchar *buf)
+{
+	int res;
+	Serial *ser;
+
+	ser = p->s;
+
+	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(Serialport *p, int val, int index)
+{
+	int res;
+	Serial *ser;
+
+	ser = p->s;
+
+	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;
+}
+
+/* BUG: I could probably read Dcr0 and set only the bits */
+static int
+plmodemctl(Serialport *p, int set)
+{
+	Serial *ser;
+
+	ser = p->s;
+
+	if(set == 0){
+		p->mctl = 0;
+		vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init);
+		return 0;
+	}
+
+	p->mctl = 1;
+	if(ser->type == TypeHX)
+		vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init|Dcr0HwFcX);
+	else
+		vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init|Dcr0HwFcH);
+	return 0;
+}
+
+static int
+plgetparam(Serialport *p)
+{
+	uchar buf[ParamReqSz];
+	int res;
+	Serial *ser;
+
+	ser = p->s;
+
+
+	res = usbcmd(ser->dev, Rd2h | Rclass | Riface, GetLineReq,
+		0, 0, buf, sizeof buf);
+	p->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)
+		p->stop = 1;
+	else if(buf[4] == 2)
+		p->stop = 2;
+	p->parity = buf[5];
+	p->bits = buf[6];
+
+	dsprint(2, "serial: getparam: ");
+	if(serialdebug)
+		dumpbuf(buf, sizeof buf);
+	dsprint(2, "serial: getparam res: %d\n", res);
+	return res;
+}
+
+static int
+plsetparam(Serialport *p)
+{
+	uchar buf[ParamReqSz];
+	int res;
+	Serial *ser;
+
+	ser = p->s;
+
+	PUT4(buf, p->baud);
+
+	if(p->stop == 1)
+		buf[4] = 0;
+	else if(p->stop == 2)
+		buf[4] = 2; 			/* see comment in getparam */
+	buf[5] = p->parity;
+	buf[6] = p->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(p, p->mctl);
+	plgetparam(p);		/* make sure our state corresponds */
+
+	dsprint(2, "serial: setparam res: %d\n", res);
+	return res;
+}
+
+static int
+revid(ulong devno)
+{
+	switch(devno){
+	case RevH:
+		return TypeH;
+	case RevX:
+	case RevHX:
+	case Rev1:
+		return TypeHX;
+	default:
+		return TypeUnk;
+	}
+}
+
+/* linux driver says the release id is not always right */
+static int
+heuristicid(ulong csp, ulong maxpkt)
+{
+	if(Class(csp) == 0x02)
+		return TypeH;
+	else if(maxpkt == 0x40)
+		return TypeHX;
+	else if(Class(csp) == 0x00 || Class(csp) == 0xFF)
+		return TypeH;
+	else{
+		fprint(2, "serial: chip unknown, setting to HX version\n");
+		return TypeHX;
+	}
+}
+
+static int
+plinit(Serialport *p)
+{
+	char *st;
+	uchar *buf;
+	ulong csp, maxpkt, dno;
+	Serial *ser;
+
+	ser = p->s;
+	buf = emallocz(VendorReqSz, 1);
+	dsprint(2, "plinit\n");
+
+	csp = ser->dev->usb->csp;
+	maxpkt = ser->dev->maxpkt;
+	dno = ser->dev->usb->dno;
+
+	if((ser->type = revid(dno)) == TypeUnk)
+		ser->type = heuristicid(csp, maxpkt);
+
+	dsprint(2, "serial: type %d\n", ser->type);
+
+	vendorread(p, 0x8484, 0, buf);
+	vendorwrite(p, 0x0404, 0);
+	vendorread(p, 0x8484, 0, buf);
+	vendorread(p, 0x8383, 0, buf);
+	vendorread(p, 0x8484, 0, buf);
+	vendorwrite(p, 0x0404, 1);
+	vendorread(p, 0x8484, 0, buf);
+	vendorread(p, 0x8383, 0, buf);
+
+	vendorwrite(p, Dcr0Idx|DcrSet, Dcr0Init);
+	vendorwrite(p, Dcr1Idx|DcrSet, Dcr1Init);
+
+	if(ser->type == TypeHX)
+		vendorwrite(p, Dcr2Idx|DcrSet, Dcr2InitX);
+	else
+		vendorwrite(p, Dcr2Idx|DcrSet, Dcr2InitH);
+
+	plgetparam(p);
+	qunlock(ser);
+	free(buf);
+	st = emallocz(255, 1);
+	qlock(ser);
+	if(serialdebug)
+		serdumpst(p, st, 255);
+	dsprint(2, st);
+	free(st);
+	/* p gets freed by closedev, the process has a reference */
+	incref(ser->dev);
+	proccreate(statusreader, p, 8*1024);
+	return 0;
+}
+
+static int
+plsetbreak(Serialport *p, int val)
+{
+	Serial *ser;
+
+	ser = p->s;
+	return usbcmd(ser->dev, Rh2d | Rclass | Riface,
+		(val != 0? BreakOn: BreakOff), val, 0, nil, 0);
+}
+
+static int
+plclearpipes(Serialport *p)
+{
+	Serial *ser;
+
+	ser = p->s;
+
+	if(ser->type == TypeHX){
+		vendorwrite(p, PipeDSRst, 0);
+		vendorwrite(p, PipeUSRst, 0);
+	}else{
+		if(unstall(ser->dev, p->epout, Eout) < 0)
+			dprint(2, "disk: unstall epout: %r\n");
+		if(unstall(ser->dev, p->epin, Ein) < 0)
+			dprint(2, "disk: unstall epin: %r\n");
+		if(unstall(ser->dev, p->epintr, Ein) < 0)
+			dprint(2, "disk: unstall epintr: %r\n");
+	}
+	return 0;
+}
+
+static int
+setctlline(Serialport *p, uchar val)
+{
+	Serial *ser;
+
+	ser = p->s;
+	return usbcmd(ser->dev, Rh2d | Rclass | Riface, SetCtlReq,
+		val, 0, nil, 0);
+}
+
+static void
+composectl(Serialport *p)
+{
+	if(p->rts)
+		p->ctlstate |= CtlRTS;
+	else
+		p->ctlstate &= ~CtlRTS;
+	if(p->dtr)
+		p->ctlstate |= CtlDTR;
+	else
+		p->ctlstate &= ~CtlDTR;
+}
+
+static int
+plsendlines(Serialport *p)
+{
+	int res;
+
+	dsprint(2, "serial: sendlines: %#2.2x\n", p->ctlstate);
+	composectl(p);
+	res = setctlline(p, p->ctlstate);
+	dsprint(2, "serial: sendlines res: %d\n", res);
+	return 0;
+}
+
+static int
+plreadstatus(Serialport *p)
+{
+	int nr, dfd;
+	char err[40];
+	uchar buf[VendorReqSz];
+	Serial *ser;
+
+	ser = p->s;
+
+	qlock(ser);
+	dsprint(2, "serial: reading from interrupt\n");
+	dfd = p->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, nil, nil, err) < 0){
+			qunlock(ser);
+			return -1;
+		}
+	}
+	if(nr < 0)
+		dsprint(2, "serial: reading status: %r");
+	else if(nr >= sizeof buf - 1){
+		p->dcd = buf[8] & DcdStatus;
+		p->dsr = buf[8] & DsrStatus;
+		p->cts = buf[8] & BreakerrStatus;
+		p->ring = buf[8] & RingStatus;
+		p->cts = buf[8] & CtsStatus;
+		if(buf[8] & FrerrStatus)
+			p->nframeerr++;
+		if(buf[8] & ParerrStatus)
+			p->nparityerr++;
+		if(buf[8] & OvererrStatus)
+			p->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)
+{
+	Serialport *p;
+	Serial *ser;
+
+	p = u;
+	ser = p->s;
+	threadsetname("statusreaderproc");
+	while(plreadstatus(p) >= 0)
+		;
+	fprint(2, "serial: statusreader exiting\n");
+	closedev(ser->dev);
+}
+
+/*
+ * Maximum number of bytes transferred per frame
+ * The output buffer size cannot be increased due to the size encoding
+ */
+
+static int
+plseteps(Serialport *p)
+{
+	devctl(p->epin,  "maxpkt 256");
+	devctl(p->epout, "maxpkt 256");
+	return 0;
+}
+
+Serialops plops = {
+	.init		= plinit,
+	.getparam	= plgetparam,
+	.setparam	= plsetparam,
+	.clearpipes	= plclearpipes,
+	.sendlines	= plsendlines,
+	.modemctl	= plmodemctl,
+	.setbreak	= plsetbreak,
+	.seteps		= plseteps,
+};
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/prolific.h
@@ -1,0 +1,178 @@
+enum {
+	/* flavours of the device */
+	TypeH,
+	TypeHX,
+	TypeUnk,
+
+	RevH		= 0x0202,
+	RevX		= 0x0300,
+	RevHX		= 0x0400,
+	Rev1		= 0x0001,
+
+	/* usbcmd parameters */
+	SetLineReq	= 0x20,
+
+	SetCtlReq	= 0x22,
+
+	BreakReq	= 0x23,
+	BreakOn		= 0xffff,
+	BreakOff	= 0x0000,
+
+	GetLineReq	= 0x21,
+
+	VendorWriteReq	= 0x01,		/* BUG: is this a standard request? */
+	VendorReadReq	= 0x01,
+
+	ParamReqSz	= 7,
+	VendorReqSz	= 10,
+
+	/* status read from interrupt endpoint */
+	DcdStatus	= 0x01,
+	DsrStatus	= 0x02,
+	BreakerrStatus	= 0x04,
+	RingStatus	= 0x08,
+	FrerrStatus	= 0x10,
+	ParerrStatus	= 0x20,
+	OvererrStatus	= 0x40,
+	CtsStatus	= 0x80,
+
+	DcrGet		= 0x80,
+	DcrSet		= 0x00,
+
+	Dcr0Idx		= 0x00,
+
+	Dcr0Init	= 0x0001,
+	Dcr0HwFcH	= 0x0040,
+	Dcr0HwFcX	= 0x0060,
+
+	Dcr1Idx		= 0x01,
+
+	Dcr1Init	= 0x0000,
+	Dcr1InitH	= 0x0080,
+	Dcr1InitX	= 0x0000,
+
+	Dcr2Idx		= 0x02,
+
+	Dcr2InitH	= 0x0024,
+	Dcr2InitX	= 0x0044,
+
+	PipeDSRst	= 0x08,
+	PipeUSRst	= 0x09,
+
+};
+
+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,
+};
+
+extern Serialops plops;
+int	plmatch(char *info);
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/serial.c
@@ -1,0 +1,876 @@
+/*
+ * This part takes care of locking except for initialization and
+ * other threads created by the hw dep. drivers.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "usb.h"
+#include "serial.h"
+#include "prolific.h"
+#include "ucons.h"
+#include "ftdi.h"
+
+int serialdebug;
+static int sdebug;
+
+Serialport **ports;
+int nports;
+
+static void
+serialfatal(Serial *ser)
+{
+	Serialport *p;
+	int i;
+
+	dsprint(2, "serial: fatal error, detaching\n");
+	devctl(ser->dev, "detach");
+
+	for(i = 0; i < ser->nifcs; i++){
+		p = &ser->p[i];
+		if(p->w4data != nil)
+			chanclose(p->w4data);
+		if(p->gotdata != nil)
+			chanclose(p->gotdata);
+		if(p->readc)
+			chanclose(p->readc);
+	}
+}
+
+/* I sleep with the lock... only way to drain in general */
+static void
+serialdrain(Serialport *p)
+{
+	Serial *ser;
+	uint baud, pipesize;
+
+	ser = p->s;
+	baud = p->baud;
+
+	if(p->baud == ~0)
+		return;
+	if(ser->maxwtrans < 256)
+		pipesize = 256;
+	else
+		pipesize = ser->maxwtrans;
+	/* wait for the at least 256-byte pipe to clear */
+	sleep(10 + pipesize/((1 + baud)*1000));
+	if(ser->clearpipes != nil)
+		ser->clearpipes(p);
+}
+
+int
+serialreset(Serial *ser)
+{
+	Serialport *p;
+	int i, res;
+
+	res = 0;
+	/* cmd for reset */
+	for(i = 0; i < ser->nifcs; i++){
+		p = &ser->p[i];
+		serialdrain(p);
+	}
+	if(ser->reset != nil)
+		res = ser->reset(ser, nil);
+	return res;
+}
+
+/* call this if something goes wrong, must be qlocked */
+int
+serialrecover(Serial *ser, Serialport *p, Dev *ep, char *err)
+{
+	if(p != nil)
+		dprint(2, "serial[%d], %s: %s, level %d\n", p->interfc,
+			p->name, err, ser->recover);
+	else
+		dprint(2, "serial[%s], global error, level %d\n",
+			ser->p[0].name, ser->recover);
+	ser->recover++;
+	if(strstr(err, "detached") != nil)
+		return -1;
+	if(ser->recover < 3){
+		if(p != nil){	
+			if(ep != nil){
+				if(ep == p->epintr)
+					unstall(ser->dev, p->epintr, Ein);
+				if(ep == p->epin)
+					unstall(ser->dev, p->epin, Ein);
+				if(ep == p->epout)
+					unstall(ser->dev, p->epout, Eout);
+				return 0;
+			}
+
+			if(p->epintr != nil)
+				unstall(ser->dev, p->epintr, Ein);
+			if(p->epin != nil)
+				unstall(ser->dev, p->epin, Ein);
+			if(p->epout != nil)
+				unstall(ser->dev, p->epout, Eout);
+		}
+		return 0;
+	}
+	if(ser->recover > 4 && ser->recover < 8)
+		serialfatal(ser);
+	if(ser->recover > 8){
+		ser->reset(ser, p);
+		return 0;
+	}
+	if(serialreset(ser) < 0)
+		return -1;
+	return 0;
+}
+
+static int
+serialctl(Serialport *p, char *cmd)
+{
+	Serial *ser;
+	int c, i, n, nf, nop, nw, par, drain, set, lines;
+	char *f[16];
+	uchar x;
+
+	ser = p->s;
+	drain = set = lines = 0;
+	nf = tokenize(cmd, f, nelem(f));
+	for(i = 0; i < nf; i++){
+		if(strncmp(f[i], "break", 5) == 0){
+			if(ser->setbreak != nil)
+				ser->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++;
+			ser->setbreak(p, 1);
+			sleep(n);
+			ser->setbreak(p, 0);
+			break;
+		case 'l':
+			drain++;
+			p->bits = n;
+			set++;
+			break;
+		case 'm':
+			drain++;
+			if(ser->modemctl != nil)
+				ser->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][1];
+			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;
+			if(ser->wait4write != nil)
+				nw = ser->wait4write(p, &x, 1);
+			else
+				nw = write(p->epout->dfd, &x, 1);
+			if(nw != 1){
+				serialrecover(ser, p, p->epout, "");
+				return -1;
+			}
+			break;
+		}
+		/*
+		 * don't print.  the condition is harmless and the print
+		 * splatters all over the display.
+		 */
+		USED(nop);
+		if (0 && nop)
+			fprint(2, "serial: %c, unsupported nop ctl\n", c);
+	}
+	if(drain)
+		serialdrain(p);
+	if(lines && !set){
+		if(ser->sendlines != nil && ser->sendlines(p) < 0)
+			return -1;
+	} else if(set){
+		if(ser->setparam != nil && ser->setparam(p) < 0)
+			return -1;
+	}
+	ser->recover = 0;
+	return 0;
+}
+
+char *pformat = "noems";
+
+char *
+serdumpst(Serialport *p, char *buf, int bufsz)
+{
+	char *e, *s;
+	Serial *ser;
+
+	ser = p->s;
+
+	e = buf + bufsz;
+	s = seprint(buf, e, "b%d ", p->baud);
+	s = seprint(s, e, "c%d ", p->dcd);	/* unimplemented */
+	s = seprint(s, e, "d%d ", p->dtr);
+	s = seprint(s, e, "e%d ", p->dsr);	/* unimplemented */
+	s = seprint(s, e, "l%d ", p->bits);
+	s = seprint(s, e, "m%d ", p->mctl);
+	if(p->parity >= 0 || p->parity < strlen(pformat))
+		s = seprint(s, e, "p%c ", pformat[p->parity]);
+	else
+		s = seprint(s, e, "p%c ", '?');
+	s = seprint(s, e, "r%d ", p->rts);
+	s = seprint(s, e, "s%d ", p->stop);
+	s = seprint(s, e, "i%d ", p->fifo);
+	s = seprint(s, e, "\ndev(%d) ", 0);
+	s = seprint(s, e, "type(%d)  ", ser->type);
+	s = seprint(s, e, "framing(%d) ", p->nframeerr);
+	s = seprint(s, e, "overruns(%d) ", p->novererr);
+	s = seprint(s, e, "berr(%d) ", p->nbreakerr);
+	s = seprint(s, e, " serr(%d)\n", p->nparityerr);
+	return s;
+}
+
+static int
+serinit(Serialport *p)
+{
+	int res;
+	res = 0;
+	Serial *ser;
+
+	ser = p->s;
+
+	if(ser->init != nil)
+		res = ser->init(p);
+	if(ser->getparam != nil)
+		ser->getparam(p);
+	p->nframeerr = p->nparityerr = p->nbreakerr = p->novererr = 0;
+
+	return res;
+}
+
+static void
+dattach(Req *req)
+{
+	req->fid->qid = (Qid) {0, 0, QTDIR};
+	req->ofcall.qid = req->fid->qid;
+	respond(req, nil);
+}
+
+static int
+dirgen(int n, Dir *d, void *)
+{
+	if(n >= nports * 2)
+		return -1;
+	d->qid.path = n + 1;
+	d->qid.vers = 0;
+	if(n >= 0)
+		d->qid.type = 0;
+	else
+		d->qid.type = QTDIR;
+	d->uid = strdup("usb");
+	d->gid = strdup(d->uid);
+	d->muid = strdup(d->uid);
+	if(n >= 0){
+		d->name = smprint((n & 1) ? "%sctl" : "%s", ports[n/2]->name);
+		d->mode = ((n & 1) ? 0664 : 0660);
+	}else{
+		d->name = strdup("");
+		d->mode = 0555 | QTDIR;
+	}
+	d->atime = d->mtime = time(0);
+	d->length = 0;
+	return 0;
+}
+
+static char *
+dwalk(Fid *fid, char *name, Qid *qidp)
+{
+	int i;
+	int len;
+	Qid qid;
+	char *p;
+
+	qid = fid->qid;
+	if((qid.type & QTDIR) == 0){
+		return "walk in non-directory";
+	}
+
+	if(strcmp(name, "..") == 0){
+		fid->qid.path = 0;
+		fid->qid.vers = 0;
+		fid->qid.type = QTDIR;
+		*qidp = fid->qid;
+		return nil;
+	}
+
+	for(i = 0; i < nports; i++)
+		if(strncmp(name, ports[i]->name, len = strlen(ports[i]->name)) == 0){
+			p = name + len;
+			if(*p == 0)
+				fid->qid.path = 2 * i + 1;
+			else if(strcmp(p, "ctl") == 0)
+				fid->qid.path = 2 * i + 2;
+			else
+				continue;
+			fid->qid.vers = 0;
+			fid->qid.type = 0;
+			*qidp = fid->qid;
+			return nil;
+		}
+	return "does not exist";
+}
+
+static void
+dstat(Req *req)
+{
+	if(dirgen(req->fid->qid.path - 1, &req->d, nil) < 0)
+		respond(req, "the front fell off");
+	else
+		respond(req, nil);
+}
+
+enum {
+	Serbufsize	= 255,
+};
+
+static void
+readproc(void *aux)
+{
+	int dfd;
+	Req *req;
+	long count, rcount;
+	void *data;
+	Serial *ser;
+	Serialport *p;
+	static int errrun, good;
+	char err[Serbufsize];
+
+	p = aux;
+	ser = p->s;
+	for(;;){
+		qlock(&p->readq);
+		while(p->readfirst == nil)
+			rsleep(&p->readrend);
+		req = p->readfirst;
+		p->readfirst = req->aux;
+		if(p->readlast == req)
+			p->readlast = nil;
+		req->aux = nil;
+		qunlock(&p->readq);
+
+		count = req->ifcall.count;
+		data = req->ofcall.data;
+		qlock(ser);
+		if(count > ser->maxread)
+			count = ser->maxread;
+		dsprint(2, "serial: reading from data\n");
+		do {
+			err[0] = 0;
+			dfd = p->epin->dfd;
+			if(usbdebug >= 3)
+				dsprint(2, "serial: reading: %ld\n", count);
+	
+			assert(count > 0);
+			if(ser->wait4data != nil)
+				rcount = ser->wait4data(p, data, count);
+			else{
+				qunlock(ser);
+				rcount = read(dfd, data, count);
+				qlock(ser);
+			}
+			/*
+			 * if we encounter a long run of continuous read
+			 * errors, do something drastic so that our caller
+			 * doesn't just spin its wheels forever.
+			 */
+			if(rcount < 0) {
+				snprint(err, Serbufsize, "%r");
+				++errrun;
+				sleep(20);
+				if (good > 0 && errrun > 10000) {
+					/* the line has been dropped; give up */
+					qunlock(ser);
+					fprint(2, "%s: line %s is gone: %r\n",
+						argv0, p->name);
+					threadexitsall("serial line gone");
+				}
+			} else {
+				errrun = 0;
+				good++;
+			}
+			if(usbdebug >= 3)
+				dsprint(2, "serial: read: %s %ld\n", err, rcount);
+		} while(rcount < 0 && strstr(err, "timed out") != nil);
+	
+		dsprint(2, "serial: read from bulk %ld, %10.10s\n", rcount, err);
+		if(rcount < 0){
+			dsprint(2, "serial: need to recover, data read %ld %r\n",
+				count);
+			serialrecover(ser, p, p->epin, err);
+		}
+		dsprint(2, "serial: read from bulk %ld\n", rcount);
+		if(rcount >= 0){
+			req->ofcall.count = rcount;
+			respond(req, nil);
+		} else
+			responderror(req);
+		qunlock(ser);
+	}
+}
+
+static void
+dread(Req *req)
+{
+	char *e;	/* change */
+	Qid q;
+	Serial *ser;
+	vlong offset;
+	Serialport *p;
+	static char buf[Serbufsize];
+	
+	q = req->fid->qid;
+	
+	if(q.path == 0){
+		dirread9p(req, dirgen, nil);
+		respond(req, nil);
+		return;
+	}
+
+	p = ports[(q.path - 1) / 2];
+	ser = p->s;
+	offset = req->ifcall.offset;
+
+	memset(buf, 0, sizeof buf);
+	qlock(ser);
+	switch((long)((q.path - 1) % 2)){
+	case 0:
+		qlock(&p->readq);
+		if(p->readfirst == nil)
+			p->readfirst = req;
+		else
+			p->readlast->aux = req;
+		p->readlast = req;
+		rwakeup(&p->readrend);
+		qunlock(&p->readq);
+		break;
+	case 1:
+		if(offset == 0) {
+			if(!p->isjtag){
+				e = serdumpst(p, buf, Serbufsize);
+				readbuf(req, buf, e - buf);
+			}
+		}
+		respond(req, nil);
+		break;
+	}
+	qunlock(ser);
+}
+
+static long
+altwrite(Serialport *p, uchar *buf, long count)
+{
+	int nw, dfd;
+	char err[128];
+	Serial *ser;
+
+	ser = p->s;
+	do{
+		dsprint(2, "serial: write to bulk %ld\n", count);
+
+		if(ser->wait4write != nil)
+			/* unlocked inside later */
+			nw = ser->wait4write(p, buf, count);
+		else{
+			dfd = p->epout->dfd;
+			qunlock(ser);
+			nw = write(dfd, buf, count);
+			qlock(ser);
+		}
+		rerrstr(err, sizeof err);
+		dsprint(2, "serial: written %s %d\n", err, nw);
+	} while(nw < 0 && strstr(err, "timed out") != nil);
+
+	if(nw != count){
+		dsprint(2, "serial: need to recover, status in write %d %r\n",
+			nw);
+		snprint(err, sizeof err, "%r");
+		serialrecover(p->s, p, p->epout, err);
+	}
+	return nw;
+}
+
+static void
+dwrite(Req *req)
+{
+	ulong path;
+	char *cmd;
+	Serial *ser;
+	long count;
+	void *buf;
+	Serialport *p;
+
+	path = req->fid->qid.path;
+	p = ports[(path-1)/2];
+	ser = p->s;
+	count = req->ifcall.count;
+	buf = req->ifcall.data;
+
+	qlock(ser);
+	switch((long)((path-1)%2)){
+	case 0:
+		count = altwrite(p, (uchar *)buf, count);
+		break;
+	case 1:
+		if(p->isjtag)
+			break;
+		cmd = emallocz(count+1, 1);
+		memmove(cmd, buf, count);
+		cmd[count] = 0;
+		if(serialctl(p, cmd) < 0){
+			qunlock(ser);
+			free(cmd);
+			respond(req, "bad control request");
+			return;
+		}
+		free(cmd);
+		break;
+	}
+	if(count >= 0)
+		ser->recover = 0;
+	else
+		serialrecover(ser, p, p->epout, "writing");
+	qunlock(ser);
+	if(count >= 0){
+		req->ofcall.count = count;
+		respond(req, nil);
+	} else
+		responderror(req);
+}
+
+static int
+openeps(Serialport *p, int epin, int epout, int epintr)
+{
+	Serial *ser;
+
+	ser = p->s;
+	p->epin = openep(ser->dev, epin);
+	if(p->epin == nil){
+		fprint(2, "serial: openep %d: %r\n", epin);
+		return -1;
+	}
+	p->epout = openep(ser->dev, epout);
+	if(p->epout == nil){
+		fprint(2, "serial: openep %d: %r\n", epout);
+		closedev(p->epin);
+		return -1;
+	}
+
+	if(!p->isjtag){
+		devctl(p->epin,  "timeout 1000");
+		devctl(p->epout, "timeout 1000");
+	}
+
+	if(ser->hasepintr){
+		p->epintr = openep(ser->dev, epintr);
+		if(p->epintr == nil){
+			fprint(2, "serial: openep %d: %r\n", epintr);
+			closedev(p->epin);
+			closedev(p->epout);
+			return -1;
+		}
+		opendevdata(p->epintr, OREAD);
+		devctl(p->epintr, "timeout 1000");
+	}
+
+	if(ser->seteps!= nil)
+		ser->seteps(p);
+	opendevdata(p->epin, OREAD);
+	opendevdata(p->epout, OWRITE);
+	if(p->epin->dfd < 0 ||p->epout->dfd < 0 ||
+	    (ser->hasepintr && p->epintr->dfd < 0)){
+		fprint(2, "serial: open i/o ep data: %r\n");
+		closedev(p->epin);
+		closedev(p->epout);
+		if(ser->hasepintr)
+			closedev(p->epintr);
+		return -1;
+	}
+	return 0;
+}
+
+static int
+findendpoints(Serial *ser, int ifc)
+{
+	int i, epin, epout, epintr;
+	Ep *ep, **eps;
+
+	epintr = epin = epout = -1;
+
+	/*
+	 * interfc 0 means start from the start which is equiv to
+	 * iterate through endpoints probably, could be done better
+	 */
+	eps = ser->dev->usb->conf[0]->iface[ifc]->ep;
+
+	for(i = 0; i < Nep; i++){
+		if((ep = eps[i]) == nil)
+			continue;
+		if(ser->hasepintr && 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[%d]: ep ids: in %d out %d intr %d\n", ifc, epin, epout, epintr);
+	if(epin == -1 || epout == -1 || (ser->hasepintr && epintr == -1))
+		return -1;
+
+	if(openeps(&ser->p[ifc], epin, epout, epintr) < 0)
+		return -1;
+
+	dprint(2, "serial: ep in %s out %s\n", ser->p[ifc].epin->dir, ser->p[ifc].epout->dir);
+	if(ser->hasepintr)
+		dprint(2, "serial: ep intr %s\n", ser->p[ifc].epintr->dir);
+
+	if(usbdebug > 1 || serialdebug > 2){
+		devctl(ser->p[ifc].epin,  "debug 1");
+		devctl(ser->p[ifc].epout, "debug 1");
+		if(ser->hasepintr)
+			devctl(ser->p[ifc].epintr, "debug 1");
+		devctl(ser->dev, "debug 1");
+	}
+	return 0;
+}
+
+/* keep in sync with main.c */
+static void
+usage(void)
+{
+	fprint(2, "usage: usb/serial [-dD] [-m mtpt] [-s srv] devid\n");
+	threadexitsall("usage");
+}
+
+static void
+serdevfree(void *a)
+{
+	Serial *ser = a;
+	Serialport *p;
+	int i;
+
+	if(ser == nil)
+		return;
+
+	for(i = 0; i < ser->nifcs; i++){
+		p = &ser->p[i];
+
+		if(ser->hasepintr)
+			closedev(p->epintr);
+		closedev(p->epin);
+		closedev(p->epout);
+		p->epintr = p->epin = p->epout = nil;
+		if(p->w4data != nil)
+			chanfree(p->w4data);
+		if(p->gotdata != nil)
+			chanfree(p->gotdata);
+		if(p->readc)
+			chanfree(p->readc);
+
+	}
+	free(ser);
+}
+
+static Srv serialfs = {
+	.attach = dattach,
+	.walk1 =	dwalk,
+	.read =	dread,
+	.write=	dwrite,
+	.stat =	dstat,
+};
+
+/*
+static void
+serialfsend(void)
+{
+	if(p->w4data != nil)
+		chanclose(p->w4data);
+	if(p->gotdata != nil)
+		chanclose(p->gotdata);
+	if(p->readc)
+		chanclose(p->readc);
+}
+*/
+
+void
+threadmain(int argc, char* argv[])
+{
+	Serial *ser;
+	Dev *dev;
+	char buf[50];
+	int i, devid;
+	Serialport *p;
+
+	ARGBEGIN{
+	case 'd':
+		serialdebug++;
+		break;
+	default:
+		usage();
+	}ARGEND
+	if(argc != 1)
+		usage();
+	devid = atoi(*argv);
+	dev = getdev(devid);
+	if(dev == nil)
+		sysfatal("getdev: %r");
+
+	ser = dev->aux = emallocz(sizeof(Serial), 1);
+	ser->maxrtrans = ser->maxwtrans = sizeof ser->p[0].data;
+	ser->maxread = ser->maxwrite = sizeof ser->p[0].data;
+	ser->dev = dev;
+	dev->free = serdevfree;
+	ser->jtag = -1;
+	ser->nifcs = 1;
+
+	snprint(buf, sizeof buf, "vid %#06x did %#06x",
+		dev->usb->vid, dev->usb->did);
+	if(plmatch(buf) == 0){
+		ser->hasepintr = 1;
+		ser->Serialops = plops;
+	} else if(uconsmatch(buf) == 0)
+		ser->Serialops = uconsops;
+	else if(ftmatch(ser, buf) == 0)
+		ser->Serialops = ftops;
+	else {
+		sysfatal("no serial devices found");
+	}
+	for(i = 0; i < ser->nifcs; i++){
+		p = &ser->p[i];
+		p->interfc = i;
+		p->s = ser;
+		if(i == ser->jtag){
+			p->isjtag++;
+		}
+		if(findendpoints(ser, i) < 0)
+			sysfatal("no endpoints found for ifc %d", i);
+		p->w4data  = chancreate(sizeof(ulong), 0);
+		p->gotdata = chancreate(sizeof(ulong), 0);
+	}
+
+	qlock(ser);
+	serialreset(ser);
+	for(i = 0; i < ser->nifcs; i++){
+		p = &ser->p[i];
+		dprint(2, "serial: valid interface, calling serinit\n");
+		if(serinit(p) < 0){
+			sysfatal("wserinit: %r");
+		}
+
+		dsprint(2, "serial: adding interface %d, %p\n", p->interfc, p);
+		if(p->isjtag){
+			snprint(p->name, sizeof p->name, "jtag");
+			dsprint(2, "serial: JTAG interface %d %p\n", i, p);
+			snprint(p->name, sizeof p->name, "jtag%d.%d", devid, i);
+		} else {
+			snprint(p->name, sizeof p->name, "eiaU");
+			if(i == 0)
+				snprint(p->name, sizeof p->name, "eiaU%d", devid);
+			else
+				snprint(p->name, sizeof p->name, "eiaU%d.%d", devid, i);
+		}
+		fprint(2, "%s...", p->name);
+		incref(dev);
+		p->readrend.l = &p->readq;
+		p->readpid = proccreate(readproc, p, mainstacksize);
+		ports = realloc(ports, (nports + 1) * sizeof(Serialport*));
+		ports[nports++] = p;
+	}
+
+	qunlock(ser);
+	if(nports > 0){
+		snprint(buf, sizeof buf, "serial-%d", devid);
+		threadpostsharesrv(&serialfs, nil, "usb", buf);
+	}
+}
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/serial.h
@@ -1,0 +1,130 @@
+typedef struct Serial Serial;
+typedef struct Serialops Serialops;
+typedef struct Serialport Serialport;
+
+struct Serialops {
+	int	(*seteps)(Serialport*);
+	int	(*init)(Serialport*);
+	int	(*getparam)(Serialport*);
+	int	(*setparam)(Serialport*);
+	int	(*clearpipes)(Serialport*);
+	int	(*reset)(Serial*, Serialport*);
+	int	(*sendlines)(Serialport*);
+	int	(*modemctl)(Serialport*, int);
+	int	(*setbreak)(Serialport*, int);
+	int	(*readstatus)(Serialport*);
+	int	(*wait4data)(Serialport*, uchar *, int);
+	int	(*wait4write)(Serialport*, uchar *, int);
+};
+
+enum {
+	DataBufSz = 8*1024,
+	Maxifc = 16,
+};
+
+
+struct Serialport {
+	char name[32];
+	Serial	*s;		/* device we belong to */
+	int	isjtag;
+
+	Dev	*epintr;	/* may not exist */
+
+	Dev	*epin;
+	Dev	*epout;
+
+	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;
+	int	rlsd;
+
+	vlong	timer;
+	int	blocked;	/* for sw flow ctl. BUG: not implemented yet */
+	int	nbreakerr;
+	int	ring;
+	int	nframeerr;
+	int	nparityerr;
+	int	novererr;
+	int	enabled;
+
+	int	interfc;	/* interfc on the device for ftdi */
+
+	Channel *w4data;
+	Channel *gotdata;
+	Channel *readc;		/* to uncouple reads, only used in ftdi... */
+	int	ndata;
+	uchar	data[DataBufSz];
+	
+	QLock readq;
+	Req *readfirst, *readlast; /* read request queue */
+	int readpid;
+	Rendez readrend;
+};
+
+struct Serial {
+	QLock;
+	Dev	*dev;		/* usb device*/
+
+	int	type;		/* serial model subtype */
+	int	recover;	/* # of non-fatal recovery tries */
+	Serialops;
+
+	int	hasepintr;
+
+	int	jtag;		/* index of jtag interface, -1 none */
+	int	nifcs;		/* # of serial interfaces, including JTAG */
+	Serialport p[Maxifc];
+	int	maxrtrans;
+	int	maxwtrans;
+
+	int	maxread;
+	int	maxwrite;
+
+	int	inhdrsz;
+	int	outhdrsz;
+	int	baudbase;	/* for special baud base settings, see ftdi */
+};
+
+enum {
+	/* soft flow control chars */
+	CTLS	= 023,
+	CTLQ	= 021,
+	CtlDTR	= 1,
+	CtlRTS	= 2,
+};
+
+/*
+ * !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 plinfo[];
+extern Cinfo uconsinfo[];
+extern int serialdebug;
+
+#define	dsprint	if(serialdebug)fprint
+
+int	serialrecover(Serial *ser, Serialport *p, Dev *ep, char *err);
+int	serialreset(Serial *ser);
+char	*serdumpst(Serialport *p, char *buf, int bufsz);
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/ucons.c
@@ -1,0 +1,48 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "usb.h"
+#include "serial.h"
+#include "ucons.h"
+
+Cinfo uconsinfo[] = {
+	{ Net20DCVid,	Net20DCDid },
+	{ 0,		0 },
+};
+
+int
+uconsmatch(char *info)
+{
+	Cinfo *ip;
+	char buf[50];
+
+	for(ip = uconsinfo; ip->vid != 0; ip++){
+		snprint(buf, sizeof buf, "vid %#06x did %#06x",
+			ip->vid, ip->did);
+		dsprint(2, "serial: %s %s\n", buf, info);
+		if(strstr(info, buf) != nil)
+			return 0;
+	}
+	return -1;
+}
+
+static int
+ucseteps(Serialport *p)
+{
+	Serial *ser;
+
+	ser = p->s;
+
+	p->baud = ~0;	/* not real port */
+	ser->maxrtrans = ser->maxwtrans = 8;
+	devctl(p->epin,  "maxpkt 8");
+	devctl(p->epout, "maxpkt 8");
+	return 0;
+}
+
+/* all nops */
+Serialops uconsops = {
+	.seteps = ucseteps,
+};
--- /dev/null
+++ b/sys/src/cmd/nusb/serial/ucons.h
@@ -1,0 +1,9 @@
+
+
+enum {
+	Net20DCVid =	0x0525,	/* Ajays usb debug cable */
+	Net20DCDid =	0x127a,
+};
+
+int	uconsmatch(char *info);
+extern Serialops uconsops;