Logo Search packages:      
Sourcecode: aes2501-wy version File versions  Download package

usb.c

/*
 * Userspace USB interface for Linux
 * Copyright (C) 2006 Wittawat Yamwong <wittawat@web.de>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/* TODO: iso transfer */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <linux/compiler.h>
#include <linux/usbdevice_fs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <string.h>
#include <sys/time.h>
#include <linux/version.h>

#ifdef WITH_RESMGR
#include <resmgr.h>
#endif

#include "usb.h"


#define INLINE __inline__
#define GET_DESC_TIMEOUT 1000/*ms*/
#define DEFAULT_UDEV_USB_ROOT "/dev/bus/usb"
#define DEFAULT_USBFS_ROOT    "/proc/bus/usb"
#define DEFAULT_USBFS_PATTERN "%03u/%03u"

/* This should be less than or equal MAX_USBFS_BUFFER_SIZE defined in
 * linux/drivers/usb/core/devio.c */
#define MAX_URB_BUFFER_SIZE (16*1024)

#ifndef NAME_MAX
# define NAME_MAX 255
#endif

#ifndef timercmp
# define timercmp(tvp, uvp, cmp)          \
         ((tvp)->tv_sec cmp (uvp)->tv_sec ||    \
        ((tvp)->tv_sec == (uvp)->tv_sec &&      \
         (tvp)->tv_usec cmp (uvp)->tv_usec))
#endif

struct USB_device {
    USBDevice *next;
    USBDeviceLocation location;
    int fd, rdwr;
    USBDeviceDescriptor deviced;
    uint8_t **rawconfd;
    unsigned nconfd;
};

typedef struct URBNode {
    struct URBNode *next;
    USBURB urb;
} URBNode;


static char usbRoot[NAME_MAX+1];
static const char *usbRootPattern;
static const char *usbfsPattern;
static const char *udevPattern;
static const char *resmgrPattern;
static USBDevice *firstDevice = NULL;
static URBNode *firstURB = NULL;

static int usbOpen(USBDevice *dev, int rdwr);
static int usbClose(USBDevice *dev);


static INLINE
USBDeviceLocation usbLocation(uint8_t bus, uint8_t device)
{
    return (bus << 8) | device;
}


static INLINE
uint8_t usbBusNumber(USBDeviceLocation l)
{
    return (uint8_t)(l >> 8);
}


static INLINE
uint8_t usbDeviceNumber(USBDeviceLocation l)
{
    return (uint8_t)l;
}


static INLINE
uint16_t getLe16(const uint16_t *le)
{
    const uint8_t *b = (const uint8_t *)le;
    return (b[1] << 8) | b[0];
}


static INLINE
void setLe16(uint16_t *le, uint16_t native)
{
    uint8_t *b = (uint8_t *)le;
    b[0] = native;
    b[1] = native >> 8;
}


static
const char *guessDirPattern(const char *root)
{
    DIR *dir;
    const char *pat = NULL;
    struct dirent *de;
    char *endptr;
    int temp;
    char s[10];

    dir = opendir(root);
    if (!dir)
      return NULL;
    while ((de = readdir(dir))) {
      if (de->d_name[0] == '.')
          continue;
      temp = strtol(de->d_name, &endptr, 10);
      if (endptr[0] == '\0' && temp >= 0) {
          sprintf(s, "%d", temp);
          if (strcmp(de->d_name, s) == 0) {
            pat = "%u/%u";
          } else {
            pat = "%03u/%03u";
          }
          break;
      }
    }
    closedir(dir);
    return pat;
}


static
void makeDeviceName(char *path, unsigned size, const char *pattern,
                const char *root, USBDeviceLocation l)
{
    unsigned len;

    snprintf(path, size, "%s/", root);
    len = strlen(path);
    snprintf(path + len, size - len, pattern, usbBusNumber(l),
           usbDeviceNumber(l));
}


