Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

usb_dev.c

/* $Id: usb_dev.c,v 1.18 2009-01-27 16:17:19 potyra Exp $ 
 *
 * Copyright (C) 2006-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>

/* libusb: we need the pre-1.0 interface */
#include <usb.h>

#include <errno.h>

#include "config.h"

#include "usb_dev.h"
#include "sig_usb.h"
#include "bridge.h"
#include "glue-log.h"

#include "dynbuf.h"

/* ********************* DEFINITIONS ************************** */
/* maximum number of supported pipes for the bridged USB device */
#define UB_MAX_PIPES          32
/* maximum number of supported interfaces for the bridged USB device */
#define UB_MAX_INTERFACES     16
/* maximum packet payload size; 1023 (sic) is the upper limit for ISO FIFOs */
#define UB_MAX_DATA           USB_RAW_DATA_LENGTH
/* libusb timeout */
#define UB_LIBUSB_TIMEOUT     50

#if UB_DEBUGMASK & UB_DEBUG_STATE_TRANSITIONS
static unsigned char usb_state_names[][20] = {
      "invalid",
      "OFF",
      "ATTACHED",
      "POWERED",
      "DEFAULT",
      "ADDRESS",
      "CONFIGURED" };
#endif

/* *********************** MACROS ***************************** */
#if UB_DEBUGMASK & UB_DEBUG_STATE_TRANSITIONS
#define SET_USB_DEVSTATE(newstate) \
      if (state.usb_state != (newstate)) { \
            int oldstate = state.usb_state; \
            state.usb_state = (newstate); \
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME, \
            "state transition: %s => %s\n", \
            usb_state_names[oldstate], \
            usb_state_names[(newstate)]); \
      }
#else
#define SET_USB_DEVSTATE(newstate) (state.usb_state = (newstate))
#endif

#define MIN(a,b) ((a) < (b) ? (a) : (b))

/* ****************** GLOBAL VARIABLES ************************ */

/* config */
static struct usb_bridge_config_s *config = NULL;

/* for usb bridge */
static struct dynamic_connection_info *dci = NULL;

/* program name (just for debugging) */
static const char *prog_name = NULL;

struct standard_device_request {
      unsigned char bmRequestType;
      unsigned char bRequest;
      unsigned short wValue;
      unsigned short wIndex;
      unsigned short wLength;
};

static struct state {
      /* cf. USB Spec. 1.1, chapter 9.1 */
      enum usb_state {
            STATE_OFF = 1,
            STATE_ATTACHED,
            STATE_POWERED,
            STATE_DEFAULT,
            STATE_ADDRESS,
            STATE_CONFIGURED } usb_state;

      /* device address */
      unsigned char addr;

      /* stage of current control transfer */
      struct control_transfer_stage_information {
            enum { NO_STAGE, SETUP_STAGE, DATA_STAGE, STATUS_STAGE } stage;

            struct standard_device_request request;

            char *data;
            unsigned long data_length, data_sent;

            char needs_passthrough;
      } control_transfer_stage_information;

      /* pipe state */
      struct pipe_information {
            /* DATA0/DATA1 protocol toggle */
            unsigned char data_toggle;
            unsigned char stalled;
            unsigned char last_data_acked;
            struct dynbuf fifo;
            unsigned int fifo_datalen;
      } pipe_in[UB_MAX_PIPES], pipe_out[UB_MAX_PIPES];

      struct pipe_information *control_pipe;

      /* information about last received token */
      struct last_token {
            unsigned char pid;
            unsigned char addr, endp;
      } last_token;

      struct sig_usb_state sig_usb_state;
} state;

static struct real_device {
      /* device information */
      struct usb_device *device;

      /* current device configuration */
      int configuration;

      /* main handle we're using for control transfers */
      struct usb_dev_handle *handle;

      /*
       * We need one device handle for each interface.
       * Array index is interface# as specified in the interface descriptors,
       * not the nth device (which may be the same, though).
       */
      struct {
            struct usb_dev_handle *handle;
            int alternatesetting;
            struct usb_interface *interface;
      } interface[UB_MAX_INTERFACES];

      /* endpoint -> interface mapping */
      struct {
            int interface;
            struct usb_endpoint_descriptor *descriptor;
      } pipe_in[UB_MAX_PIPES], pipe_out[UB_MAX_PIPES];

      /* detach kernel drivers if necessary? */
      char force_claim;
} real_device;

struct dynbuf buffer;

/*
FIXME: implement this in a generic way for bridge
#if UB_DEBUGMASK & UB_DEBUG_STATISTICS
struct statistics {
      unsigned long token_count_total,
                  token_count,
                  token_count_ctrl,
                  token_count_bulk_in,
                  token_count_bulk_out;
} statistics = { 0, 0, 0, 0, 0 };
#endif
*/

/* ******************** IMPLEMENTATION ************************* */

/* forward declarations */
static int
usb_initialize_bridge(void);
static int
usb_realdev_attach(struct usb_device_selection *devselect);
static int
usb_realdev_detach(void);
static int
usb_realdev_claim_interfaces(void);
static int
usb_realdev_release_interfaces(void);
static void
usb_handle_packet(struct usb_msg *msg);
static void
usb_handle_packet_linestate(struct usb_msg *msg);
static void
usb_handle_packet_usb_token(struct usb_msg *msg);
static void
usb_handle_packet_usb_token_in_control(struct usb_msg *msg);
static void
usb_handle_packet_usb_token_in_other(struct usb_msg *msg);
static void
usb_handle_packet_usb_data(struct usb_msg *msg);
static void
usb_handle_packet_usb_data_control(struct usb_msg *msg);
static void
usb_handle_packet_usb_data_other(struct usb_msg *msg);
static void
usb_handle_packet_usb_handshake(struct usb_msg *msg);
static void
usb_handle_packet_usb_handshake_control(struct usb_msg *msg);
static void
usb_handle_packet_usb_handshake_other(struct usb_msg *msg);
static unsigned char
usb_toggle_data(unsigned char toggle);
static void
usb_reset_state(void);
static void
__attribute__ ((unused))
usb_send_handshake(unsigned char pid);
static void
__attribute__ ((unused))
usb_send_data(unsigned char pid, unsigned char *data, unsigned int length);
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
static void
usb_debug_data(unsigned char *data, unsigned int length);
#endif

int
usb_create(const char *progname,
      struct usb_bridge_config_s *cfg,
      struct usb_device_selection *devselect)
{
      int i;

      /* initialize locals */
      prog_name = progname;
      config = cfg;

      /*
       * create dynamic buffers for temporary data storage and endpoint FIFOs
       */
      if (dynbuf_create(&buffer, 64, 1024*1024, 64, 1)) {
            return -1;
      }
      for (i = 0; i < UB_MAX_PIPES; i++) {
            if (dynbuf_create(&state.pipe_in[i].fifo, 0, UB_MAX_DATA, 8, 0)) {
                  return -1;
            }
            if (dynbuf_create(&state.pipe_out[i].fifo, 0, UB_MAX_DATA, 8, 0)) {
                  return -1;
            }
      }
      /* special case: control pipe 0 is both IN and OUT */
      state.control_pipe = &state.pipe_in[0];

