/*
 * Linux USB support
 *
 * Copyright (c) 2000-2003 Johannes Erdfelt <johannes@erdfelt.com>
 *
 * This library is covered by the LGPL, read LICENSE for details.
 */

#include <stdlib.h>	/* getenv, etc */
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <dirent.h>

#include "linux.h"
#include "usbi.h"

static char usb_path[PATH_MAX + 1] = "";

static int device_open(struct usb_device *dev)
{
  char filename[PATH_MAX + 1];
  int fd;

  snprintf(filename, sizeof(filename) - 1, "%s/%s/%s",
    usb_path, dev->bus->dirname, dev->filename);

  fd = open(filename, O_RDWR);
  if (fd < 0) {
    fd = open(filename, O_RDONLY);
    if (fd < 0)
      USB_ERROR_STR(-errno, "failed to open %s: %s",
	filename, strerror(errno));
  }

  return fd;
}

int usb_os_open(usb_dev_handle *dev)
{
  dev->fd = device_open(dev->device);

  return 0;
}

int usb_os_close(usb_dev_handle *dev)
{
  if (dev->fd < 0)
    return 0;

  if (close(dev->fd) == -1)
    /* Failing trying to close a file really isn't an error, so return 0 */
    USB_ERROR_STR(0, "tried to close device fd %d: %s", dev->fd,
	strerror(errno));

  return 0;
}

int usb_set_configuration(usb_dev_handle *dev, int configuration)
{
  int ret;

  ret = ioctl(dev->fd, IOCTL_USB_SETCONFIG, &configuration);
  if (ret < 0)
    USB_ERROR_STR(-errno, "could not set config %d: %s", configuration,
	strerror(errno));

  dev->config = configuration;

  return 0;
}

int usb_claim_interface(usb_dev_handle *dev, int interface)
{
  int ret;

  ret = ioctl(dev->fd, IOCTL_USB_CLAIMINTF, &interface);
  if (ret < 0) {
    if (errno == EBUSY && usb_debug > 0)
      fprintf(stderr, "Check that you have permissions to write to %s/%s and, if you don't, that you set up hotplug (http://linux-hotplug.sourceforge.net/) correctly.\n", dev->bus->dirname, dev->device->filename);

    USB_ERROR_STR(-errno, "could not claim interface %d: %s", interface,
	strerror(errno));
  }

  dev->interface = interface;

  return 0;
}

int usb_release_interface(usb_dev_handle *dev, int interface)
{
  int ret;

  ret = ioctl(dev->fd, IOCTL_USB_RELEASEINTF, &interface);
  if (ret < 0)
    USB_ERROR_STR(-errno, "could not release intf %d: %s", interface,
    	strerror(errno));

  dev->interface = -1;

  return 0;
}

int usb_set_altinterface(usb_dev_handle *dev, int alternate)
{
  int ret;
  struct usb_setinterface setintf;

  if (dev->interface < 0)
    USB_ERROR(-EINVAL);

  setintf.interface = dev->interface;
  setintf.altsetting = alternate;

  ret = ioctl(dev->fd, IOCTL_USB_SETINTF, &setintf);
  if (ret < 0)
    USB_ERROR_STR(-errno, "could not set alt intf %d/%d: %s",
	dev->interface, alternate, strerror(errno));

  dev->altsetting = alternate;

  return 0;
}

/*
 * Linux usbfs has a limit of one page size for synchronous bulk read/write.
 * 4096 is the most portable maximum we can do for now.
 * Linux usbfs has a limit of 16KB for the URB interface. We use this now
 * to get better performance for USB 2.0 devices.
 */
#define MAX_READ_WRITE	(16 * 1024)

int usb_control_msg(usb_dev_handle *dev, int requesttype, int request,
	int value, int index, char *bytes, int size, int timeout)
{
  struct usb_ctrltransfer ctrl;
  int ret;

  ctrl.bRequestType = requesttype;
  ctrl.bRequest = request;
  ctrl.wValue = value;
  ctrl.wIndex = index;
  ctrl.wLength = size;

  ctrl.data = bytes;
  ctrl.timeout = timeout;

  ret = ioctl(dev->fd, IOCTL_USB_CONTROL, &ctrl);
  if (ret < 0)
    USB_ERROR_STR(-errno, "error sending control message: %s", strerror(errno));

  return ret;
}