static
int openInUsbfs(USBDeviceLocation l, int rdwr)
{
    char path[NAME_MAX];

    if (!usbfsPattern) {
      errno = ENOENT;
      return -1;
    }
    makeDeviceName(path, sizeof(path), usbfsPattern, DEFAULT_USBFS_ROOT, l);
    return open(path, rdwr ? O_RDWR : O_RDONLY);
}


static
int openInUdev(USBDeviceLocation l, int rdwr)
{
    char path[NAME_MAX];

    if (!udevPattern) {
      errno = ENOENT;
      return -1;
    }
    makeDeviceName(path, sizeof(path), udevPattern, DEFAULT_UDEV_USB_ROOT, l);
    return open(path, rdwr ? O_RDWR : O_RDONLY);
}


static
int openInResmgr(USBDeviceLocation l, int rdwr)
{
#ifdef WITH_RESMGR
    char path[NAME_MAX];

    snprintf(path, sizeof(path), resmgrPattern,
           usbBusNumber(l), usbDeviceNumber(l));
    return rsm_open_device(path, rdwr ? O_RDWR : O_RDONLY);
#else
    (void) l;
    (void) rdwr;
    errno = ENOENT;
    return -1;
#endif
}


static
void freeConfigBuf(USBDevice *dev)
{
    unsigned i;

    if (!dev->rawconfd)
      return;
    for (i=0; i != dev->nconfd; i++)
      free(dev->rawconfd[i]);
    free(dev->rawconfd);
    dev->rawconfd = NULL;
    dev->nconfd = 0;
}


static
void readConfigurationDescriptor(USBDevice *dev, int idx)
{
    int result;
    USBConfigurationDescriptor confd, *c;
    uint8_t *buf;

    result = usbGetDescriptor(dev, USB_DT_CONFIGURATION, idx, 0, sizeof(confd),
                        &confd);
    if (result != sizeof(confd))
      return;
    confd.wTotalLength = getLe16(&confd.wTotalLength);
    free(dev->rawconfd[idx]);
    buf = (uint8_t *) malloc(confd.wTotalLength);
    dev->rawconfd[idx] = buf;
    if (!buf)
      return;
    result = usbGetDescriptor(dev, USB_DT_CONFIGURATION, idx, 0,
                        confd.wTotalLength, buf);
    if (result < (int)sizeof(USBConfigurationDescriptor)) {
      free(dev->rawconfd[idx]);
      dev->rawconfd[idx] = NULL;
      return;
    }
    c = (USBConfigurationDescriptor *) buf;
    setLe16(&c->wTotalLength, result);
}


static
void readDescriptors(USBDevice *dev)
{
    int result,i;

    result = usbGetDescriptor(dev, USB_DT_DEVICE, 0, 0, sizeof(dev->deviced),
                        &dev->deviced);
    if (result != sizeof(dev->deviced)) {
      dev->deviced.bLength = 0;
      return;
    }
    dev->deviced.bcdUSB = getLe16(&dev->deviced.bcdUSB);
    dev->deviced.idVendor = getLe16(&dev->deviced.idVendor);
    dev->deviced.idProduct = getLe16(&dev->deviced.idProduct);
    dev->deviced.bcdDevice = getLe16(&dev->deviced.bcdDevice);

    freeConfigBuf(dev);
    dev->rawconfd = (uint8_t **)
      calloc(dev->deviced.bNumConfigurations, sizeof(*dev->rawconfd));
    if (!dev->rawconfd)
      return;
    dev->nconfd = dev->deviced.bNumConfigurations;
    for (i=0; i != dev->deviced.bNumConfigurations; i++) {
      readConfigurationDescriptor(dev, i);
    }
}


