usbcmd, classname, closedev, configdev, devctl, getdev, loaddevstr, opendev, opendevdata, openep, unstall – USB device driver library

#include <u.h>
#include <libc.h>
#include "../lib/usb.h"
struct Dev {
char* dir;         /* path for the endpoint dir */
int     id;          /* usb id for device or ep. number */
int     dfd;         /* descriptor for the data file */
int     cfd;         /* descriptor for the control file */
int     isusb3;           /* this is a usb3 device */
int     depth;            /* hub depth for usb3 hubs */
int     maxpkt;           /* cached from usb description */
Usbdev*      usb;         /* USB description */
Ep*     ep;          /* endpoint from epopen() */
void* aux;         /* for the device driver */
char* hname;            /* hash name, unique for device */
struct Usbdev {
int     ver;         /* usb version */
ulong csp;         /* USB class/subclass/proto */
int     vid;         /* vendor id */
int     did;         /* product (device) id */
int     dno;         /* device release number */
char* vendor;
char* product;
char* serial;
int     vsid;
int     psid;
int     ssid;
int     class;            /* from descriptor */
int     nconf;            /* from descriptor */
Conf* conf[Nconf];       /* configurations */
Ep*     ep[Epmax+1];       /* all endpoints in device (chained), indexed by address */
Desc* ddesc[Nddesc];     /* (raw) device specific descriptors */
struct Ep {
Iface*       iface;            /* the endpoint belongs to */
Conf* conf;        /* the endpoint belongs to */
int     id;          /* endpoint number: (id & Epmax) == endpoint address */
uchar dir;         /* direction, Ein/Eout/Eboth */
uchar type;        /* Econtrol, Eiso, Ebulk, Eintr */
int     attrib;
int     pollival;
int     maxpkt;           /* max. packet size */
int     ntds;        /* nb. of Tds per µframe */
/* chain of endpoints with same address (used in different interfaces/altsettings) */
Ep*     next;
void* aux;         /* for the driver program */
struct Iface {
int     id;          /* interface number */
int     alt;         /* altsetting for this interface */
ulong csp;         /* USB class/subclass/proto */
Iface*       next;        /* chain of interfaces of different altsettings */
Ep*     ep[Nep];     /* consecutive array of endpoints in this interface (not including ep0) */
void* aux;         /* for the driver program */
struct Conf {
int     cval;        /* value for set configuration */
int     attrib;
int     milliamps; /* maximum power in this config. */
Iface*       iface[Niface];     /* up to 16 interfaces */
struct Desc {
Conf* conf;        /* where this descriptor was read */
Iface*       iface;            /* last iface before desc in conf. */
Ep*     ep;          /* last endpt before desc in conf. */
DDesc data;        /* unparsed standard USB descriptor */
struct DDesc {
uchar bLength;
uchar bDescriptorType;
uchar bbytes[1];
/* extra bytes allocated here to keep the rest of it */
#define Class(csp)      ((csp)&0xff)
#define Subclass(csp) (((csp)>>8)&0xff)
#define Proto(csp)      (((csp)>>16)&0xff)
#define CSP(c, s, p)    ((c) | ((s)<<8) | ((p)<<16))
#define      GET2(p)           ...
#define      PUT2(p,v)    ...
#define      GET4(p)           ...
#define      PUT4(p,v)    ...
#define dprint      if(usbdebug)fprint
#define ddprint if(usbdebug > 1)fprint
int     Ufmt(Fmt *f);
char* classname(int c);
void    closedev(Dev *d);
int     configdev(Dev *d);
int     devctl(Dev *dev, char *fmt, ...);
void* emallocz(ulong size, int zero);
char* estrdup(char *s);
char* hexstr(void *a, int n);
char* loaddevstr(Dev *d, int sid);
Dev*    opendev(char *fn);
int     opendevdata(Dev *d, int mode);
Dev*    openep(Dev *d, Ep *e);
int     unstall(Dev *dev, Dev *ep, int dir);
int     usbcmd(Dev *d, int type, int req,
int value, int index, uchar *data, int count);
Dev*    getdev(char *devid);
extern int usbdebug;    /* more messages for bigger values */

This library provides convenience structures and functions to write USB device drivers. It is not intended for user programs using USB devices. See usb(3) for a description of the interfaces provided for that purpose.

Usb drivers rely on usb(3) to perform I/O through USB as well as on usbd to perform the initial configuration for the device's setup endpoint. The rest of the work is up to the driver and is where this library may help.

An endpoint as provided by usb(3) is represented by a Dev data structure. The setup endpoint for a device represents the USB device, because it is the means to configure and operate the device. This structure is reference counted. Functions creating Devs adjust the number of references to one, initially. The driver is free to call incref (in lock(2)) to add references and closedev to drop references (and release resources when the last one vanishes). As an aid to the driver, the field aux may keep driver–specific data.

Dev.dir holds the path for the endpoint's directory.

The field id keeps the device number for setup endpoints and the endpoint number for all other endpoints. The endpoint number identifies devusb endpoint and is unique within a device. For example, it would be 3 for /dev/usb/ep3.0 and 1 for /dev/usb/ep3.1. It is easy to remember this because the former is created to operate on the device, while the later has been created as a particular endpoint to perform I/O.

The field ep holds the endpoint structure that was passed in epopen which gives easy access to the endpoint configuration.

Fields dfd and cfd keep the data and control file descriptors, respectively. When a Dev is created the control file is open, initially. Opening the data file requires calling opendevdata with the appropriate mode.

When the device configuration information has been loaded (see below), maxpkt holds the maximum packet size (in bytes) for the endpoint and usb keeps the rest of the USB information.

Most of the information in usb comes from parsing various device and configuration descriptors provided by the device, by calling one of the functions described later. Only descriptors unknown to the library are kept unparsed at usb.ddesc as an aid for the driver (which should know how to parse them and what to do with the information).


Getdev is the primary entry point for device setup. It takes a numeric device address or device path which usually gets passed to drivers as a program argument and sets up the device, retuning a configured Dev representing the setup endpoint of the device.

Opendev creates a Dev for the endpoint with directory fn. Usually, the endpoint is a setup endpoint representing a device. The endpoint control file is open, but the data file is not. The USB description is void. In most cases drivers call getdev and openep and do not call this function directly.

Configdev opens the data file for the device supplied and loads and parses its configuration information. After calling it, the device is ready for I/O and the USB description in Dev.usb is valid. In most cases drivers call getdev and do not call this function directly.

Control requests for an endpoint may be written by calling devctl in the style of print(2). It is better not to call print directly because the control request should be issued as a single write system call. See usb(3) for a list of available control requests (not to be confused with USB control transfers performed on a control endpoint).

Opendevdata opens the data file for the device according to the given mode. The mode must match that of the endpoint, doing otherwise is considered an error. Actual I/O is performed by reading/writing the descriptor kept in the dfd field of Dev.

For control endpoints, it is not necessary to call read and write directly. Instead, usbcmd issues a USB control request to the device d (not to be confused with a usb(3) control request sent to its control file). Usbcmd retries the control request several times upon failure because some devices require it. The format of requests is fixed per the USB standard: type is the type of request and req identifies the request. Arguments value and index are parameters to the request and the last two arguments, data and count, are similar to read and write arguments. However, data may be nil if no transfer (other than the control request) has to take place. The library header file includes numerous symbols defined to help writing the type and arguments for a request.

The return value from usbcmd is the number of bytes transferred, zero to indicate a stall and –1 to indicate an error.

A common request is to unstall an endpoint that has been stalled due to some reason by the device (eg., when read or write indicate a count of zero bytes read or written on the endpoint). The function unstall does this. It is given the device that stalled the endpoint, dev, the stalled endpoint, ep, and the direction of the stall (one of Ein or Eout). The function takes care of notifying the device of the unstall as well as notifying the kernel.

Class returns the class part of the number given, representing a CSP. Subclass does the same for the device subclass and Proto for the protocol. The counterpart is CSP, which builds a CSP from the device class, subclass, and protocol. For some classes, classname knows the name (for those with constants in the library header file).

The macros GET2 and PUT2 get and put a (little–endian) two–byte value and are useful to parse descriptors and replies for control requests.

Functions emallocz and estrdup are similar to mallocz and strdup but abort program operation upon failure.

The function Ufmt is a format routine suitable for fmtinstall(2) to print a Dev data structure. The auxiliary hexstr returns a string representing a dump (in hexadecimal) of n bytes starting at a. The string is allocated using malloc(2) and memory must be released by the caller.

Loaddevstr returns the string obtained by reading the device string descriptor number sid.


usb(3), nusb(4).

Not heavily exercised yet.