      if (usb_realdev_attach(devselect)) {
            return -1;
      }

      state.usb_state = 0;
      usb_reset_state();
      if (usb_initialize_bridge()) {
            return -1;
      }

      if (cim_get_peer_count(&cfg->bcc_usb) > 0) {
#if UB_DEBUGMASK & UB_DEBUG_CIM
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "telling USB we are bus powered and a fullspeed device\n");
#endif
            /*
             * FIXME:
             * Unfortunately, libusb 0.1.* does not have an interface to
             * figure out the real device speed, so we're assuming full
             * speed (12Mb/s) for now.
             */
            SET_USB_DEVSTATE(STATE_ATTACHED);
            sig_usb_send_linestate(&config->bcc_usb, dci,
                  USB_MSG_LINESTATE_TYPE_BUS_POWERED,
                  USB_SPEED_FULL, 0);
      }

      return 0;
}

/* setup bridge */
static int
usb_initialize_bridge(void) {
      assert(config != NULL);

      dci = malloc(sizeof(struct dynamic_connection_info));
      if (!dci) {
            fprintf(stderr, "%s: not enough memory (trying to allocate %u bytes)\n",
                  prog_name, sizeof(struct dynamic_connection_info));
            return -1;
      }

      /* tell the other side of the CIM cable who we are */
      strncpy(config->bcc_usb.callername, prog_name,
            sizeof(config->bcc_usb.callername));
      config->bcc_usb.callername[sizeof(config->bcc_usb.callername) - 1] = 0;

      cim_create(&config->bcc_usb, dci, 1);

      sig_usb_init(&state.sig_usb_state);

      return 0;
}

/* find and connect real USB device via libusb */
static int
usb_realdev_attach(struct usb_device_selection *devselect)
{
      struct usb_bus *bus = NULL, *found_bus = NULL;
      struct usb_device *dev = NULL, *found_dev = NULL;
      char found = 0;
      int i;

      /*
       * Note: These function calls go to libusb. Fortunately, the function
       * prefix changes to libusb_ in libusb 1.0.
       */
      usb_init();
      usb_find_busses();
      usb_find_devices();

      faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
            "searching for USB device with ");
      if (devselect->addr_selected) {
            faum_cont(FAUM_LOG_INFO, "address %03d:%03d",
                  devselect->addr_bus,
                  devselect->addr_dev);
      }
      if (devselect->addr_selected
       && devselect->id_selected) {
            faum_cont(FAUM_LOG_INFO, " and ");
      }
      if (devselect->id_selected) {
            faum_cont(FAUM_LOG_INFO, "id %04x:%04x",
                  devselect->id_vendor,
                  devselect->id_product);
      }
      faum_cont(FAUM_LOG_INFO, "\n");

      for (bus = usb_get_busses(); bus; bus = bus->next) {
            for (dev = bus->devices; dev; dev = dev->next) {
                  char bus_str[10];

                  if (devselect->addr_selected) {
                        snprintf(bus_str, sizeof(bus_str), "%03d",
                              devselect->addr_bus);
                  }

                  /* correct address? */
                  if (devselect->addr_selected
                   && (strcmp(bus_str, bus->dirname)
                    || dev->devnum != devselect->addr_dev)) {
                        continue;
                  }

                  /* correct vendor/product id? */
                  if (devselect->id_selected
                   && (dev->descriptor.idVendor != devselect->id_vendor
                    || dev->descriptor.idProduct != devselect->id_product)) {
                        continue;
                  }

                  if (found) {
                        faum_log(FAUM_LOG_WARNING, UB_LOG_TYPE, UB_LOG_NAME,
                              "ambiguous device selection, using first matching device\n");
                        continue;
                  }

                  found = 1;
                  found_dev = dev;
                  found_bus = bus;
            }
      }

      dev = found_dev;
      bus = found_bus;

      real_device.device = dev;

      if (!found) {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "no matching device found.\n");
            return -1;
      }

      faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
            "device found (address %s:%03d, id %04x:%04x)\n",
            bus->dirname, dev->devnum,
            dev->descriptor.idVendor, dev->descriptor.idProduct);

      for (i = 0; i < UB_MAX_INTERFACES; i++) {
            real_device.interface[i].handle = NULL;
            real_device.interface[i].interface = NULL;
      }
      for (i = 0; i < UB_MAX_PIPES; i++) {
            real_device.pipe_in[i].interface = -1;
            real_device.pipe_out[i].interface = -1;
            real_device.pipe_in[i].descriptor = NULL;
            real_device.pipe_out[i].descriptor = NULL;
      }
      real_device.force_claim = devselect->force;

      faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
            "opening device\n");
      real_device.handle = usb_open(dev);
      if (!real_device.handle) {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "usb_open failed: %s\n", usb_strerror());
            return -1;
      }

      /* SetConfiguration 0 */
      /*
       * FIXME: we must not really go to configuration 0, the kernel won't
       * allow us to do any control transfers then.
       */
      if (usb_set_configuration(real_device.handle, 1) == 0) {
            real_device.configuration = 0;
            faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                  "device deconfigured\n");
            return 0;
      } else if (!real_device.force_claim) {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "could not deconfigure device (%s)\n", usb_strerror());
            faum_cont(FAUM_LOG_ERROR,
                  "* make sure the device is writable\n"
                  "* try running the USB bridge with -f\n");
            return -1;
      }

      /*
       * FIXME: read current configuration, don't assume it's 1!
       * Unfortunately libusb 0.1.x does not provide an interface for this.
       */
      real_device.configuration = 1;

      if (usb_realdev_claim_interfaces()) {
            return -1;
      }
      if (usb_realdev_release_interfaces()) {
            return -1;
      }

      /* SetConfiguration 0 */
      /*
       * FIXME: we must not really go to configuration 0, the kernel won't
       * allow us to do any control transfers then.
       */
      if (usb_set_configuration(real_device.handle, 1)) {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "could not deconfigure device (%s)\n", usb_strerror());
            faum_cont(FAUM_LOG_ERROR,
                  "* make sure the device is writable\n");
            return -1;
      }

      real_device.configuration = 0;
      faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
            "device deconfigured\n");

      /* actual interface claiming happens after SetConfiguration from guest */

      return 0;
}

/* detach from real USB device via libusb */
static int
usb_realdev_detach(void)
{
      usb_close(real_device.handle);
      real_device.handle = NULL;
      /* TODO */
      return -1;
}