#define URB_USERCONTEXT_COOKIE		((void *)0x1)

/* Reading and writing are the same except for the endpoint */
static int usb_urb_transfer(usb_dev_handle *dev, int ep, int urbtype,
	char *bytes, int size, int timeout)
{
  struct usb_urb urb[2];
  int urbos[2];		/* Flag, nz if urb is outstanding */
  int pp;		/* Ping pong phase */
  int bytesdone, beingdone, requested[2];
  struct timeval tv, tv_ref, tv_now;
  fd_set writefds;
  int ret, tdout, rc;

//printf("~1 usb_urb_transfer ep 0x%x %d bytes\n",ep,size);
  /*
   * HACK: The use of urb.usercontext is a hack to get threaded applications
   * sort of working again. Threaded support is still not recommended, but
   * this should allow applications to work in the common cases. Basically,
   * if we get the completion for an URB we're not waiting for, then we update
   * the usercontext pointer to 1 for the other threads URB and it will see
   * the change after it wakes up from the the timeout. Ugly, but it works.
   */

  /*
   * Get actual time, and add the timeout value. The result is the absolute
   * time where we have to quit waiting for an message.
   */
  gettimeofday(&tv_ref, NULL);
  tv_ref.tv_sec = tv_ref.tv_sec + timeout / 1000;
  tv_ref.tv_usec = tv_ref.tv_usec + (timeout % 1000) * 1000;
  if (tv_ref.tv_usec > 1000000) {
    tv_ref.tv_usec -= 1000000;
    tv_ref.tv_sec++;
  }

  /* For large transfers we use two URBs, and ping pong between them, */
  /* so that there is no delay in continuing the transfer. */
  pp = 0;
  bytesdone = beingdone = 0;
  urbos[0] = urbos[1] = 0;

  /* Start the first request */
  requested[0] = size - bytesdone;
  if (requested[0] > MAX_READ_WRITE)
    requested[0] = MAX_READ_WRITE;
//printf("~1 starting urb %d with %d bytes\n",0,requested[0]);

  urb[0].type = urbtype;
  urb[0].endpoint = ep;
  urb[0].flags = 0;
  urb[0].buffer = bytes + beingdone;
  urb[0].buffer_length = requested[0];
  urb[0].signr = 0;
  urb[0].actual_length = 0;
  urb[0].number_of_packets = 0;	/* don't do isochronous yet */
  urb[0].usercontext = NULL;
  urbos[0] = 1;
  beingdone += requested[0];

  ret = ioctl(dev->fd, IOCTL_USB_SUBMITURB, &urb[0]);
  if (ret < 0) {
    USB_ERROR_STR(-errno, "error submitting URB: %s", strerror(errno));
    return ret;
  }

  /* If there is more after that, start it as well */
  if (size > beingdone) {

    requested[1] = size - beingdone;
    if (requested[1] > MAX_READ_WRITE)
      requested[1] = MAX_READ_WRITE;
//printf("~1 starting urb %d with %d bytes\n",1,requested[1]);

    urb[1].type = urbtype;
    urb[1].endpoint = ep;
    urb[1].flags = 0;
    urb[1].buffer = bytes + beingdone;
    urb[1].buffer_length = requested[1];
    urb[1].signr = 0;
    urb[1].actual_length = 0;
    urb[1].number_of_packets = 0;	/* don't do isochronous yet */
    urb[1].usercontext = NULL;
    urbos[1] = 1;
    beingdone += requested[1];

    ret = ioctl(dev->fd, IOCTL_USB_SUBMITURB, &urb[1]);
    if (ret < 0) {
      USB_ERROR_STR(-errno, "error submitting URB: %s", strerror(errno));
      return ret;
    }
  }

  FD_ZERO(&writefds);
  FD_SET(dev->fd, &writefds);

  /* Untill we've trasferred size MAX_READ_WRITE at a time */
  for(;;) {
    struct usb_urb *context;

    /* Now wait for our urb[pp] to turn up */
    tdout = 0;
    for (;;) {

      ret = 0;
      if (urb[pp].usercontext) {
//printf("~1 other thread found urb %d\n",pp);
	break;				/* Another thread found our URB for us */
      }
      context = NULL;
      ret = ioctl(dev->fd, IOCTL_USB_REAPURBNDELAY, &context);
      if (ret == 0 && context) {	/* Found something */
        if (context == &urb[pp]) {	/* Got the URB we were waiting for */
//printf("~1 got urb %d we were waiting for\n",pp);
	  break;
	}
	if (context != &urb[pp ^1]) {	/* If not the other one of ours */
	  /* Must be some other threads. Mark it and keep waiting for ours */
	  context->usercontext = URB_USERCONTEXT_COOKIE;
	}
//else
//printf("~1 got urb %d we were wern't waiting for\n",pp ^1);
      } else {
	if (errno != EAGAIN) {
          fprintf(stderr, "error reaping URB: %s", strerror(errno));
	  break;
	}
	/* We are still waiting. See if we've timed out */
        gettimeofday(&tv_now, NULL);
        if ((tv_now.tv_sec > tv_ref.tv_sec) ||
            ((tv_now.tv_sec == tv_ref.tv_sec) && (tv_now.tv_usec >= tv_ref.tv_usec))) {
//printf("~1 timed out waiting for urb %d\n",pp);
	  if (tdout) {	/* Second time we've timed out. Discard must have failed */
//printf("~1 2nd time out\n");
	    break;
	  }
          tdout = 1;
	  /* Discard our outstanding URB's and continue waiting a while for it to turn up */	
//printf("~1 discard urb %d\n",pp);
	  ret = ioctl(dev->fd, IOCTL_USB_DISCARDURB, &urb[pp]);
          if (ret < 0 && errno != EINVAL && usb_debug >= 1) {
            fprintf(stderr, "error discarding URB: %s", strerror(errno));
          }
          if (urbos[pp ^ 1]) {	/* The pong urb is outstanding */
//printf("~1 discard urb %d\n",pp ^ 1);
	    ret = ioctl(dev->fd, IOCTL_USB_DISCARDURB, &urb[pp ^ 1]);
            if (ret < 0 && errno != EINVAL && usb_debug >= 1) {
              fprintf(stderr, "error discarding URB: %s", strerror(errno));
            }
            pp ^= 1;	/* Wait for it rather than first outstanding urb */
          }
	  /* Allow another 100msec for discard to occur. Should normally happen faster than this. */
          tv_ref.tv_sec = tv_now.tv_sec + 100 / 1000;
          tv_ref.tv_usec = tv_now.tv_usec + (100 % 1000) * 1000;
          if (tv_ref.tv_usec > 1000000) {
            tv_ref.tv_usec -= 1000000;
            tv_ref.tv_sec++;
          }
        }
      }
      /* Sleep for 2msec to wait for things to happen */
      tv.tv_sec = 0;
      tv.tv_usec = 2000; 	/* 2 msec */
      select(dev->fd + 1, NULL, &writefds, NULL, &tv); 
    }
//printf("~1 finished waiting on urb %d, ret = %d, tdout = %d\n",pp,ret,tdout);

    if (ret < 0 || tdout)	/* We didn't get a sucessful URB back */
      break;

    bytesdone += urb[pp].actual_length;
    urbos[pp] = 0;
//printf("~1 done %d bytes, urb %d, ret = %d, tdout = %d\n", urb[pp].actual_length,pp,ret,tdout);

    /* If we've got a short read, we're done */
    if (urb[pp].actual_length != requested[pp]) {

//printf("~1 Got a short read on urb %d\n",pp);
      if (urbos[pp ^ 1]) {	/* The other urb is outstanding though. */

//printf("~1 discard urb %d\n",pp ^ 1);
	/* Discard it */
	pp ^= 1;
	ret = ioctl(dev->fd, IOCTL_USB_DISCARDURB, &urb[pp]);
        if (ret < 0 && errno != EINVAL && usb_debug >= 1) {
          fprintf(stderr, "error discarding URB: %s", strerror(errno));
        }

	/* Allow another 100msec for discard to occur. Should normally happen faster than this. */
        gettimeofday(&tv_now, NULL);
        tv_ref.tv_sec = tv_now.tv_sec + 100 / 1000;
        tv_ref.tv_usec = tv_now.tv_usec + (100 % 1000) * 1000;
        if (tv_ref.tv_usec > 1000000) {
          tv_ref.tv_usec -= 1000000;
          tv_ref.tv_sec++;
        }

        /* Now wait for our urb[pp] to be discarded */
        tdout = 0;
        for (;;) {
    
          ret = 0;
          if (urb[pp].usercontext) {
    	    break;				/* Another thread found our URB for us */
          }
          context = NULL;
          ret = ioctl(dev->fd, IOCTL_USB_REAPURBNDELAY, &context);
          if (ret == 0 && context) {	/* Found something */
            if (context == &urb[pp]) {	/* Got the URB we were waiting for */
//printf("~1 discarded urb %d\n",pp);
    	      break;
    	    }
    	    /* Must be some other threads. Mark it and keep waiting for ours */
            context->usercontext = URB_USERCONTEXT_COOKIE;
          } else {
    	    if (errno != EAGAIN) {
              fprintf(stderr, "error reaping URB: %s", strerror(errno));
    	      break;
    	    }
    	    /* We are still waiting. See if we've timed out */
            gettimeofday(&tv_now, NULL);
            if ((tv_now.tv_sec > tv_ref.tv_sec) ||
                ((tv_now.tv_sec == tv_ref.tv_sec) && (tv_now.tv_usec >= tv_ref.tv_usec))) {
    	        break;		/* Hmm. discard time out - give up */
    	    }
          }
          /* Sleep for 2msec to wait for things to happen */
          tv.tv_sec = 0;
          tv.tv_usec = 2000; 	/* 2 msec */
          select(dev->fd + 1, NULL, &writefds, NULL, &tv); 
        }
      }
//printf("~1 done short read\n");
      break;		/* We're done with short read */
    }

    /* Got a full read of urb[pp] */

    /* See if there's more to do */
    if (size > beingdone) {
      requested[pp] = size - beingdone;
      if (requested[pp] > MAX_READ_WRITE)
        requested[pp] = MAX_READ_WRITE;
//printf("~1 starting urb %d with %d bytes\n",pp,requested[pp]);
  
      urb[pp].type = urbtype;
      urb[pp].endpoint = ep;
      urb[pp].flags = 0;
      urb[pp].buffer = bytes + beingdone;
      urb[pp].buffer_length = requested[pp];
      urb[pp].signr = 0;
      urb[pp].actual_length = 0;
      urb[pp].number_of_packets = 0;	/* don't do isochronous yet */
      urb[pp].usercontext = NULL;
      urbos[pp] = 1;
      beingdone += requested[pp];
  
      ret = ioctl(dev->fd, IOCTL_USB_SUBMITURB, &urb[pp]);
      if (ret < 0) {
        USB_ERROR_STR(-errno, "error submitting URB: %s", strerror(errno));
        return ret;
      }

    } else  if (bytesdone >= size) {
//printf("~1 we're done all requested bytes\n");
      break;		/* We're done */
    }

    pp ^= 1;	/* Swap to other urb and wait for it */
//printf("~1 swapping to wait on urb %d\n",pp);
  }

  if (tdout)
    rc = -ETIMEDOUT;
  else if (ret < 0)
    rc = urb[pp].status;
  else
    rc = bytesdone; 
//printf("~1 returning %d\n",rc);
  return rc;
}