static
int usbOpen(USBDevice *dev, int rdwr)
{
    int result = 0;
    char path[NAME_MAX];

    if (dev->fd >= 0 && dev->rdwr < rdwr) {
      usbClose(dev); /* Open with O_RDWR later. */
    }
    if (dev->fd == -1) {
      makeDeviceName(path, sizeof(path), usbRootPattern, usbRoot,
                   dev->location);
      dev->fd = open(path, rdwr ? O_RDWR : O_RDONLY);
      if (dev->fd == -1)
          dev->fd = openInUdev(dev->location, rdwr);
      if (dev->fd == -1)
          dev->fd = openInUsbfs(dev->location, rdwr);
      if (dev->fd == -1)
          dev->fd = openInResmgr(dev->location, rdwr);
      if (dev->fd == -1) {
          result = -errno;
      } else {
          dev->rdwr = rdwr;
          readDescriptors(dev);
      }
    }
    return result;
}


static
int usbClose(USBDevice *dev)
{
    if (dev->fd != -1) {
      if (close(dev->fd) == -1)
          return -errno;
      dev->fd = -1;
    }
    return 0;
}


static
int usbScanDevicesInBus(unsigned bus, const char *bus_name,
                  uint16_t vid, uint16_t pid,
                  USBFindDevicesCallback callback, void *data)
{
    DIR *dir;
    char path[NAME_MAX+1];
    struct dirent *de;
    USBDevice *dev;
    char *endptr;
    const USBDeviceDescriptor *ddesc;
    int stop = 0;
    unsigned idVendor, idProduct;

    snprintf(path, sizeof(path), "%s/%s", usbRoot, bus_name);
    dir = opendir(path);
    if (!dir)
      return stop;
    while (!stop && (de = readdir(dir))) {
      int devnum = strtol(de->d_name, &endptr, 10);
      if (*endptr != '\0' || devnum < 0)
          continue;

      dev = usbGetDevice(usbLocation(bus, devnum));
      ddesc = usbGetDeviceDescriptor(dev);
      if (!ddesc) {
          usbFreeDevice(dev);
          continue;
      }
      idVendor = ddesc->idVendor;
      idProduct = ddesc->idProduct;
      usbFreeDevice(dev);

      if ((vid == 0 || (idVendor == vid)) &&
          (pid == 0 || (idProduct == pid)))
          stop = callback(usbLocation(bus, devnum), data);
    }
    closedir(dir);
    return stop;
}


void usbInit(const char *root)
{
    usbRoot[0] = '\0';
    udevPattern = guessDirPattern(DEFAULT_UDEV_USB_ROOT);
    usbfsPattern = guessDirPattern(DEFAULT_USBFS_ROOT);
    resmgrPattern = "usb:%u,%u";

    if (root) {
      strncpy(usbRoot, root, sizeof(usbRoot));
      usbRootPattern = guessDirPattern(usbRoot);
      if (usbRootPattern)
          return;
    }

    if (udevPattern) {
      strncpy(usbRoot, DEFAULT_UDEV_USB_ROOT, sizeof(usbRoot));
      usbRootPattern = udevPattern;
      return;
    }

    if (usbfsPattern) {
      strncpy(usbRoot, DEFAULT_USBFS_ROOT, sizeof(usbRoot));
      usbRootPattern = usbfsPattern;
      return;
    }

    /* Fallback to usbfs even if it isn't mounted. */
    strncpy(usbRoot, DEFAULT_USBFS_ROOT, sizeof(usbRoot));
    usbRootPattern = DEFAULT_USBFS_PATTERN;
}


void usbCleanup(void)
{
    while (firstDevice)
      usbFreeDevice(firstDevice);
    while (firstURB) {
      firstURB->urb.status = 0;
      usbDestroyUrb(&(firstURB->urb));
    }
}


const char *usbGetRootDir(void)
{
    return usbRoot;
}


void usbFindDevices(uint16_t vid, uint16_t pid,
                USBFindDevicesCallback callback, void *data)
{
    DIR *dir;
    struct dirent *de;
    char *endptr;
    int stop = 0;

    dir = opendir(usbRoot);
    if (!dir)
      return;
    while (!stop && (de = readdir(dir))) {
      int bus = strtol(de->d_name, &endptr, 10);
      if (*endptr == '\0' && bus >= 0)
          stop = usbScanDevicesInBus(bus, de->d_name, vid, pid,
                               callback, data);
    }
    closedir(dir);
}