/*
 * Claim all interfaces in the current configuration, create a USB device
 * handle for each (libusb docs say to do so) and memorize the endpoint ->
 * interface mapping.
 * Needed before any communication with endpoints other than 0 can happen.
 */
static int
usb_realdev_claim_interfaces(void)
{
      int i;
      struct usb_device *dev;
      struct usb_config_descriptor *config_desc = NULL;

      assert(real_device.handle);

      faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
            "trying to claim interfaces (configuration %d)\n",
            real_device.configuration);
      dev = real_device.device;

      /* find current configuration */
      for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
            if (dev->config[i].bConfigurationValue != real_device.configuration) {
                  continue;
            }
            config_desc = &dev->config[i];
      }

      if (config_desc == NULL) {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "could not find configuration descriptor\n");
            return -1;
      }

      /*
       * Iterate over all interfaces of this configuration and claim them.
       */
      for (i = 0; i < config_desc->bNumInterfaces; i++) {
            struct usb_interface *interface = &config_desc->interface[i];
            int interface_nr = interface->altsetting[0].bInterfaceNumber;
            int alternatesetting;
            struct usb_dev_handle *handle;
            int ret, ep;

            faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                  "trying to claim interface #%d\n",
                  interface_nr);
            
            /* open device */
            handle = usb_open(dev);
            if (!handle) {
                  faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                        "usb_open failed: %s\n", usb_strerror());
                  return -1;
            }

            /* claim this interface */
            ret = usb_claim_interface(handle, interface_nr);
            
            if (ret == -EBUSY && real_device.force_claim) {
#if LIBUSB_HAS_GET_DRIVER_NP
                  char drivername[256];
                  if (usb_get_driver_np(handle, interface_nr, drivername, sizeof(drivername))) {
                        faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                              "usb_get_driver_np failed: %s\n", usb_strerror());
                        return -1;
                  }
                  faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                        "kernel driver '%s' is bound to this interface\n",
                        drivername);
#else
                  faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                        "kernel driver is bound to this interface\n");
#endif /* LIBUSB_HAS_GET_DRIVER_NP */

#if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
                  if (usb_detach_kernel_driver_np(handle, interface_nr)) {
                        faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                              "usb_detach_kernel_driver_np failed: %s\n", usb_strerror());
                        return -1;
                  }
                  faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                        "kernel driver successfully detached\n");

                  /* retry claiming this interface */
                  ret = usb_claim_interface(handle, interface_nr);
#endif /* LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP */
            }
            
            if (ret < 0) {
                  faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                        "usb_claim_interface failed: %s\n", usb_strerror());
                  faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                        "* try running the USB bridge with -f\n");
                  return -1;
            }

            alternatesetting = 0;

            real_device.interface[interface_nr].handle = handle;
            real_device.interface[interface_nr].alternatesetting = alternatesetting;
            real_device.interface[interface_nr].interface = interface;
                  
            /* iterate over all endpoints, create mapping */
            for (ep = 0; ep < interface->altsetting[alternatesetting].bNumEndpoints; ep++) {
                  int endpoint_addr, endpoint_nr;
                  struct usb_endpoint_descriptor *endpoint_desc;
                  
                  endpoint_desc = &interface->altsetting[alternatesetting].endpoint[ep];
                  endpoint_addr = endpoint_desc->bEndpointAddress;
                  endpoint_nr = endpoint_addr & USB_ENDPOINT_ADDRESS_MASK;

                  if ((endpoint_addr & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_IN) {
                        faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                              "mapping endpoint %d IN to interface %d\n",
                              endpoint_nr, interface_nr);
                        if (0 <= real_device.pipe_in[endpoint_nr].interface) {
                              faum_log(FAUM_LOG_WARNING, UB_LOG_TYPE, UB_LOG_NAME,
                                    "there is already a interface mapping for endpoint %d IN\n",
                                    endpoint_nr);
                        }
                        real_device.pipe_in[endpoint_nr].interface = interface_nr;
                        real_device.pipe_in[endpoint_nr].descriptor = endpoint_desc;
                  } else {
                        faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                              "mapping endpoint %d OUT to interface %d\n",
                              endpoint_nr, interface_nr);
                        if (0 <= real_device.pipe_out[endpoint_nr].interface) {
                              faum_log(FAUM_LOG_WARNING, UB_LOG_TYPE, UB_LOG_NAME,
                                    "there is already a interface mapping for endpoint %d OUT\n",
                                    endpoint_nr);
                        }
                        real_device.pipe_out[endpoint_nr].interface = interface_nr;
                        real_device.pipe_out[endpoint_nr].descriptor = endpoint_desc;
                  }
            }
      }

      return 0;
}

/*
 * Release all interfaces and close the corresponding USB device handles.
 * Needed before SetConfiguration and on bridge shutdown.
 */
static int
usb_realdev_release_interfaces(void)
{
      int i;

      for (i = 0; i < UB_MAX_INTERFACES; i++) {
            if (real_device.interface[i].handle == NULL) {
                  continue;
            }

            faum_log(FAUM_LOG_INFO, UB_LOG_TYPE, UB_LOG_NAME,
                  "releasing interface %d\n", i);

            if (usb_release_interface(real_device.interface[i].handle, i) < 0) {
                  faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                        "could not release interface %d (%s)\n",
                        i, usb_strerror());
                  return -1;
            }
            if (usb_close(real_device.interface[i].handle) < 0) {
                  faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                        "could not close interface device %d (%s)\n",
                        i, usb_strerror());
                  return -1;
            }

            real_device.interface[i].handle = NULL;
            real_device.interface[i].interface = NULL;
      }

      for (i = 0; i < UB_MAX_PIPES; i++) {
            real_device.pipe_in[i].interface = -1;
            real_device.pipe_out[i].interface = -1;
            real_device.pipe_in[i].descriptor = NULL;
            real_device.pipe_out[i].descriptor = NULL;
      }

      return 0;
}

/*
 * Send a control transfer to the device handle (if the device is addressed) or
 * the interface handle that is responsible for the addressed interface or
 * endpoint.
 */
static int
usb_realdev_control_msg(int bmRequestType, int bRequest, int wValue,
      int wIndex, char *data, int size, int timeout)
{
      struct usb_dev_handle *handle = NULL;

      switch (bmRequestType & USB_DEV_REQ_RECIPIENT) {
      case USB_DEV_REQ_RECIPIENT_DEVICE:
      case USB_DEV_REQ_RECIPIENT_OTHER:
            handle = real_device.handle;
            break;

      case USB_DEV_REQ_RECIPIENT_INTERFACE:
            handle = real_device.interface[wIndex].handle;
            break;

      case USB_DEV_REQ_RECIPIENT_ENDPOINT:
            {
            int interface_nr;

            if ((wIndex & USB_ENDPOINT_DIR_MASK)
             == USB_ENDPOINT_IN) {
                  interface_nr = real_device.pipe_in[wIndex & USB_ENDPOINT_ADDRESS_MASK].interface;
            } else {
                  interface_nr = real_device.pipe_out[wIndex & USB_ENDPOINT_ADDRESS_MASK].interface;
            }

            handle = real_device.interface[interface_nr].handle;
            }
            break;
      default:
            /*
             * This must not happen; there is no other recipient type
             * defined in the specs.
             */
            assert(0);
      }

      return usb_control_msg(handle,
            bmRequestType,
            bRequest,
            wValue,
            wIndex,
            data,
            size,
            timeout);
}