int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size,
	int timeout)
{
  /* Ensure the endpoint address is correct */
  return usb_urb_transfer(dev, ep, USB_URB_TYPE_BULK, bytes, size,
		timeout);
}

int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size,
	int timeout)
{
  /* Ensure the endpoint address is correct */
  ep |= USB_ENDPOINT_IN;
  return usb_urb_transfer(dev, ep, USB_URB_TYPE_BULK, bytes, size,
		timeout);
}

/*
 * FIXME: Packetize large buffers here. 2.4 HCDs (atleast, haven't checked
 * 2.5 HCDs yet) don't handle multi-packet Interrupt transfers. So we need
 * to lookup the endpoint packet size and packetize appropriately here.
 */
int usb_interrupt_write(usb_dev_handle *dev, int ep, char *bytes, int size,
	int timeout)
{
  /* Ensure the endpoint address is correct */
  return usb_urb_transfer(dev, ep, USB_URB_TYPE_INTERRUPT, bytes, size,
		timeout);
}

int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,
	int timeout)
{
  /* Ensure the endpoint address is correct */
  ep |= USB_ENDPOINT_IN;
  return usb_urb_transfer(dev, ep, USB_URB_TYPE_INTERRUPT, bytes, size,
		timeout);
}

int usb_os_find_busses(struct usb_bus **busses)
{
  struct usb_bus *fbus = NULL;
  DIR *dir;
  struct dirent *entry;

  dir = opendir(usb_path);
  if (!dir)
    USB_ERROR_STR(-errno, "couldn't opendir(%s): %s", usb_path,
	strerror(errno));

  while ((entry = readdir(dir)) != NULL) {
    struct usb_bus *bus;

    /* Skip anything starting with a . */
    if (entry->d_name[0] == '.')
      continue;

    if (!strchr("0123456789", entry->d_name[strlen(entry->d_name) - 1])) {
      if (usb_debug >= 2)
        fprintf(stderr, "usb_os_find_busses: Skipping non bus directory %s\n",
		entry->d_name);
      continue;
    }

    bus = malloc(sizeof(*bus));
    if (!bus)
      USB_ERROR(-ENOMEM);

    memset((void *)bus, 0, sizeof(*bus));

    strncpy(bus->dirname, entry->d_name, sizeof(bus->dirname) - 1);
    bus->dirname[sizeof(bus->dirname) - 1] = 0;

    LIST_ADD(fbus, bus);

    if (usb_debug >= 2)
       fprintf(stderr, "usb_os_find_busses: Found %s\n", bus->dirname);
  }

  closedir(dir);

  *busses = fbus;

  return 0;
}