void usbScanDevices(USBFindDevicesCallback callback, void *data)
{
    usbFindDevices(0, 0, callback, data);
}


USBDevice *usbGetDevice(USBDeviceLocation l)
{
    USBDevice *dev;

    dev = (USBDevice *) calloc(1,sizeof(dev[0]));
    if (dev) {
      dev->next = firstDevice;
      firstDevice = dev;
      dev->location = l;
      dev->fd = -1;
      usbOpen(dev, 0);
    }
    return dev;
}


void usbFreeDevice(USBDevice *dev)
{
    USBDevice **p;

    if (!dev)
      return;
    for (p = &firstDevice; *p && *p != dev; p = &((*p)->next)) { }
    if (!(*p)) {
      fprintf(stderr,"BUG:usbFreeDevice():invalid device\n");
      return;
    }
    *p = dev->next;
    freeConfigBuf(dev);
    usbClose(dev);
    free(dev);
}


const USBDeviceDescriptor *usbGetDeviceDescriptor(USBDevice *dev)
{
    return (dev->deviced.bLength != 0) ? &dev->deviced : NULL;
}


const USBConfigurationDescriptor *usbGetConfigurationDescriptor(
    USBDevice *dev, unsigned idx)
{
    return (idx < dev->nconfd) ?
      (USBConfigurationDescriptor *) dev->rawconfd[idx] : NULL;
}


USBDeviceLocation usbGetDeviceLocation(USBDevice *dev)
{
    return dev->location;
}


int usbClaimInterface(USBDevice *dev, unsigned intf)
{
    int result;

    result = usbOpen(dev, 1);
    if (result == 0 && ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &intf) == -1)
      result = -errno;
    return result;
}


int usbReleaseInterface(USBDevice *dev, unsigned intf)
{
    int result = 0;

    if (dev->fd != -1 &&
      ioctl(dev->fd, USBDEVFS_RELEASEINTERFACE, &intf) == -1)
      result = -errno;
    return result;
}


int usbSubmitUrb(USBDevice *dev, USBURB *urb)
{
    struct usbdevfs_urb *lurb = NULL;
    int error = 0;

    error = usbOpen(dev, 1);
    if (error < 0)
      return error;
    if (!urb->priv || urb->status == -EINPROGRESS) {
        fprintf(stderr, "BUG:usbSubmitUrb():invalid urb %p %p %s\n",
                (void*)urb, urb->priv, strerror(-urb->status));
      return -EINVAL;
    }
    lurb = (struct usbdevfs_urb *) urb->priv;
    memset(lurb, 0, sizeof(lurb[0]));
    switch (urb->type) {
      case USBControl:
          lurb->type = USBDEVFS_URB_TYPE_CONTROL;
          break;
      case USBBulk:
          lurb->type = USBDEVFS_URB_TYPE_BULK;
          break;
      case USBInterrupt:
          lurb->type = USBDEVFS_URB_TYPE_INTERRUPT;
          break;
      case USBIsochronous:
      default:
          return -EINVAL;
    }
    lurb->endpoint = urb->ep;
    lurb->buffer = urb->buffer;
    lurb->buffer_length = urb->bufferSize;
    lurb->usercontext = urb;
    urb->status = 0;
    if (ioctl(dev->fd, USBDEVFS_SUBMITURB, lurb) < 0)
      return -errno;
    urb->status = -EINPROGRESS;
    return 0;
}