void
usb_destroy() {
      int i;

      if (config == NULL || dci == NULL) {
            return;
      }

      /* TODO: tell USB we're gone? */

      cim_destroy(&config->bcc_usb, dci, 1);
      free(dci);

      usb_realdev_release_interfaces();
      usb_realdev_detach();

      for (i = 0; i < UB_MAX_PIPES; i++) {
            dynbuf_destroy(&state.pipe_in[i].fifo);
            dynbuf_destroy(&state.pipe_out[i].fifo);
      }
      dynbuf_destroy(&buffer);

/*
FIXME: implement this in a generic way for bridge
#if UB_DEBUGMASK & UB_DEBUG_STATISTICS
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "token count on USB = %lu\n"
            "token count for me = %lu\n"
            "token count CTL0 = %lu\n"
            "token count BULK_IN = %lu\n"
            "token count BULK_OUT = %lu\n",
            statistics.token_count_total,
            statistics.token_count,
            statistics.token_count_ctrl,
            statistics.token_count_bulk_in,
            statistics.token_count_bulk_out);
#endif
*/
}

void
usb_handle_event_prepare(fd_set *rfds)
{
      cim_prepare_select(&config->bcc_usb, dci, rfds);
}

/*****************************************************/

void
usb_handle_event(fd_set *rfds)
{
      struct usb_msg msg;
      int len;

      assert(config != NULL);
      assert(dci != NULL);

      /* receive from node-pc's USB controller */
      len = sig_usb_receive_packet(
            &state.sig_usb_state, &config->bcc_usb, dci, &msg);

      while (len) {
            usb_handle_packet(&msg);

            /* receive even more from node-pc's USB controller */
            len = sig_usb_receive_packet(
                  &state.sig_usb_state, &config->bcc_usb, dci, &msg);
      }
}

static void
usb_handle_packet(struct usb_msg *msg) {
      if (msg->msg_type == USB_MSG_TYPE_LINESTATE) {
            usb_handle_packet_linestate(msg);
            return;
      }

      if (state.usb_state < STATE_DEFAULT) {
            /* unable to handle USB packets in these states */
            return;
      }

      switch (msg->msg_type) {
      case USB_MSG_TYPE_USB_TOKEN:
            usb_handle_packet_usb_token(msg);
            break;
      case USB_MSG_TYPE_USB_SOF:
            /* not interested */
            break;
      case USB_MSG_TYPE_USB_DATA:
            usb_handle_packet_usb_data(msg);
            break;
      case USB_MSG_TYPE_USB_HANDSHAKE:
            usb_handle_packet_usb_handshake(msg);
            break;
      case USB_MSG_TYPE_USB_SPECIAL:
            /* not interested */
            break;
      default:
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE,
                  UB_LOG_NAME,
                  "%s: invalid msg_type %d; discarding\n",
                  __FUNCTION__, msg->msg_type);
      }
}

static void
usb_handle_packet_linestate(struct usb_msg *msg) {
      assert(config != NULL);
      assert(dci != NULL);

#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "received linestate packet type %d\n",
            msg->content.linestate.type);
#endif

      if (msg->content.linestate.type == USB_MSG_LINESTATE_TYPE_POWER) {
            /* move to 'powered' state */
            usb_reset_state();
            SET_USB_DEVSTATE(STATE_POWERED);

            /* only answer if the packet was not already a reply */
            if (!msg->content.linestate.solicited) {
#if UB_DEBUGMASK & UB_DEBUG_CIM
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "answering we are bus powered and a fullspeed device\n");
#endif
                  /*
                   * FIXME:
                   * Unfortunately, libusb 0.1.* does not have an interface to
                   * figure out the real device speed, so we're assuming full
                   * speed (12Mb/s) for now.
                   */
                  /* send answer: we are bus powered now */
                  sig_usb_send_linestate(&config->bcc_usb, dci,
                        USB_MSG_LINESTATE_TYPE_BUS_POWERED,
                        USB_SPEED_FULL, 1);
            } else {
#if UB_DEBUGMASK & UB_DEBUG_CIM
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "we are bus powered now\n");
#endif
            }
      } else if (msg->content.linestate.type == USB_MSG_LINESTATE_TYPE_NOPOWER) {
#if UB_DEBUGMASK & UB_DEBUG_CIM
            if (state.usb_state != STATE_OFF
             && state.usb_state != STATE_ATTACHED) {
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "no power on USB anymore, powering down\n");
            }
#endif
            /* FIXME: put real_device in some default state, too */
            SET_USB_DEVSTATE(STATE_ATTACHED);
      } else if (msg->content.linestate.type == USB_MSG_LINESTATE_TYPE_RESET) { /* RESET */
            /* FIXME: reset real_device */
            state.addr = 0;
            SET_USB_DEVSTATE(STATE_DEFAULT);
      } else {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "unknown linestate type %d\n",
                  msg->content.linestate.type);
      }
}

static void
usb_handle_packet_usb_token(struct usb_msg *msg) {
      unsigned char pid = msg->content.usb_token.pid;

      /* unknown PID? */
      if (pid != USB_PID_SETUP
       && pid != USB_PID_IN
       && pid != USB_PID_OUT) {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "unknown PID %02X\n",
                  msg->content.usb_token.pid);
            return;
      }

      /* save for later use */
      state.last_token.pid = msg->content.usb_token.pid;
      state.last_token.addr = msg->content.usb_token.addr;
      state.last_token.endp = msg->content.usb_token.endp;

#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "received %s token to %02d:%02d%s\n",
            sig_usb_debug_pid2str(msg->content.usb_token.pid),
            msg->content.usb_token.addr,
            msg->content.usb_token.endp,
            state.addr == msg->content.usb_token.addr ?
            " (this is me!)" : "");