int usb_os_find_devices(struct usb_bus *bus, struct usb_device **devices)
{
  struct usb_device *fdev = NULL;
  DIR *dir;
  struct dirent *entry;
  char dirpath[PATH_MAX + 1];

  snprintf(dirpath, PATH_MAX, "%s/%s", usb_path, bus->dirname);

  dir = opendir(dirpath);
  if (!dir)
    USB_ERROR_STR(-errno, "couldn't opendir(%s): %s", dirpath,
	strerror(errno));

  while ((entry = readdir(dir)) != NULL) {
    unsigned char device_desc[DEVICE_DESC_LENGTH];
    char filename[PATH_MAX + 1];
    struct usb_device *dev;
    struct usb_connectinfo connectinfo;
    int i, fd, ret;

    /* Skip anything starting with a . */
    if (entry->d_name[0] == '.')
      continue;

    dev = malloc(sizeof(*dev));
    if (!dev)
      USB_ERROR(-ENOMEM);

    memset((void *)dev, 0, sizeof(*dev));

    dev->bus = bus;

    strncpy(dev->filename, entry->d_name, sizeof(dev->filename) - 1);
    dev->filename[sizeof(dev->filename) - 1] = 0;

    snprintf(filename, sizeof(filename) - 1, "%s/%s", dirpath, entry->d_name);
    fd = open(filename, O_RDWR);
    if (fd < 0) {
      fd = open(filename, O_RDONLY);
      if (fd < 0) {
        if (usb_debug >= 2)
          fprintf(stderr, "usb_os_find_devices: Couldn't open %s\n",
                  filename);

        free(dev);
        continue;
      }
    }

    /* Get the device number */
    ret = ioctl(fd, IOCTL_USB_CONNECTINFO, &connectinfo);
    if (ret < 0) {
      if (usb_debug)
        fprintf(stderr, "usb_os_find_devices: couldn't get connect info\n");
    } else
      dev->devnum = connectinfo.devnum;

    ret = read(fd, (void *)device_desc, DEVICE_DESC_LENGTH);
    if (ret < 0) {
      if (usb_debug)
        fprintf(stderr, "usb_os_find_devices: Couldn't read descriptor\n");

      free(dev);

      goto err;
    }

    /*
     * Linux kernel converts the words in this descriptor to CPU endian, so
     * we use the undocumented W character for usb_parse_descriptor() that
     * doesn't convert endianess when parsing the descriptor
     */
    usb_parse_descriptor(device_desc, "bbWbbbbWWWbbbb", &dev->descriptor);

    LIST_ADD(fdev, dev);

    if (usb_debug >= 2)
      fprintf(stderr, "usb_os_find_devices: Found %s on %s\n",
		dev->filename, bus->dirname);

    /* Now try to fetch the rest of the descriptors */
    if (dev->descriptor.bNumConfigurations > USB_MAXCONFIG)
      /* Silent since we'll try again later */
      goto err;

    if (dev->descriptor.bNumConfigurations < 1)
      /* Silent since we'll try again later */
      goto err;

    dev->config = (struct usb_config_descriptor *)malloc(dev->descriptor.bNumConfigurations * sizeof(struct usb_config_descriptor));
    if (!dev->config)
      /* Silent since we'll try again later */
      goto err;

    memset(dev->config, 0, dev->descriptor.bNumConfigurations *
          sizeof(struct usb_config_descriptor));

    for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
      unsigned char buffer[8], *bigbuffer;
      struct usb_config_descriptor config;

      /* Get the first 8 bytes so we can figure out what the total length is */
      ret = read(fd, (void *)buffer, 8);
      if (ret < 8) {
        if (usb_debug >= 1) {
          if (ret < 0)
            fprintf(stderr, "Unable to get descriptor (%d)\n", ret);
          else
            fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", 8, ret);
        }

        goto err;
      }

      usb_parse_descriptor(buffer, "bbw", &config);

      bigbuffer = malloc(config.wTotalLength);
      if (!bigbuffer) {
        if (usb_debug >= 1)
          fprintf(stderr, "Unable to allocate memory for descriptors\n");
        goto err;
      }

      /* Read the rest of the config descriptor */
      memcpy(bigbuffer, buffer, 8);

      ret = read(fd, (void *)(bigbuffer + 8), config.wTotalLength - 8);
      if (ret < config.wTotalLength - 8) {
        if (usb_debug >= 1) {
          if (ret < 0)
            fprintf(stderr, "Unable to get descriptor (%d)\n", ret);
          else
            fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", config.wTotalLength, ret);
        }

        free(bigbuffer);
        goto err;
      }

      ret = usb_parse_configuration(&dev->config[i], bigbuffer);
      if (usb_debug >= 2) {
        if (ret > 0)
          fprintf(stderr, "Descriptor data still left\n");
        else if (ret < 0)
          fprintf(stderr, "Unable to parse descriptors\n");
      }

      free(bigbuffer);
    }

err:
    close(fd);
  }

  closedir(dir);

  *devices = fdev;

  return 0;
}