int usbGetCompleteUrb(USBDevice *dev, USBURB **completed, int timeout/*[ms]*/)
{
    int error;
    struct pollfd pf;
    struct usbdevfs_urb *lurb;
    USBURB *urb;

    *completed = NULL;
    error = usbOpen(dev, 1);
    if (error < 0)
      return error;

    do {
      pf.fd = dev->fd;
      pf.events = POLLOUT;
      pf.revents = 0;
      error = poll(&pf, 1, timeout);
      if (error == -1)
          return -errno; /* e.g. EINTR */
      if (error == 0)
          return 0; /* timed out */
      if ((pf.revents & POLLERR) != 0)
          return -ENODEV;
      if (ioctl(dev->fd, USBDEVFS_REAPURB, &lurb) == -1)
          return -errno;
      urb = (USBURB *)lurb->usercontext;
      if (urb) {
          urb->status = lurb->status;
          urb->actualLength = lurb->actual_length;
          if (urb->callback)
            urb->callback(urb);
      } else {
          /* USBURB has been already destroyed. */
          free(lurb);
      }
      *completed = urb;
    } while (*completed == NULL);
    return 1;
}


/* A version of usbGetCompleteUrb() that is immune against signals. */
int usbGetCompleteUrbNoIntr(USBDevice *dev, USBURB **completed, int timeout)
{
    struct timeval to,t;
    int sec,error;

    if (timeout < 0) {
      do {
          error = usbGetCompleteUrb(dev, completed, timeout);
      } while (error == -EINTR);
      return error;
    } else {
      gettimeofday(&to, NULL);
      sec = timeout / 1000;
      to.tv_usec += (timeout - 1000*sec) * 1000;
      to.tv_sec += sec;
      if (to.tv_usec >= 1000000) {
          to.tv_usec -= 1000000;
          to.tv_sec++;
      }
      error = usbGetCompleteUrb(dev, completed, timeout);
      while (error == -EINTR) {
          gettimeofday(&t, NULL);
          if (timercmp(&to, &t, <)) {
            error = 0;
            break;
          }
          timeout = (to.tv_sec - t.tv_sec)*1000
            + (to.tv_usec - t.tv_usec)/1000;
          error = usbGetCompleteUrb(dev, completed, timeout);
      }
      return error;
    }
}


int usbCancelUrb(USBDevice *dev, USBURB *urb)
{
    if (dev->fd < 0 || !urb || !urb->priv || urb->status != -EINPROGRESS)
      return -EINVAL;
    ioctl(dev->fd, USBDEVFS_DISCARDURB, urb->priv);
    return 0;
}


static
int usbSendBulkMsg_i(USBDevice *dev, USBEndpoint ep, void *data, unsigned len,
                 int timeout/*ms*/)
{
    int error;
    struct usbdevfs_bulktransfer bulk;

    bulk.ep = ep;
    bulk.len = len;
    if (timeout < 0)
      timeout = 0;
    else if (timeout == 0)
      timeout = 1;
    bulk.timeout = timeout;
    bulk.data = data;
    error = ioctl(dev->fd, USBDEVFS_BULK, &bulk);
    if (error == -1)
      error = -errno;
    return error;
}


int usbSendBulkMsg(USBDevice *dev, USBEndpoint ep, void *data_, unsigned len,
               int timeout/*ms*/)
{
    int error;
    uint8_t *data = (uint8_t *)data_;
    int pos, count, blocksize;

    error = usbOpen(dev, 1);
    if (error < 0)
      return error;
    pos = 0;
    do {
      blocksize = (len < MAX_URB_BUFFER_SIZE) ? len : MAX_URB_BUFFER_SIZE;
      count = usbSendBulkMsg_i(dev, ep, data+pos, blocksize, timeout);
      if (count < 0)
          return count;
      pos += count;
      len -= count;
    } while (len != 0 && count == blocksize);
    return pos;
}