#endif

      /*
       * TODO
#if UB_DEBUGMASK & UB_DEBUG_STATISTICS
      statistics.token_count_total++;
#endif
      */

      /* ignore tokens to other addresses */
      if (state.addr != msg->content.usb_token.addr) {
            return;
      }
      
      /*
       * TODO
#if UB_DEBUGMASK & UB_DEBUG_STATISTICS
      statistics.token_count++;

      switch (msg->content.usb_token.endp) {
      case 0: statistics.token_count_ctrl++; break;
      case USB_ENDPT_NR_BULK_OUT: statistics.token_count_bulk_out++; break;
      case USB_ENDPT_NR_BULK_IN: statistics.token_count_bulk_in++; break;
      }
#endif
      */
            
      if (pid == USB_PID_IN) {
            /*
             * FIXME: There _may_ be real devices with more than one
             * control endpoint!
             */
            if (msg->content.usb_token.endp == 0) {
                  usb_handle_packet_usb_token_in_control(msg);
                  return;
            }

            if (state.usb_state != STATE_CONFIGURED) {
                  /* we must not accept packets to other endpoints
                   * but 0 if not in CONFIGURED state */
                  return;
            }
            
            /* token for any other endpoint */
            usb_handle_packet_usb_token_in_other(msg);
      } else if (pid == USB_PID_SETUP
            && msg->content.usb_token.endp == 0) {
            /* initialize stage information */
            state.control_transfer_stage_information.stage = SETUP_STAGE;
            state.control_pipe->data_toggle = USB_PID_DATA0;
      } else if (pid == USB_PID_OUT
            && msg->content.usb_token.endp == 0
            && state.control_transfer_stage_information.stage == DATA_STAGE
            && state.control_transfer_stage_information.request.bmRequestType
             & USB_DEV_REQ_DEVICE_TO_HOST) {
            /* advance stage */
            state.control_transfer_stage_information.stage = STATUS_STAGE;
            state.control_pipe->data_toggle = USB_PID_DATA1;
      }
}

/* handle IN token on control endpoint (0) */
static void
usb_handle_packet_usb_token_in_control(struct usb_msg *msg) {
      if (state.control_transfer_stage_information.stage == DATA_STAGE
       && state.control_transfer_stage_information.request.bmRequestType
        & USB_DEV_REQ_DEVICE_TO_HOST) {
            /* data stage of a device-to-host transfer */
            usb_send_data(state.control_pipe->data_toggle,
                    state.control_transfer_stage_information.data
                  +  state.control_transfer_stage_information.data_sent,
                  MIN(real_device.device->descriptor.bMaxPacketSize0,
                        state.control_transfer_stage_information.data_length
                      - state.control_transfer_stage_information.data_sent));
      } else if (state.control_transfer_stage_information.stage == STATUS_STAGE
            || (state.control_transfer_stage_information.stage == DATA_STAGE
             && !(state.control_transfer_stage_information.request.bmRequestType
                & USB_DEV_REQ_DEVICE_TO_HOST))) {
            int ret;
            /* status stage or end of data stage of host-to-device transfer */

            if (state.control_transfer_stage_information.needs_passthrough) {
                  if (state.control_transfer_stage_information.data_length
                   != state.control_transfer_stage_information.request.wLength) {
                        faum_log(FAUM_LOG_WARNING, UB_LOG_TYPE, UB_LOG_NAME,
                              "OUT control transfer request.wLength = %d, data_length = %d\n",
                              state.control_transfer_stage_information.request.wLength,
                              (int)state.control_transfer_stage_information.data_length);
                  }

                  ret = usb_realdev_control_msg(
                        state.control_transfer_stage_information.request.bmRequestType,
                        state.control_transfer_stage_information.request.bRequest,
                        state.control_transfer_stage_information.request.wValue,
                        state.control_transfer_stage_information.request.wIndex,
                        state.control_transfer_stage_information.data,
                        state.control_transfer_stage_information.data_length,
                        UB_LIBUSB_TIMEOUT);

                  state.control_transfer_stage_information.stage = STATUS_STAGE;

                  if (ret < 0) {
                        faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                              "usb_control_msg failed: %s\n",
                              usb_strerror());
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }

                  /* send zero byte status stage packet */
                  usb_send_data(USB_PID_DATA1, NULL, 0);
            } else {
                  state.control_transfer_stage_information.stage = STATUS_STAGE;

                  /* send zero byte status stage packet */
                  usb_send_data(USB_PID_DATA1, NULL, 0);
            }
      }
}

/* handle IN token on any other endpoint */
static void
usb_handle_packet_usb_token_in_other(struct usb_msg *msg) {
      int endpoint_nr = state.last_token.endp & USB_ENDPOINT_ADDRESS_MASK;
      char *data = dynbuf_get(&state.pipe_in[endpoint_nr].fifo,
            real_device.pipe_in[endpoint_nr].descriptor->wMaxPacketSize);

      /* only fetch new data if previous packet was ACKed! */
      if (state.pipe_in[endpoint_nr].last_data_acked) {
            int ret;

            switch (real_device.pipe_in[endpoint_nr].descriptor->bmAttributes & USB_ENDPOINT_TYPE_MASK) {
            case USB_ENDPOINT_TYPE_BULK:
                  ret = usb_bulk_read(real_device.interface[real_device.pipe_in[endpoint_nr].interface].handle,
                        state.last_token.endp,
                        data,
                        real_device.pipe_in[endpoint_nr].descriptor->wMaxPacketSize,
                        UB_LIBUSB_TIMEOUT);
                  break;

            case USB_ENDPOINT_TYPE_INTERRUPT:
                  ret = usb_interrupt_read(real_device.interface[real_device.pipe_in[endpoint_nr].interface].handle,
                        state.last_token.endp,
                        data,
                        real_device.pipe_in[endpoint_nr].descriptor->wMaxPacketSize,
                        UB_LIBUSB_TIMEOUT);
                  break;

            default:
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "cannot read data from endpoint type %d\n",
                        real_device.pipe_in[endpoint_nr].descriptor->bmAttributes &
                         USB_ENDPOINT_TYPE_MASK);
#endif
                  usb_send_handshake(USB_PID_NAK);
                  return;
            }


            if (ret == -ETIMEDOUT) {
                  state.pipe_in[endpoint_nr].last_data_acked = 1;
                  usb_send_handshake(USB_PID_NAK);
                  return;
            } else if (ret < 0) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "read from endpoint %d IN failed (ret=%d): %s\n",
                        endpoint_nr,
                        ret,
                        usb_strerror());
#endif
                  state.pipe_in[endpoint_nr].last_data_acked = 1;
                  usb_send_handshake(USB_PID_STALL);
                  return;
            }
      } else {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "last data packet not acked, sending same data again!\n");
#endif
      }

      if (state.pipe_in[endpoint_nr].fifo_datalen == 0) {
            state.pipe_in[endpoint_nr].last_data_acked = 1;
            usb_send_handshake(USB_PID_NAK);
      } else {
            state.pipe_in[endpoint_nr].last_data_acked = 0;
            usb_send_data(state.pipe_in[endpoint_nr].data_toggle,
                  data,
                  state.pipe_in[endpoint_nr].fifo_datalen);
      }
}

/* handle DATA0/DATA1 packet */
static void
usb_handle_packet_usb_data(struct usb_msg *msg) {
      if (state.addr != state.last_token.addr) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "data packet not meant for me\n");