int usb_os_determine_children(struct usb_bus *bus)
{
  struct usb_device *dev, *devices[256];
  struct usb_ioctl command;
  int ret, i, i1;

  /* Create a list of devices first */
  memset(devices, 0, sizeof(devices));
  for (dev = bus->devices; dev; dev = dev->next)
    if (dev->devnum)
      devices[dev->devnum] = dev;

  /* Now fetch the children for each device */
  for (dev = bus->devices; dev; dev = dev->next) {
    struct usb_hub_portinfo portinfo;
    int fd;

    fd = device_open(dev);
    if (fd < 0)
      continue;

    /* Query the hub driver for the children of this device */
    if (dev->config && dev->config->interface && dev->config->interface->altsetting)
      command.ifno = dev->config->interface->altsetting->bInterfaceNumber;
    else
      command.ifno = 0;
    command.ioctl_code = IOCTL_USB_HUB_PORTINFO;
    command.data = &portinfo;
    ret = ioctl(fd, IOCTL_USB_IOCTL, &command);
    if (ret < 0) {
      /* errno == ENOSYS means the device probably wasn't a hub */
      if (errno != ENOSYS && usb_debug > 1)
        fprintf(stderr, "error obtaining child information: %s\n",
		strerror(errno));

      close(fd);
      continue;
    }

    dev->num_children = 0;
    for (i = 0; i < portinfo.numports; i++)
      if (portinfo.port[i])
        dev->num_children++;

    /* Free any old children first */
    free(dev->children);

    dev->children = malloc(sizeof(struct usb_device *) * dev->num_children);
    if (!dev->children) {
      if (usb_debug > 1)
        fprintf(stderr, "error allocating %zu bytes memory for dev->children\n",
                sizeof(struct usb_device *) * dev->num_children);

      dev->num_children = 0;
      close(fd);
      continue;
    }

    for (i = 0, i1 = 0; i < portinfo.numports; i++) {
      if (!portinfo.port[i])
        continue;

      dev->children[i1++] = devices[portinfo.port[i]];

      devices[portinfo.port[i]] = NULL;
    }

    close(fd);
  }

  /*
   * There should be one device left in the devices list and that should be
   * the root device
   */
  for (i = 0; i < sizeof(devices) / sizeof(devices[0]); i++) {
    if (devices[i])
      bus->root_dev = devices[i];
  }

  return 0;
}