int usbSendControlMsg(USBDevice *dev, USBEndpoint ep,
                  uint8_t bRequest, uint8_t bmRequestType,
                  uint16_t wValue, uint16_t wIndex, uint16_t wLength,
                  void *data, int timeout)
{
    int error;
    struct usbdevfs_ctrltransfer ctrl;

    error = usbOpen(dev, 1);
    if (error < 0)
      return error;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
    /* FIXME: In which version has the naming convension changed? */
    ctrl.request = bRequest;
    ctrl.requesttype = bmRequestType;
    ctrl.value = wValue;
    ctrl.index = wIndex;
    ctrl.length = wLength;
#else
    ctrl.bRequest = bRequest;
    ctrl.bRequestType = bmRequestType;
    ctrl.wValue = wValue;
    ctrl.wIndex = wIndex;
    ctrl.wLength = wLength;
#endif
    if (timeout < 0)
      timeout = 0;
    else if (timeout == 0)
      timeout = 1;
    ctrl.timeout = timeout;
    ctrl.data = data;
    error = ioctl(dev->fd, USBDEVFS_CONTROL, &ctrl);
    if (error == -1)
      error = -errno;
    return error;
}


USBURB *usbNewUrb(void)
{
    URBNode *urbNode;
    struct usbdevfs_urb *lurb;

    urbNode = (URBNode *) calloc(1, sizeof(*urbNode));
    if (!urbNode)
      return NULL;
    lurb = (struct usbdevfs_urb *) calloc(1, sizeof(*lurb));
    if (!lurb) {
      free(urbNode);
      return NULL;
    }
    urbNode->next = firstURB;
    firstURB = urbNode;
    urbNode->urb.priv = lurb;
    return &urbNode->urb;
}


void usbDestroyUrb(USBURB *urb)
{
    URBNode *urbNode, **p;
    struct usbdevfs_urb *lurb;

    if (!urb)
      return;
    for (p = &firstURB; *p && &((*p)->urb) != urb; p = &((*p)->next)) { }
    urbNode = *p;
    if (!urbNode) {
      fprintf(stderr,"BUG:usbDestroyUrb():invalid URB\n");
      return;
    }
    lurb = (struct usbdevfs_urb *) urb->priv;
    urb->priv = NULL;
    if (lurb)
      lurb->usercontext = NULL;
    if (urb->status == -EINPROGRESS) {
      fprintf(stderr, "BUG:usbDestroyUrb(): destroying pending urb\n");
    } else {
      free(lurb);
    }
    *p = urbNode->next;
    free(urbNode);
}


int usbGetDescriptor(USBDevice *dev, uint8_t type, uint8_t descidx,
                 uint16_t wIndex, uint16_t wLength, void *data)
{
    return usbSendControlMsg(dev, 0, 6 /*GET_DESCRIPTOR*/, 0x80,
                      (type << 8) | descidx, wIndex, wLength,
                      data, GET_DESC_TIMEOUT);
}


int usbGetFirstLanguageString(USBDevice *dev, unsigned idx,
                        void *buf, unsigned bufsize)
{
    struct PACKED {
      USBDescriptor h;
      uint16_t lang;
    } temp;
    int result;

    if (idx == 0)
      return 0;
    /* Read the first language code. Assumed that there is at least one. */
    result = usbGetDescriptor(dev, USB_DT_STRING, 0, 0, 4, &temp);
    if (result != 4)
      return result;
    /* Then read the string. */
    return usbGetDescriptor(dev, USB_DT_STRING, idx, getLe16(&temp.lang),
                      bufsize, buf);
}

int usbResetDevice(USBDevice *dev)
{
    int error;

    error = usbOpen(dev, 1);
    if (error < 0)
      return error;
    if (ioctl(dev->fd, USBDEVFS_RESET, NULL) == -1)
      return -errno;
    return 0;
}

int usbSetInterface(USBDevice *dev, uint8_t iface, uint8_t altsetting)
{
    int error;
    struct usbdevfs_setinterface si;

    error = usbOpen(dev, 1);
    if (error < 0)
        return error;
    si.interface = iface;
    si.altsetting = altsetting;
    if (ioctl(dev->fd, USBDEVFS_SETINTERFACE, &si) == -1)
        return -errno;
    return 0;
}


Generated by  Doxygen 1.6.0   Back to index