#endif
            return;
      }
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "%s packet received with %d bytes",
            sig_usb_debug_pid2str(msg->content.usb_data.pid),
            msg->content.usb_data.length);
      usb_debug_data(msg->content.usb_data.data,
            msg->content.usb_data.length);
#endif
      
      if (state.last_token.endp == 0) {
            usb_handle_packet_usb_data_control(msg);
            return;
      }

      if (state.usb_state != STATE_CONFIGURED) {
            /* we must not accept packets to other endpoints
             * but 0 if not in CONFIGURED state */
            /* FIXME: stall target pipe instead? */
            state.control_pipe->stalled = 1;
            usb_send_handshake(USB_PID_STALL);
            return;
      }
      
      usb_handle_packet_usb_data_other(msg);
}

/* handle DATA0/DATA1 packet for control endpoint (0) */
static void
usb_handle_packet_usb_data_control(struct usb_msg *msg) {
      char request_handled;

      if (msg->content.usb_data.pid != state.control_pipe->data_toggle) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "data packet with wrong data toggle (%s; expected %s)\n",
                  sig_usb_debug_pid2str(msg->content.usb_data.pid),
                  sig_usb_debug_pid2str(state.control_pipe->data_toggle));
#endif
            usb_send_handshake(USB_PID_ACK);
            return;
      }

      if (state.control_transfer_stage_information.stage == NO_STAGE) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "not in any control transfer stage, ignoring data packet\n");
#endif
            state.control_pipe->stalled = 1;
            usb_send_handshake(USB_PID_STALL);
            return;
      }
            
      if (state.control_transfer_stage_information.stage == SETUP_STAGE) {
            if (msg->content.usb_data.length != 8) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "ignoring device request with %d != 8 bytes length\n",
                        msg->content.usb_data.length);
#endif
                  state.control_pipe->stalled = 1;
                  usb_send_handshake(USB_PID_STALL);
                  return;
            }

            state.control_transfer_stage_information.request.bmRequestType =
                  msg->content.usb_data.data[0];
            state.control_transfer_stage_information.request.bRequest =
                  msg->content.usb_data.data[1];
            state.control_transfer_stage_information.request.wValue =
                  msg->content.usb_data.data[2] +
                  (msg->content.usb_data.data[3] << 8);
            state.control_transfer_stage_information.request.wIndex =
                  msg->content.usb_data.data[4] +
                  (msg->content.usb_data.data[5] << 8);
            state.control_transfer_stage_information.request.wLength =
                  msg->content.usb_data.data[6] +
                  (msg->content.usb_data.data[7] << 8);

            if ((state.control_transfer_stage_information.request.bmRequestType
               & USB_DEV_REQ_TYPE) != USB_DEV_REQ_TYPE_STANDARD) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "non-standard device request\n");
#endif
                  goto passthrough;
                  return;
            }

#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "%s default request received\n",
                  sig_usb_debug_defaultrequest2str(
                   state.control_transfer_stage_information.request.bRequest));
#endif

            request_handled = 1;

            /* is this standard request implemented? */
            switch (state.control_transfer_stage_information.request.bRequest) {
            case USB_DEV_REQ_SET_ADDRESS:
                  /* check sanity */
                  if (state.control_transfer_stage_information.request.bmRequestType != 0
                   || state.control_transfer_stage_information.request.wIndex != 0
                   || state.control_transfer_stage_information.request.wLength != 0) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                              "ignoring invalid %s request\n",
                              sig_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe->stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "(new device address %02d, not valid until status stage completes)\n",
                        state.control_transfer_stage_information.request.wValue & 0x00FF);
#endif
                  /* address gets valid after status stage, not right now! */
                  break;
                  
            case USB_DEV_REQ_SET_CONFIGURATION:
                  {
                  unsigned char configuration;

                  /* check sanity */
                  if (state.control_transfer_stage_information.request.bmRequestType != 0
                   || state.control_transfer_stage_information.request.wIndex != 0
                   || state.control_transfer_stage_information.request.wLength != 0) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                              "ignoring invalid %s request\n",
                              sig_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe->stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }
                  configuration =
                        state.control_transfer_stage_information.request.wValue & 0x00FF;
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "setting configuration %d\n", configuration);
#endif
                  /* FIXME: maybe we may only transit to CONFIGURED state
                   * after status stage, but I don't care at the moment
                   */

                  usb_realdev_release_interfaces();
                  /* SetConfiguration on real device */
                  /*
                   * FIXME: we must not really go to configuration 0, the kernel won't
                   * allow us to do any control transfers then.
                   */
                  if (configuration != 0
                   && usb_set_configuration(real_device.handle, configuration) != 0) {
                        faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                              "SetConfiguration on real device failed: %s\n",
                              usb_strerror());
                        return;
                  }
                  real_device.configuration = configuration;
                  if (0 < configuration) {
                        usb_realdev_claim_interfaces();
                  }

                  if (state.usb_state == STATE_ADDRESS
                   && configuration == 1) {
                        int i;
                        /* configured */
                        SET_USB_DEVSTATE(STATE_CONFIGURED);

                        /* reset endpoint state */
                        for (i = 0; i < UB_MAX_PIPES; i++) {
                              state.pipe_in[i].data_toggle = USB_PID_DATA0;
                              state.pipe_in[i].last_data_acked = 1;
                              state.pipe_out[i].data_toggle = USB_PID_DATA0;
                              state.pipe_out[i].last_data_acked = 1;
                        }
                  } else if (state.usb_state == STATE_CONFIGURED
                   && configuration == 0) {
                        /* deconfigured */
                        SET_USB_DEVSTATE(STATE_ADDRESS);
                  } else {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                              "cannot move to CONFIGURED state, not yet in ADDRESS state\n");
#endif
                  }
                  break;
                  }

            case USB_DEV_REQ_SET_INTERFACE:
                  {
                  unsigned char alternate_setting;
                  int i;

                  /* check sanity */
                  if (state.control_transfer_stage_information.request.bmRequestType != 0x01
                   || state.control_transfer_stage_information.request.wLength != 0) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                              "ignoring invalid %s request\n",
                              sig_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe->stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }
                  alternate_setting =
                        state.control_transfer_stage_information.request.wValue & 0x00FF;
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "interface %d, setting AlternateSetting %d (NOT IMPLEMENTED)\n",
                        state.control_transfer_stage_information.request.wIndex,
                        alternate_setting);