static int check_usb_vfs(const char *dirname)
{
  DIR *dir;
  struct dirent *entry;
  int found = 0;

  dir = opendir(dirname);
  if (!dir)
    return 0;

  while ((entry = readdir(dir)) != NULL) {
    /* Skip anything starting with a . */
    if (entry->d_name[0] == '.')
      continue;

    /* We assume if we find any files that it must be the right place */
    found = 1;
    break;
  }

  closedir(dir);

  return found;
}

void usb_os_init(void)
{
  /* Find the path to the virtual filesystem */
  if (getenv("USB_DEVFS_PATH")) {
    if (check_usb_vfs(getenv("USB_DEVFS_PATH"))) {
      strncpy(usb_path, getenv("USB_DEVFS_PATH"), sizeof(usb_path) - 1);
      usb_path[sizeof(usb_path) - 1] = 0;
    } else if (usb_debug)
      fprintf(stderr, "usb_os_init: couldn't find USB VFS in USB_DEVFS_PATH\n");
  }

  if (!usb_path[0]) {
    if (check_usb_vfs("/dev/bus/usb")) {
      strncpy(usb_path, "/dev/bus/usb", sizeof(usb_path) - 1);
      usb_path[sizeof(usb_path) - 1] = 0;
    } else if (check_usb_vfs("/proc/bus/usb")) {
      strncpy(usb_path, "/proc/bus/usb", sizeof(usb_path) - 1);
      usb_path[sizeof(usb_path) - 1] = 0;
    } else
      usb_path[0] = 0;	/* No path, no USB support */
  }

  if (usb_debug) {
    if (usb_path[0])
      fprintf(stderr, "usb_os_init: Found USB VFS at %s\n", usb_path);
    else
      fprintf(stderr, "usb_os_init: No USB VFS found, is it mounted?\n");
  }
}

