--- sys/src/cmd/usb/serial/serial.h
+++ sys/src/cmd/usb/serial/serial.h
@@ -3,6 +3,7 @@
typedef struct Serialport Serialport;
struct Serialops {
+ int (*findeps)(Serialport*, int*, int*, int*);
int (*seteps)(Serialport*);
int (*init)(Serialport*);
int (*getparam)(Serialport*);
@@ -59,6 +60,7 @@
int nparityerr;
int novererr;
int enabled;
+ int opens; /* open data and ctl files */
int interfc; /* interfc on the device for ftdi */
--- sys/src/cmd/usb/serial/acm.h
+++ sys/src/cmd/usb/serial/acm.h
@@ -0,0 +1,3 @@
+extern Serialops acmops;
+
+int acmmatch(Serial *ser, char *info);
--- sys/src/cmd/usb/serial/acm.c
+++ sys/src/cmd/usb/serial/acm.c
@@ -0,0 +1,552 @@
+/* USB communications devices, abstract control model */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "usb.h"
+#include "usbfs.h"
+#include "serial.h"
+#include "acm.h"
+
+enum {
+ Scacm = 0x02, /* abstract control model subclass */
+ Maxproto = 0x06, /* last protocol code for ACM ports */
+
+ /* functional descriptor subtypes */
+ Fncallmgmt = 0x01,
+ Fnacm = 0x02,
+ Fnunion = 0x06,
+
+ /* call management descriptor bmCapabilities */
+ Cmself = 1<<0, /* device handles call management */
+ Cmdata = 1<<1, /* call management over the data interface */
+
+ /* ACM descriptor bmCapabilities */
+ Commcaps = 1<<0, /* comm feature requests */
+ Linecaps = 1<<1, /* line coding, line state, serial state */
+ Breakcaps = 1<<2, /* send break */
+
+ /* usbcmd parameters */
+ SetCommReq = 0x02,
+ AbstractState = 0x01,
+ Multiplexed = 1<<1,
+ SetLineReq = 0x20,
+ GetLineReq = 0x21,
+ SetCtlReq = 0x22,
+ BreakReq = 0x23,
+ BreakOn = 0xffff,
+ BreakOff = 0x0000,
+
+ ParamReqSz = 7,
+
+ /* notification read from interrupt endpoint */
+ SerialState = 0x20,
+ NotifyHdrSz = 8,
+ StateSz = 2,
+ NotifySz = NotifyHdrSz + StateSz, /* the part kept */
+
+ /* bits in the serial state */
+ DcdStatus = 0x01,
+ DsrStatus = 0x02,
+ BreakerrStatus = 0x04,
+ RingStatus = 0x08,
+ FrerrStatus = 0x10,
+ ParerrStatus = 0x20,
+ OvererrStatus = 0x40,
+};
+
+static void statusreader(void *u);
+
+static int
+isacm(ulong csp)
+{
+ return Class(csp) == Clcomms && Subclass(csp) == Scacm &&
+ Proto(csp) <= Maxproto;
+}
+
+/* functional descriptor of the given subtype following ifc */
+static uchar*
+funcdesc(Usbdev *ud, Iface *ifc, int subtype, int minlen)
+{
+ int i;
+ uchar *b;
+ Desc *dd;
+
+ for(i = 0; i < nelem(ud->ddesc); i++)
+ if((dd = ud->ddesc[i]) != nil && dd->conf == ud->conf[0] &&
+ dd->iface == ifc){
+ b = (uchar*)&dd->data;
+ if(b[0] >= minlen &&
+ b[1] == Dfunction && b[2] == subtype)
+ return b;
+ }
+ return nil;
+}
+
+/* the data interface named by the union descriptor */
+static Iface*
+finddataifc(Usbdev *ud, Iface *ci)
+{
+ int id;
+ uchar *b;
+ Conf *c;
+
+ b = funcdesc(ud, ci, Fnunion, 5);
+ if(b == nil || b[3] != ci->id)
+ return nil;
+ c = ud->conf[0];
+ id = b[4];
+ if(id >= nelem(c->iface) || c->iface[id] == nil ||
+ Class(c->iface[id]->csp) != Cldata)
+ return nil;
+ return c->iface[id];
+}
+
+/* ep is listed by an interface other than ifc */
+static int
+isshared(Conf *c, Iface *ifc, Ep *ep)
+{
+ int i, j;
+ Iface *o;
+
+ for(i = 0; i < nelem(c->iface); i++){
+ if((o = c->iface[i]) == nil || o == ifc)
+ continue;
+ for(j = 0; j < nelem(o->ep); j++)
+ if(o->ep[j] == ep)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * The Ep for endpoint number N describes both 0x8N and 0x0N.
+ * When the two belong to different interfaces, the Ep is listed
+ * by both and keeps the type, address and interface of the
+ * descriptor parsed last; only that half is known.
+ */
+static int
+isbulk(Conf *c, Iface *ifc, Ep *ep, int dir)
+{
+ int last;
+
+ if(ep->type != Ebulk)
+ return 0;
+ if(ep->dir == dir)
+ return 1;
+ if(ep->dir != Eboth)
+ return 0;
+ if(!isshared(c, ifc, ep))
+ return 1;
+ last = (ep->addr & 0x80) != 0? Ein: Eout;
+ return ep->iface == ifc && last == dir;
+}
+
+static int
+portendpoints(Usbdev *ud, Iface *ci, int *epin, int *epout, int *epintr)
+{
+ int i;
+ Conf *c;
+ Ep *ep;
+ Iface *di;
+
+ c = ud->conf[0];
+ *epin = *epout = *epintr = -1;
+ if((di = finddataifc(ud, ci)) == nil){
+ werrstr("no data interface for interface %d", ci->id);
+ return -1;
+ }
+ for(i = 0; i < nelem(ci->ep); i++){
+ ep = ci->ep[i];
+ if(ep != nil && ep->type == Eintr && ep->dir == Ein){
+ *epintr = ep->id;
+ break;
+ }
+ }
+ for(i = 0; i < nelem(di->ep); i++){
+ if((ep = di->ep[i]) == nil)
+ continue;
+ if(*epin == -1 && isbulk(c, di, ep, Ein))
+ *epin = ep->id;
+ if(*epout == -1 && isbulk(c, di, ep, Eout))
+ *epout = ep->id;
+ }
+ if(*epin == -1 || *epout == -1){
+ werrstr("no bulk endpoints for interface %d", ci->id);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * One port per control interface. serial.c opens a status
+ * endpoint for every port or for none.
+ */
+static int
+findports(Serial *ser)
+{
+ int i, n, epin, epout, epintr;
+ Conf *c;
+ Iface *ifc;
+ Usbdev *ud;
+
+ ud = ser->dev->usb;
+ c = ud->conf[0];
+ n = 0;
+ ser->hasepintr = 1;
+ for(i = 0; i < nelem(c->iface); i++){
+ ifc = c->iface[i];
+ if(ifc == nil || !isacm(ifc->csp))
+ continue;
+ if(portendpoints(ud, ifc, &epin, &epout, &epintr) < 0){
+ fprint(2, "serial: acm: %r\n");
+ continue;
+ }
+ if(epintr == -1)
+ ser->hasepintr = 0;
+ ser->p[n++].interfc = ifc->id;
+ }
+ if(n == 0){
+ ser->hasepintr = 0;
+ werrstr("no acm ports");
+ return -1;
+ }
+ ser->nifcs = n;
+ return 0;
+}
+
+int
+acmmatch(Serial *ser, char *info)
+{
+ int r;
+ char *s;
+
+ if(ser != nil){
+ qlock(ser);
+ r = findports(ser);
+ qunlock(ser);
+ return r;
+ }
+ for(s = info; (s = strstr(s, "csp ")) != nil; s += 4)
+ if(isacm(strtoul(s+4, nil, 0)))
+ return 0;
+ return -1;
+}
+
+/* bmCapabilities of the call management or ACM descriptor */
+static int
+funccaps(Serialport *p, int subtype)
+{
+ uchar *b;
+ Usbdev *ud;
+
+ ud = p->s->dev->usb;
+ b = funcdesc(ud, ud->conf[0]->iface[p->interfc], subtype, 4);
+ if(b == nil)
+ return 0;
+ return b[3];
+}
+
+static int
+acmfindeps(Serialport *p, int *epin, int *epout, int *epintr)
+{
+ Usbdev *ud;
+
+ ud = p->s->dev->usb;
+ return portendpoints(ud, ud->conf[0]->iface[p->interfc],
+ epin, epout, epintr);
+}
+
+static int
+acmseteps(Serialport *p)
+{
+ Serial *ser;
+
+ ser = p->s;
+ /*
+ * The device flow-controls the data endpoints with NAK,
+ * and a timed out write cannot tell how much was sent.
+ */
+ if(devctl(p->epin, "timeout 0") < 0 ||
+ devctl(p->epout, "timeout 0") < 0){
+ fprint(2, "can't set timeout on %s: %r\n", p->epin->dir);
+ return -1;
+ }
+ /* one packet per read: a short or full packet ends it */
+ if(p->epin->maxpkt > 0 && p->epin->maxpkt < ser->maxread)
+ ser->maxread = p->epin->maxpkt;
+ return 0;
+}
+
+static int
+acminit(Serialport *p)
+{
+ uchar buf[2];
+ Serial *ser;
+
+ ser = p->s;
+ dsprint(2, "acminit\n");
+ /*
+ * A device that takes call management over the data
+ * interface gets it multiplexed there (PSTN 1.2 3.2.2.1).
+ * A refusal is not an error.
+ */
+ if((funccaps(p, Fncallmgmt) & (Cmself|Cmdata)) == (Cmself|Cmdata) &&
+ (funccaps(p, Fnacm) & Commcaps) != 0){
+ PUT2(buf, Multiplexed);
+ usbcmd(ser->dev, Rh2d | Rclass | Riface, SetCommReq,
+ AbstractState, p->interfc, buf, sizeof buf);
+ }
+ if(ser->hasepintr){
+ /* p gets freed by closedev, the process has a reference */
+ incref(ser->dev);
+ proccreate(statusreader, p, 8*1024);
+ }
+ return 0;
+}
+
+static int
+acmgetparam(Serialport *p)
+{
+ int res;
+ uchar buf[ParamReqSz];
+ Serial *ser;
+
+ ser = p->s;
+ res = usbcmd(ser->dev, Rd2h | Rclass | Riface, GetLineReq, 0,
+ p->interfc, buf, sizeof buf);
+ dsprint(2, "serial: getparam res: %d\n", res);
+ if(res != ParamReqSz || buf[5] > 4){
+ /* uart defaults */
+ p->baud = 9600;
+ p->bits = 8;
+ p->parity = 0;
+ p->stop = 1;
+ return 0;
+ }
+ p->baud = GET4(buf);
+ p->stop = buf[4] == 2? 2: 1; /* 1.5 stop bits read as 1 */
+ p->parity = buf[5];
+ p->bits = buf[6];
+ return 0;
+}
+
+/*
+ * The line requests go out even when the ACM descriptor lacks
+ * Linecaps: such devices may still act on them. A refusal is an
+ * error only from a device that declares Linecaps.
+ */
+static int
+acmsendlines(Serialport *p)
+{
+ int res;
+ Serial *ser;
+
+ ser = p->s;
+ p->ctlstate = 0;
+ if(p->dtr)
+ p->ctlstate |= CtlDTR;
+ if(p->rts)
+ p->ctlstate |= CtlRTS;
+ dsprint(2, "serial: sendlines: %#2.2x\n", p->ctlstate);
+ res = usbcmd(ser->dev, Rh2d | Rclass | Riface, SetCtlReq,
+ p->ctlstate, p->interfc, nil, 0);
+ dsprint(2, "serial: sendlines res: %d\n", res);
+ if(res < 0 && (funccaps(p, Fnacm) & Linecaps) != 0)
+ return -1;
+ return 0;
+}
+
+static int
+acmsetparam(Serialport *p)
+{
+ int res;
+ uchar buf[ParamReqSz];
+ Serial *ser;
+
+ ser = p->s;
+ dsprint(2, "serial: setparam: b%ud l%d p%d s%d\n",
+ p->baud, p->bits, p->parity, p->stop);
+ PUT4(buf, p->baud);
+ buf[4] = p->stop == 2? 2: 0;
+ buf[5] = p->parity;
+ buf[6] = p->bits;
+ res = usbcmd(ser->dev, Rh2d | Rclass | Riface, SetLineReq, 0,
+ p->interfc, buf, sizeof buf);
+ dsprint(2, "serial: setparam res: %d\n", res);
+ if(res < 0 && (funccaps(p, Fnacm) & Linecaps) != 0)
+ return -1;
+ /* serialctl sends no line state with line parameters */
+ return acmsendlines(p);
+}
+
+static int
+acmsetbreak(Serialport *p, int val)
+{
+ Serial *ser;
+
+ ser = p->s;
+ if((funccaps(p, Fnacm) & Breakcaps) == 0){
+ werrstr("break not supported");
+ return -1;
+ }
+ return usbcmd(ser->dev, Rh2d | Rclass | Riface, BreakReq,
+ (val != 0? BreakOn: BreakOff), p->interfc, nil, 0);
+}
+
+static int
+unstalleps(Serial *ser, Serialport *p)
+{
+ int r;
+
+ r = 0;
+ if(p->epintr != nil && unstall(ser->dev, p->epintr, Ein) < 0)
+ r = -1;
+ if(p->epin != nil && unstall(ser->dev, p->epin, Ein) < 0)
+ r = -1;
+ if(p->epout != nil && unstall(ser->dev, p->epout, Eout) < 0)
+ r = -1;
+ return r;
+}
+
+static int
+acmreset(Serial *ser, Serialport *p)
+{
+ int i, r;
+
+ if(p != nil)
+ return unstalleps(ser, p);
+ r = 0;
+ for(i = 0; i < ser->nifcs; i++)
+ if(unstalleps(ser, &ser->p[i]) < 0)
+ r = -1;
+ return r;
+}
+
+/* a zero-length packet is not end of file */
+static int
+wait4data(Serialport *p, uchar *data, int count)
+{
+ int n;
+
+ qunlock(p->s);
+ while((n = read(p->epin->dfd, data, count)) == 0)
+ ;
+ qlock(p->s);
+ return n;
+}
+
+/* one packet from the status endpoint: 0 if none came, -1 to stop */
+static int
+readnotify(Serialport *p, uchar *buf, int nbuf)
+{
+ int nr, dfd;
+ char err[40];
+ Serial *ser;
+
+ ser = p->s;
+ qlock(ser);
+ dfd = p->epintr->dfd;
+ qunlock(ser);
+ nr = read(dfd, buf, nbuf);
+ if(nr >= 0)
+ return nr;
+ rerrstr(err, sizeof err);
+ if(strstr(err, "timed out") != nil)
+ return 0;
+ dsprint(2, "serial: need to recover, status read %s\n", err);
+ qlock(ser);
+ nr = serialrecover(ser, p, p->epintr, err);
+ qunlock(ser);
+ return nr < 0? -1: 0;
+}
+
+/* a whole notification; ser locked */
+static void
+updatestatus(Serialport *p, uchar *msg, int len)
+{
+ int st;
+
+ if(msg[1] != SerialState || GET2(msg+4) != p->interfc ||
+ len < NotifyHdrSz + StateSz){
+ dsprint(2, "serial: notification %#x ignored\n", msg[1]);
+ return;
+ }
+ st = GET2(msg+NotifyHdrSz);
+ p->dcd = (st & DcdStatus) != 0;
+ p->dsr = (st & DsrStatus) != 0;
+ p->ring = (st & RingStatus) != 0;
+ if(st & BreakerrStatus)
+ p->nbreakerr++;
+ if(st & FrerrStatus)
+ p->nframeerr++;
+ if(st & ParerrStatus)
+ p->nparityerr++;
+ if(st & OvererrStatus)
+ p->novererr++;
+}
+
+/*
+ * A notification is a header and wLength bytes, sent as
+ * full packets and a short last one (USB 2.0 5.7.3).
+ * Each read takes one packet, so none overruns the read.
+ */
+static void
+statusreader(void *u)
+{
+ int n, m, nr, len, npkt;
+ uchar *pkt, msg[NotifySz];
+ Serialport *p;
+ Serial *ser;
+
+ p = u;
+ ser = p->s;
+ threadsetname("statusreaderproc");
+ npkt = p->epintr->maxpkt;
+ if(npkt < NotifyHdrSz)
+ npkt = NotifyHdrSz;
+ pkt = emallocz(npkt, 0);
+ n = len = 0;
+ while((nr = readnotify(p, pkt, npkt)) >= 0){
+ if(nr == 0)
+ continue;
+ if(n == 0){
+ if(nr < NotifyHdrSz ||
+ pkt[0] != (Rd2h | Rclass | Riface)){
+ dsprint(2, "serial: bad notification\n");
+ continue;
+ }
+ len = NotifyHdrSz + GET2(pkt+6);
+ }
+ m = nr;
+ if(m > NotifySz - n)
+ m = NotifySz - n;
+ if(m > 0)
+ memmove(msg+n, pkt, m);
+ n += nr;
+ if(n < len && nr == npkt)
+ continue; /* more packets follow */
+ if(n >= len){
+ qlock(ser);
+ updatestatus(p, msg, len);
+ qunlock(ser);
+ } else
+ dsprint(2, "serial: short notification, "
+ "%d of %d bytes\n", n, len);
+ n = 0;
+ }
+ free(pkt);
+ fprint(2, "serial: statusreader exiting\n");
+ closedev(ser->dev);
+}
+
+Serialops acmops = {
+ .findeps = acmfindeps,
+ .seteps = acmseteps,
+ .init = acminit,
+ .getparam = acmgetparam,
+ .setparam = acmsetparam,
+ .reset = acmreset,
+ .sendlines = acmsendlines,
+ .setbreak = acmsetbreak,
+ .wait4data = wait4data,
+};
--- sys/src/cmd/usb/serial/serial.c
+++ sys/src/cmd/usb/serial/serial.c
@@ -14,6 +14,7 @@
#include "ucons.h"
#include "ftdi.h"
#include "silabs.h"
+#include "acm.h"
int serialdebug;
@@ -415,29 +416,85 @@
return 0;
}
+/*
+ * As uart(3): the first open of the data or ctl file programs
+ * the line and raises DTR and RTS; the last close drops them.
+ */
+static void
+setlines(Serialport *p, int on)
+{
+ Serial *ser;
+
+ ser = p->s;
+ p->dtr = p->rts = on;
+ if(ser->sendlines != nil)
+ ser->sendlines(p);
+}
+
static int
dopen(Usbfs *fs, Fid *fid, int)
{
ulong path;
Serialport *p;
+ Serial *ser;
path = fid->qid.path & ~fs->qid;
p = fs->aux;
+ ser = p->s;
+ qlock(ser);
switch(path){ /* BUG: unneeded? */
case Qdata:
dsprint(2, "serial, opened data\n");
break;
case Qctl:
dsprint(2, "serial, opened ctl\n");
- if(p->isjtag)
+ if(p->isjtag){
+ qunlock(ser);
return 0;
+ }
serialctl(p, "l8 i1"); /* default line parameters */
break;
+ default:
+ qunlock(ser);
+ return 0;
+ }
+ if(!p->isjtag && p->opens++ == 0){
+ /* as uartenable: defaults for unset values */
+ if(p->bits == 0)
+ p->bits = 8;
+ if(p->stop == 0)
+ p->stop = 1;
+ if(p->baud == 0)
+ p->baud = 9600;
+ if(ser->setparam != nil)
+ ser->setparam(p);
+ setlines(p, 1);
}
+ qunlock(ser);
return 0;
}
+static void
+dclunk(Usbfs *fs, Fid *fid)
+{
+ ulong path;
+ Serialport *p;
+ Serial *ser;
+ path = fid->qid.path & ~fs->qid;
+ if(fid->omode == ONONE || (path != Qdata && path != Qctl))
+ return;
+ p = fs->aux;
+ if(p->isjtag)
+ return;
+ ser = p->s;
+ qlock(ser);
+ if(--p->opens == 0)
+ setlines(p, 0);
+ qunlock(ser);
+}
+
+
static void
filldir(Usbfs *fs, Dir *d, Dirtab *tab, int i, void *v)
{
@@ -704,6 +761,12 @@
epintr = epin = epout = -1;
+ if(ser->findeps != nil){
+ if(ser->findeps(&ser->p[ifc], &epin, &epout, &epintr) < 0)
+ return -1;
+ goto Found;
+ }
+
/*
* interfc 0 means start from the start which is equiv to
* iterate through endpoints probably, could be done better
@@ -723,6 +786,7 @@
epout = ep->id;
}
}
+Found:
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;
@@ -783,6 +847,7 @@
static Usbfs serialfs = {
.walk = dwalk,
+ .clunk= dclunk,
.open = dopen,
.read = dread,
.write= dwrite,
@@ -833,6 +898,8 @@
dev->free = serdevfree;
ser->jtag = -1;
ser->nifcs = 1;
+ for(i = 0; i < nelem(ser->p); i++)
+ ser->p[i].interfc = i;
snprint(buf, sizeof buf, "vid %#06x did %#06x",
dev->usb->vid, dev->usb->did);
@@ -845,13 +912,14 @@
ser->Serialops = ftops;
else if(slmatch(buf) == 0)
ser->Serialops = slops;
+ else if(acmmatch(ser, buf) == 0)
+ ser->Serialops = acmops;
else {
werrstr("serial: no serial devices found");
return -1;
}
for(i = 0; i < ser->nifcs; i++){
p = &ser->p[i];
- p->interfc = i;
p->s = ser;
p->fs = serialfs;
if(i == ser->jtag){
--- sys/src/cmd/usb/serial/main.c
+++ sys/src/cmd/usb/serial/main.c
@@ -8,6 +8,7 @@
#include "prolific.h"
#include "ftdi.h"
#include "silabs.h"
+#include "acm.h"
enum {
Arglen = 80,
@@ -27,7 +28,8 @@
matchserial(char *info, void*)
{
if(uconsmatch(info) == 0 || plmatch(info) == 0 ||
- ftmatch(nil, info) == 0 || slmatch(info) == 0)
+ ftmatch(nil, info) == 0 || slmatch(info) == 0 ||
+ acmmatch(nil, info) == 0)
return 0;
return -1;
}
--- sys/src/cmd/usb/serial/mkfile
+++ sys/src/cmd/usb/serial/mkfile
@@ -2,9 +2,10 @@
TARG=serial
OFILES=main.$O
-LIBDOFILES=ftdi.$O serial.$O prolific.$O ucons.$O silabs.$O
+LIBDOFILES=ftdi.$O serial.$O prolific.$O ucons.$O silabs.$O acm.$O
HFILES=\
../lib/usb.h\
+ acm.h\
ftdi.h\
prolific.h\
serial.h\
--- sys/src/cmd/usb/usbd/usbdb
+++ sys/src/cmd/usb/usbd/usbdb
@@ -10,6 +10,8 @@
serial class=255 csp=0xffffff vid=0x9e88 did=0x9e8f args=
serial class=255 csp=0xffffff vid=0x0403 args=
serial class=255 csp=0x0000ff vid=0x10c4 args=
+ serial csp=0x000202 csp=0x010202 csp=0x020202 csp=0x030202 args=
+ serial csp=0x040202 csp=0x050202 csp=0x060202 args=
# wifi class=0 csp=0 vid=0x0bda did=0x8192 args=
# wifi class=0 csp=0 vid=0x148f did=0x2870 args=
auto
--- sys/man/4/usb
+++ sys/man/4/usb
@@ -290,8 +290,25 @@
.BI eia N status
in
.IR uart (3).
+As there, the first open of either file programs the line
+and raises DTR and RTS, and the last close drops them.
Options are similar to those of
.IR disk .
+.PP
+CDC ACM devices
+have one serial port per control interface.
+A break
+.RB ( k )
+reaches the device only if it declares support for it.
+A device that takes call management over its data interface
+is switched to multiplexed call management there.
+If the device sends serial state notifications,
+.B c
+and
+.B e
+in
+.B eiaUctl
+report its carrier and data set ready.
.PP
JTAG ports are similar
but the files are named
@@ -504,7 +521,8 @@
Both the Ethernet and printer drivers have not
been tested and it is likely they will fail.
.PP
-The serial driver works only for the Prolific chip and Ftdi,
+The serial driver works only for the Prolific chip, Ftdi,
+Silicon Labs and CDC ACM devices,
and control of the
.B dcd
and
@@ -512,3 +530,25 @@
signals and some of the extra features are unimplemented.
For Ftdi, only the Sheevaplug and Guruplug have been tried.
There is support for the EHCI debug port, but it loses bytes.
+.PP
+CDC ACM devices without a union descriptor
+are not supported.
+.PP
+The USB library and the kernel keep one endpoint per endpoint number.
+A CDC ACM device that uses one number in two interfaces
+loses its serial state or its port.
+.PP
+Writes that are a multiple of the packet size
+are not followed by a zero-length packet.
+.PP
+Usbd starts one driver per device.
+A device that also has a mass storage interface,
+such as a DAPLink debug board,
+is given to
+.I disk
+and gets no serial port;
+moving the serial entries above
+.B disk
+in usbd's
+.B usbdb
+and rebuilding usbd reverses the choice.
|