#endif
                  /* TODO */

                  /* reset endpoint state */
                  for (i = 0; i < UB_MAX_PIPES; i++) {
                        state.pipe_in[i].data_toggle = USB_PID_DATA0;
                        state.pipe_in[i].last_data_acked = 1;
                        state.pipe_out[i].data_toggle = USB_PID_DATA0;
                        state.pipe_out[i].last_data_acked = 1;
                  }

                  break;
                  }

            default:
                  request_handled = 0;
            }

            if (request_handled) {
                  state.control_transfer_stage_information.needs_passthrough = 0;
            } else {
passthrough:
                  /* handle device-to-host transfers immediately */
                  if (state.control_transfer_stage_information.request.bmRequestType
                    & USB_DEV_REQ_DEVICE_TO_HOST) {
                        int ret;
                        char *data = dynbuf_get(&buffer,
                              state.control_transfer_stage_information.request.wLength);

                        ret = usb_realdev_control_msg(
                              state.control_transfer_stage_information.request.bmRequestType,
                              state.control_transfer_stage_information.request.bRequest,
                              state.control_transfer_stage_information.request.wValue,
                              state.control_transfer_stage_information.request.wIndex,
                              data,
                              state.control_transfer_stage_information.request.wLength,
                              UB_LIBUSB_TIMEOUT);

                        if (ret < 0) {
                              faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                                    "usb_control_msg failed: %s\n",
                                    usb_strerror());
                              state.control_pipe->stalled = 1;
                              usb_send_handshake(USB_PID_STALL);
                              return;
                        }

                        state.control_transfer_stage_information.data = data;
                        state.control_transfer_stage_information.data_length = ret;
                        state.control_transfer_stage_information.data_sent = 0;

                  /* handle host-to-device transfers later */
                  } else {
                        state.control_transfer_stage_information.data =
                              dynbuf_get(&buffer,
                              state.control_transfer_stage_information.request.wLength);
                        state.control_transfer_stage_information.data_length = 0;
                        state.control_transfer_stage_information.data_sent = 0;
                        state.control_transfer_stage_information.needs_passthrough = 1;
                  }
            }

            /* advance stage */
            /* length == 0 indicates there is no data stage (USB spec., 9.3.5) */
            if (state.control_transfer_stage_information.request.wLength == 0)
                  state.control_transfer_stage_information.stage = STATUS_STAGE;
            else {
                  state.control_transfer_stage_information.stage = DATA_STAGE;
            }
      } else if (state.control_transfer_stage_information.stage == DATA_STAGE) {
            /* save data for later passthrough */
            /* FIXME: buffer overflow possible if host sends more data than announced */
            assert(state.control_transfer_stage_information.needs_passthrough);
            memcpy(state.control_transfer_stage_information.data +
                  state.control_transfer_stage_information.data_length,
                  msg->content.usb_data.data,
                  msg->content.usb_data.length);
            state.control_transfer_stage_information.data +=
                  msg->content.usb_data.length;
      } else if (state.control_transfer_stage_information.stage == STATUS_STAGE) {
            if (state.control_transfer_stage_information.request.bmRequestType
              & USB_DEV_REQ_DEVICE_TO_HOST) {
                  /* this should be the zero byte status stage packet */
                  if (msg->content.usb_data.length != 0) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                              "ignoring status stage data packet with %d > 0 bytes length\n",
                              msg->content.usb_data.length);
#endif
                        state.control_pipe->stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }

                  state.control_transfer_stage_information.stage = NO_STAGE;
            } else {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                        "ignoring status stage data packet in host-to-device request\n");
#endif
                  state.control_pipe->stalled = 1;
                  usb_send_handshake(USB_PID_STALL);
                  return;
            }
      }

      state.control_pipe->data_toggle =
            usb_toggle_data(state.control_pipe->data_toggle);
      usb_send_handshake(USB_PID_ACK);
}

/* handle DATA0/DATA1 packet for any other endpoint */
static void
usb_handle_packet_usb_data_other(struct usb_msg *msg) {
      int endpoint_nr = state.last_token.endp & USB_ENDPOINT_ADDRESS_MASK;
      int ret;
      
      /* sanity checks */
      if ((state.last_token.endp & USB_ENDPOINT_DIR_MASK)
        == USB_ENDPOINT_IN) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "ignoring %s packet to endpoint %d IN\n",
                  sig_usb_debug_pid2str(msg->content.usb_data.pid),
                  endpoint_nr);
#endif
            usb_send_handshake(USB_PID_NAK);
            return;
      }

      if (real_device.pipe_out[endpoint_nr].interface == -1) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "no interface mapping for endpoint %d OUT\n",
                  endpoint_nr);
#endif
            usb_send_handshake(USB_PID_NAK);
            return;
      }

      /* check data toggle */
      if (msg->content.usb_data.pid != state.pipe_out[endpoint_nr].data_toggle) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "data packet with wrong data toggle (%s; expected %s)\n",
                  sig_usb_debug_pid2str(msg->content.usb_data.pid),
                  sig_usb_debug_pid2str(state.pipe_out[endpoint_nr].data_toggle));
#endif
            usb_send_handshake(USB_PID_ACK);
            return;
      }

      switch (real_device.pipe_out[endpoint_nr].descriptor->bmAttributes & USB_ENDPOINT_TYPE_MASK) {
      case USB_ENDPOINT_TYPE_BULK:
            ret = usb_bulk_write(real_device.interface[real_device.pipe_out[endpoint_nr].interface].handle,
                  state.last_token.endp,
                  msg->content.usb_data.data,
                  msg->content.usb_data.length,
                  UB_LIBUSB_TIMEOUT);
            break;

      case USB_ENDPOINT_TYPE_INTERRUPT:
            ret = usb_interrupt_write(real_device.interface[real_device.pipe_out[endpoint_nr].interface].handle,
                  state.last_token.endp,
                  msg->content.usb_data.data,
                  msg->content.usb_data.length,
                  UB_LIBUSB_TIMEOUT);
            break;

      default:
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "cannot forward data to endpoint type %d\n",
                  real_device.pipe_out[endpoint_nr].descriptor->bmAttributes &
                   USB_ENDPOINT_TYPE_MASK);
#endif
            usb_send_handshake(USB_PID_NAK);
            return;
      }

      if (ret == -ETIMEDOUT) {
            usb_send_handshake(USB_PID_NAK);
            return;
      } else if (ret < 0) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "write to endpoint %d OUT failed: %s\n",
                  endpoint_nr,
                  usb_strerror());
#endif
            usb_send_handshake(USB_PID_STALL);
            return;
      }

      if (msg->content.usb_data.length != ret) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_WARNING, UB_LOG_TYPE, UB_LOG_NAME,
                  "%d instead of %d bytes written to endpoint %d OUT\n",
                  ret,
                  msg->content.usb_data.length,
                  endpoint_nr);
#endif
      }
      
      state.pipe_out[endpoint_nr].data_toggle =
            usb_toggle_data(state.pipe_out[endpoint_nr].data_toggle);
      usb_send_handshake(USB_PID_ACK);
}

/* handle handshake packet */
static void
usb_handle_packet_usb_handshake(struct usb_msg *msg) {
      if (state.addr != state.last_token.addr) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "handshake packet not meant for me\n");