int usb_resetep(usb_dev_handle *dev, unsigned int ep)
{
  int ret;

  ret = ioctl(dev->fd, IOCTL_USB_RESETEP, &ep);
  if (ret)
    USB_ERROR_STR(-errno, "could not reset ep %d: %s", ep,
    	strerror(errno));

  return 0;
}

int usb_clear_halt(usb_dev_handle *dev, unsigned int ep)
{
  int ret;

  ret = ioctl(dev->fd, IOCTL_USB_CLEAR_HALT, &ep);
  if (ret)
    USB_ERROR_STR(-errno, "could not clear/halt ep %d: %s", ep,
    	strerror(errno));

  return 0;
}

int usb_reset(usb_dev_handle *dev)
{
  int ret;

  ret = ioctl(dev->fd, IOCTL_USB_RESET, NULL);
  if (ret)
     USB_ERROR_STR(-errno, "could not reset: %s", strerror(errno));

  return 0;
}

int usb_get_driver_np(usb_dev_handle *dev, int interface, char *name,
	unsigned int namelen)
{
  struct usb_getdriver getdrv;
  int ret;

  getdrv.interface = interface;
  ret = ioctl(dev->fd, IOCTL_USB_GETDRIVER, &getdrv);
  if (ret)
    USB_ERROR_STR(-errno, "could not get bound driver: %s", strerror(errno));

  strncpy(name, getdrv.driver, namelen - 1);
  name[namelen - 1] = 0;

  return 0;
}

int usb_detach_kernel_driver_np(usb_dev_handle *dev, int interface)
{
  struct usb_ioctl command;
  int ret;

  command.ifno = interface;
  command.ioctl_code = IOCTL_USB_DISCONNECT;
  command.data = NULL;

  ret = ioctl(dev->fd, IOCTL_USB_IOCTL, &command);
  if (ret)
    USB_ERROR_STR(-errno, "could not detach kernel driver from interface %d: %s",
        interface, strerror(errno));

  return 0;
}