#endif
            return;
      }
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "%s packet received\n",
            sig_usb_debug_pid2str(msg->content.usb_handshake.pid));
#endif
      
      if (state.last_token.endp == 0) {
            usb_handle_packet_usb_handshake_control(msg);
            return;
      }

      if (state.usb_state != STATE_CONFIGURED) {
            /* we must not accept packets to other endpoints
             * but 0 if not in CONFIGURED state */
            state.control_pipe->stalled = 1;
            usb_send_handshake(USB_PID_STALL);
            return;
      }
      
      usb_handle_packet_usb_handshake_other(msg);
}

/* handle handshake packet for control endpoint (0) */
static void
usb_handle_packet_usb_handshake_control(struct usb_msg *msg) {

      if (state.control_transfer_stage_information.stage == DATA_STAGE
       && state.control_transfer_stage_information.request.bmRequestType
        & USB_DEV_REQ_DEVICE_TO_HOST) {
            /* data stage of a device-to-host transfer, data packet handshake */
            if (msg->content.usb_handshake.pid == USB_PID_ACK) {
                  state.control_transfer_stage_information.data_sent +=
                        MIN(real_device.device->descriptor.bMaxPacketSize0,
                              state.control_transfer_stage_information.data_length
                            - state.control_transfer_stage_information.data_sent);
                  state.control_pipe->data_toggle =
                        usb_toggle_data(state.control_pipe->data_toggle);
                  state.control_pipe->last_data_acked = 1;
            }

      } else if (state.control_transfer_stage_information.stage == STATUS_STAGE
       && !(state.control_transfer_stage_information.request.bmRequestType
          & USB_DEV_REQ_DEVICE_TO_HOST)) {
            /* status stage of a host-to-device transfer, zero data packet handshake */
            if (state.control_transfer_stage_information.request.bRequest
             == USB_DEV_REQ_SET_ADDRESS) {
                  /* new address gets valid now */
                  state.addr = state.control_transfer_stage_information.request.wValue & 0x0F;
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                              "new device address %02d is valid now\n",
                              state.addr);
#endif
                  if (state.addr == 0) {
                        SET_USB_DEVSTATE(STATE_DEFAULT);
                  } else {
                        SET_USB_DEVSTATE(STATE_ADDRESS);
                  }
            }
            state.control_transfer_stage_information.stage = NO_STAGE;
            state.control_pipe->data_toggle = USB_PID_DATA0;
            state.control_pipe->last_data_acked = 1;
      }
}

/* handle handshake packet for any other endpoint */
static void
usb_handle_packet_usb_handshake_other(struct usb_msg *msg) {
      int endpoint_nr = state.last_token.endp & USB_ENDPOINT_ADDRESS_MASK;
      /* TODO: sanity checks */
      /* we can assume that an IN endpoint was addressed */
      if (msg->content.usb_handshake.pid == USB_PID_ACK) {
            state.pipe_in[endpoint_nr].data_toggle =
                  usb_toggle_data(state.pipe_in[endpoint_nr].data_toggle);
            state.pipe_in[endpoint_nr].last_data_acked = 1;
      }
}

/* invert DATA0 <-> DATA1 */
static unsigned char
usb_toggle_data(unsigned char toggle) {
      if (toggle == USB_PID_DATA0) {
            return USB_PID_DATA1;
      } else if (toggle == USB_PID_DATA1) {
            return USB_PID_DATA0;
      } else {
            faum_log(FAUM_LOG_ERROR, UB_LOG_TYPE, UB_LOG_NAME,
                  "%s: invalid data toggle %X\n",
                  __FUNCTION__, toggle);
            return USB_PID_DATA0;
      }
}

/* reconfigure CIM */
void
usb_reconfig(void) {
      int new_peer_count;
#if UB_DEBUGMASK & UB_DEBUG_CIM
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "reconfiguring USB bridge\n");
#endif
      cim_reconfig(&config->bcc_usb, dci, 1);
      new_peer_count = cim_get_peer_count(&config->bcc_usb);

      if (new_peer_count > 0
       && (state.usb_state == STATE_OFF
        || state.usb_state == STATE_ATTACHED)) {
#if UB_DEBUGMASK & UB_DEBUG_CIM
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "telling USB we are bus powered and a fullspeed device\n");
#endif
            /*
             * FIXME:
             * Unfortunately, libusb 0.1.* does not have an interface to
             * figure out the real device speed, so we're assuming full
             * speed (12Mb/s) for now.
             */
            SET_USB_DEVSTATE(STATE_ATTACHED);
            sig_usb_send_linestate(&config->bcc_usb, dci,
                  USB_MSG_LINESTATE_TYPE_BUS_POWERED,
                  USB_SPEED_FULL, 0);
      } else if (new_peer_count == 0
              && state.usb_state != STATE_OFF) {
            SET_USB_DEVSTATE(STATE_OFF);
            /* TODO: move real device to some default state */
#if UB_DEBUGMASK & UB_DEBUG_CIM
            faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
                  "detached from USB bus\n");
#endif
      }
}

/* reset USB state machine to defaults */
static void
usb_reset_state(void) {
      state.addr = 0;
      state.control_pipe->data_toggle = USB_PID_DATA0;
      state.control_pipe->stalled = 0;
      state.control_pipe->last_data_acked = 1;
      state.last_token.pid = 0xFF;
      state.last_token.addr = 0xFF;
      state.last_token.endp = 0xFF;
      SET_USB_DEVSTATE(STATE_OFF);
}

/* USB signaling wrappers for debugging purposes */
static void
usb_send_handshake(unsigned char pid)
{
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "sending %s handshake\n",
            sig_usb_debug_pid2str(pid));
#endif
      sig_usb_send_usb_handshake(&state.sig_usb_state, &config->bcc_usb, dci, pid);
}

static void
usb_send_data(unsigned char pid, unsigned char *data, unsigned int length) {
#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, UB_LOG_TYPE, UB_LOG_NAME,
            "sending %s with %d bytes",
            sig_usb_debug_pid2str(pid), length);
      usb_debug_data(data, length);
#endif
      sig_usb_send_usb_data(&state.sig_usb_state, &config->bcc_usb, dci, pid, data, length);
}

#if UB_DEBUGMASK & UB_DEBUG_DUMP_PACKETS
static void
usb_debug_data(unsigned char *data, unsigned int length) {
      int i;
      if (0 < length) {
            faum_cont(FAUM_LOG_DEBUG, " ( ");

            for (i = 0; i < length && i < 8; i++) {
                  faum_cont(FAUM_LOG_DEBUG, "%02X ", data[i]);
            }

            if (i != length) {
                  faum_cont(FAUM_LOG_DEBUG, "... ");
            }

            faum_cont(FAUM_LOG_DEBUG, ")");
      }

      faum_cont(FAUM_LOG_DEBUG, "\n");
}
#endif

Generated by  Doxygen 1.6.0   Back to index