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

usb2serial.c

/* $Id: usb2serial.c,v 1.17 2009-01-28 12:59:22 potyra Exp $ 
 *
 * Copyright (C) 2007-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 "config.h"

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

#include "lib_usb.h"
#include "glue-log.h"
#include "glue-main.h"

#include "usb2serial.h"

#include "usb_device_descriptor.h"

/* ********************* DEFINITIONS ************************** */

#define SIHOSCHI        0

/*----------------------------DEBUG-------------------------------------------*/
/* binary OR these in U2S_DEBUGMASK */
#define U2S_DEBUG_WARNINGS              (1 << 0)
#define U2S_DEBUG_CIM                   (1 << 1)
#define U2S_DEBUG_DUMP_PACKETS          (1 << 2)
#define U2S_DEBUG_STATE_TRANSITIONS     (1 << 3)
#define U2S_DEBUG_WELCOME_MESSAGES      (1 << 4)
#define U2S_DEBUG_DUMP_SERIAL_DATA      (1 << 5)
#define U2S_DEBUG_STATISTICS            (1 << 6)

#if SIHOSCHI
/* #define U2S_DEBUGMASK (U2S_DEBUG_WARNINGS | U2S_DEBUG_STATE_TRANSITIONS) */
#define U2S_DEBUGMASK (U2S_DEBUG_WARNINGS | U2S_DEBUG_STATE_TRANSITIONS | U2S_DEBUG_DUMP_SERIAL_DATA)
#else
#define U2S_DEBUGMASK 0x0000
#endif

#define U2S_LOG_TYPE                    "usb_to_serial"
#define U2S_LOG_NAME                    ""

/* endpoint numbers for PL2303 */
/* encoding like used in device requests (with bit 7 for in/out) */
#define USB_ENDPT_INTERRUPT_IN      0x81
#define USB_ENDPT_BULK_OUT    0x02
#define USB_ENDPT_BULK_IN     0x83

/* encoding like used in token packets */
#define USB_ENDPT_NR_INTERRUPT_IN   (USB_ENDPT_INTERRUPT_IN & 0x0F)
#define USB_ENDPT_NR_BULK_OUT       (USB_ENDPT_BULK_OUT & 0x0F)
#define USB_ENDPT_NR_BULK_IN        (USB_ENDPT_BULK_IN & 0x0F)

/* endpoint FIFO sizes */
#define USB_PACKETSIZE_CONTROL0           8
#define USB_PACKETSIZE_INTERRUPT_IN 10
#define USB_PACKETSIZE_BULK_OUT           64
#define USB_PACKETSIZE_BULK_IN            64

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

/* *********************** MACROS ***************************** */
#if U2S_DEBUGMASK & U2S_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, U2S_LOG_TYPE, U2S_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 ************************ */

struct ringbuffer {
      unsigned long buffer_size;
      unsigned char *buffer;

      unsigned long head, tail;

      unsigned char full;
      int overwrite;
};

/* for usb bridge */
static struct sig_serial *cpssp_port_serial;
static struct sig_boolean *cpssp_port_usb_power;
static struct sig_usb_bus_main *cpssp_port_usb_main;

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;
      } 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;
            unsigned char fifo[USB_PACKETSIZE_BULK_IN];
            unsigned int fifo_datalen;
      } control_pipe,
            interrupt_in_pipe,
            bulk_in_pipe, bulk_out_pipe;

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

#define SENDBUFFER_SIZE 1024
static unsigned char sendbuffer[SENDBUFFER_SIZE];

#define RINGBUFFER_SIZE 1024
static struct ringbuffer serial_buffer;

#if U2S_DEBUGMASK & U2S_DEBUG_STATISTICS
static struct statistics {
      unsigned long token_count_total,
                  token_count,
                  token_count_ctrl,
                  token_count_int_in,
                  token_count_bulk_in,
                  token_count_bulk_out;
} statistics = { 0, 0, 0, 0, 0, 0 };
#endif

static struct usb_device_descriptor device_descriptor_pl2303 = {
      .bLength = 18,
      .bDescriptorType = USB_DESCRIPTOR_TYPE_DEVICE,
      .bcdUSB = 0x0110,
      .bDeviceClass = 0,
      .bDeviceSubClass = 0,
      .bDeviceProtocol = 0,
      .bMaxPacketSize0 = 8,
      .idVendor = 0x067b,
      .idProduct = 0x2303,
      .bcdDevice = 0x0202,
      .iManufacturer = 0,
      .iProduct = 0,
      .iSerialNumber = 0,
      .bNumConfigurations = 1
};

static struct __attribute__ ((__packed__)) usb_combined_descriptor {
      struct usb_configuration_descriptor conf0;
      struct usb_interface_descriptor if0;
      struct usb_endpoint_descriptor ep1;
      struct usb_endpoint_descriptor ep2;
      struct usb_endpoint_descriptor ep3;
} combined_descriptor_pl2303 = {
      .conf0 = {
            .bLength = 9,
            .bDescriptorType = USB_DESCRIPTOR_TYPE_CONFIGURATION,
            .wTotalLength = 39,
            .bNumInterfaces = 1,
            .bConfigurationValue = 1,
            .iConfiguration = 0,
            .bmAttributes = 0xa0,   /* remote wakeup */
            .MaxPower = 50          /* 100mA */
      },
      .if0 = {
            .bLength = 9,
            .bDescriptorType = USB_DESCRIPTOR_TYPE_INTERFACE,
            .bInterfaceNumber = 0,
            .bAlternateSetting = 0,
            .bNumEndpoints = 3,
            .bInterfaceClass = 255,
            .bInterfaceSubClass = 0,
            .bInterfaceProtocol = 0,
            .iInterface = 0
      },
      .ep1 = {
            .bLength = 7,
            .bDescriptorType = USB_DESCRIPTOR_TYPE_ENDPOINT,
            .bEndpointAddress = 0x81,
            .bmAttributes = 3,
            .wMaxPacketSize = 10,
            .bInterval = 1,
      },
      .ep2 = {
            .bLength = 7,
            .bDescriptorType = USB_DESCRIPTOR_TYPE_ENDPOINT,
            .bEndpointAddress = 0x02,
            .bmAttributes = 2,
            .wMaxPacketSize = 64,
            .bInterval = 0,
      },
      .ep3 = {
            .bLength = 7,
            .bDescriptorType = USB_DESCRIPTOR_TYPE_ENDPOINT,
            .bEndpointAddress = 0x83,
            .bmAttributes = 2,
            .wMaxPacketSize = 64,
            .bInterval = 0,
      }
};

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

static void
ringbuffer_create(struct ringbuffer *rb, unsigned long size, int overwrite)
{
      assert(rb);
      
      rb->buffer = malloc(size);
      if (! rb->buffer) {
            fprintf(stderr, "failed to malloc() %lu bytes\n", size);
            exit(1);
      }

      rb->buffer_size = size;
      rb->head = rb->tail = 0;

      rb->full = 0;
      rb->overwrite = overwrite;
}

static void
ringbuffer_destroy(struct ringbuffer *rb)
{
      assert(rb);

      /* also works if buffer is NULL */
      free(rb->buffer);
}

static inline int
ringbuffer_put_byte(struct ringbuffer *rb, unsigned char c)
{
      if (rb->full) {
            if (!rb->overwrite) {
                  return -1;
            } else {
                  rb->head++;
            }
      }

      rb->buffer[rb->tail++] = c;
      rb->tail %= rb->buffer_size;

      if (rb->tail == rb->head) {
            rb->full = 1;
      }

      return 0;
}

/*
 * returns the number of bytes stored, which is between 0 and size
 */
static unsigned long
ringbuffer_put(
      struct ringbuffer *rb,
      const unsigned char *data,
      unsigned long size
)
{
      unsigned long i;
      
      assert(rb);
      assert(rb->buffer);
      assert(data);

      for (i = 0; i < size; i++) {
            if (ringbuffer_put_byte(rb, data[i])) {
                  break;
            }
      }

      return i;
}

static inline int
ringbuffer_get_byte(struct ringbuffer *rb, unsigned char *c)
{
      if (!rb->full
       && rb->head == rb->tail) {
            return -1;
      }
      
      *c = rb->buffer[rb->head++];
      rb->head %= rb->buffer_size;

      rb->full = 0;

      return 0;
}

/*
 * returns the number of bytes retrieved, which is between 0 and size
 */
static unsigned long
ringbuffer_get(
      struct ringbuffer *rb,
      unsigned char *data,
      unsigned long size
)
{
      unsigned long i;
      
      assert(rb);
      assert(rb->buffer);
      assert(data);

      for (i = 0; i < size; i++) {
            if (ringbuffer_get_byte(rb, &data[i])) {
                  break;
            }
      }

      return i;
}

/* forward declarations */
static unsigned char
usb_toggle_data(unsigned char toggle);

#if U2S_DEBUGMASK & U2S_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

/* USB signaling wrappers for debugging purposes */
static void
usb_send_handshake(unsigned char pid)
{
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "sending %s handshake\n",
            cim_usb_debug_pid2str(pid));
#endif
      sig_usb_bus_send_handshake(cpssp_port_usb_main, (void *) 1, pid);
}

static void
usb_send_data(unsigned char pid, unsigned char *data, unsigned int length) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "sending %s with %d bytes",
            cim_usb_debug_pid2str(pid), length);
      usb_debug_data(data, length);
#endif
      sig_usb_bus_send_data(cpssp_port_usb_main, (void *) 1, pid,
                  length, data, 0 /* FIXME */);
}

static void
usb_reset_set(void *s, int val)
{
      /* RESET */
      state.addr = 0;
      SET_USB_DEVSTATE(STATE_DEFAULT);
}

/* handle IN token on control endpoint (0) */
static void
usb_handle_packet_usb_token_in_control(void)
{
      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(USB_PACKETSIZE_CONTROL0,
                        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))) {
            /* status stage or end of data stage of host-to-device transfer */
            /* send zero byte status stage packet */
            state.control_transfer_stage_information.stage = STATUS_STAGE;
            usb_send_data(USB_PID_DATA1, NULL, 0);
      }
}

/* handle IN token on interrupt in endpoint */
static void
usb_handle_packet_usb_token_in_interrupt_in(void)
{
      /* TODO: ? */
/*    usb_send_handshake(USB_PID_NAK); */
      memcpy(sendbuffer, "\xa1\x20\x00\x00\x00\x00\x02\x00\x82\x00", 10);
      usb_send_data(state.interrupt_in_pipe.data_toggle,
            sendbuffer,
            10);
}

/* handle IN token on bulk in endpoint */
static void
usb_handle_packet_usb_token_in_bulk_in(void)
{
      /* answer with bytes from serial buffer */
      
      /* only fetch new data if previous packet was ACKed! */
      if (state.bulk_in_pipe.last_data_acked) {
            state.bulk_in_pipe.fifo_datalen =
                  ringbuffer_get(&serial_buffer,
                        state.bulk_in_pipe.fifo,
                        USB_PACKETSIZE_BULK_IN);
      } else {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "last data packet not acked, sending same data again!\n");
#endif
      }

      if (state.bulk_in_pipe.fifo_datalen == 0) {
            state.bulk_in_pipe.last_data_acked = 1;
            usb_send_handshake(USB_PID_NAK);
      } else {
            state.bulk_in_pipe.last_data_acked = 0;
            usb_send_data(state.bulk_in_pipe.data_toggle,
                  state.bulk_in_pipe.fifo,
                  state.bulk_in_pipe.fifo_datalen);
      }
}

static void
usb_handle_packet_usb_token(
      void *_cpssp,
      int pid,
      int addr,
      int endp
)
{
      if (state.usb_state < STATE_DEFAULT) {
            /* unable to handle USB packets in these states */
            return;
      }

      /* unknown PID? */
      if (pid != USB_PID_SETUP
       && pid != USB_PID_IN
       && pid != USB_PID_OUT) {
            faum_log(FAUM_LOG_ERROR, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "unknown PID %02X\n", pid);
            return;
      }

      /* save for later use */
      state.last_token.pid = pid;
      state.last_token.addr = addr;
      state.last_token.endp = endp;

#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "received %s token to %02d:%02d%s\n",
            cim_usb_debug_pid2str(pid), addr, endp,
            state.addr == addr ? " (this is me!)" : "");
#endif

#if U2S_DEBUGMASK & U2S_DEBUG_STATISTICS
      statistics.token_count_total++;
#endif

      /* ignore tokens to other addresses */
      if (state.addr != addr) {
            return;
      }
      
#if U2S_DEBUGMASK & U2S_DEBUG_STATISTICS
      statistics.token_count++;

      switch (endp) {
      case 0: statistics.token_count_ctrl++; break;
      case USB_ENDPT_NR_INTERRUPT_IN: statistics.token_count_int_in++; 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) {
            if (endp == 0) {
                  usb_handle_packet_usb_token_in_control();
                  return;
            }

            if (state.usb_state != STATE_CONFIGURED) {
                  /* we must not accept packets to other endpoints
                   * but 0 if not in CONFIGURED state */
                  return;
            }
            
            switch (endp) {
            case USB_ENDPT_NR_INTERRUPT_IN:
                  usb_handle_packet_usb_token_in_interrupt_in();
                  break;
            case USB_ENDPT_NR_BULK_IN:
                  usb_handle_packet_usb_token_in_bulk_in();
                  break;
            case USB_ENDPT_NR_BULK_OUT:
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "IN token cannot go into an OUT endpoint\n");
#endif
                  /* FIXME: stall bulk out pipe instead? */
                  state.control_pipe.stalled = 1;
                  usb_send_handshake(USB_PID_STALL);
                  break;
            default:
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "IN token for unknown endpoint\n");
#endif
                  state.control_pipe.stalled = 1;
                  usb_send_handshake(USB_PID_STALL);
            }
      } else if (pid == USB_PID_SETUP
            && 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
            && 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 DATA0/DATA1 packet for control endpoint (0) */
static void
usb_handle_packet_usb_data_control(
      int pid,
      unsigned int length,
      uint8_t *data
)
{
      if (pid != state.control_pipe.data_toggle) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "data packet with wrong data toggle (%s; expected %s)\n",
                  cim_usb_debug_pid2str(pid),
                  cim_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 U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_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 (length != 8) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "ignoring device request with %d != 8 bytes length\n",
                        length);
#endif
                  state.control_pipe.stalled = 1;
                  usb_send_handshake(USB_PID_STALL);
                  return;
            }

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

            if ((state.control_transfer_stage_information.request.bmRequestType
               & USB_DEV_REQ_TYPE) != USB_DEV_REQ_TYPE_STANDARD) {
                  /* we have a problem now: undocumented vendor specific request type */
                  if (state.control_transfer_stage_information.request.bmRequestType
                    & USB_DEV_REQ_DEVICE_TO_HOST) {
                        unsigned short _length =
                              state.control_transfer_stage_information.request.wLength;
                        int i;
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_WARNING, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "answering non-standard device request with zero-filled packet\n");
#endif
                        for (i = 0; i < _length; i++) {
                              sendbuffer[i] = 0;
                        }
                        state.control_transfer_stage_information.data = sendbuffer;
                        state.control_transfer_stage_information.data_length = _length;
                        state.control_transfer_stage_information.data_sent = 0;
                  } else {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_WARNING, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "acknowledging non-standard device request\n");
#endif
                  }
                  /* FIXME: ugly coding style! */
                  goto advance_stage;
            }

#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "%s default request received\n",
                  cim_usb_debug_defaultrequest2str(
                   state.control_transfer_stage_information.request.bRequest));
#endif

            /* is this standard request implemented? */
            switch (state.control_transfer_stage_information.request.bRequest) {

            /* GET_STATUS not implemented */

            case USB_DEV_REQ_SET_FEATURE:
            case USB_DEV_REQ_CLEAR_FEATURE:
                  /* check sanity */
                  if ((state.control_transfer_stage_information.request.bmRequestType != 0
                    && state.control_transfer_stage_information.request.bmRequestType != 2)
                   || (state.control_transfer_stage_information.request.bmRequestType == 0
                    && (state.control_transfer_stage_information.request.wValue != USB_FEATURE_DEVICE_REMOTE_WAKEUP
                     || state.control_transfer_stage_information.request.wIndex != 0))
                   || (state.control_transfer_stage_information.request.bmRequestType == 2
                    && (state.control_transfer_stage_information.request.wValue != USB_FEATURE_ENDPOINT_STALL
                     || (state.control_transfer_stage_information.request.wIndex != 0
                      && state.control_transfer_stage_information.request.wIndex != USB_ENDPT_INTERRUPT_IN
                      && state.control_transfer_stage_information.request.wIndex != USB_ENDPT_BULK_OUT
                      && state.control_transfer_stage_information.request.wIndex != USB_ENDPT_BULK_IN)))
                   || state.control_transfer_stage_information.request.wLength != 0) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring invalid %s request\n",
                              cim_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe.stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }

                  if (state.control_transfer_stage_information.request.bmRequestType == 0) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "DEVICE_REMOTE_WAKEUP");
#endif
                  } else {
                        int value;
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ENDPOINT_STALL on EP %02X",
                              state.control_transfer_stage_information.request.wIndex);
#endif
                        value = state.control_transfer_stage_information.request.bRequest
                              == USB_DEV_REQ_SET_FEATURE ? 1 : 0;

                        switch (state.control_transfer_stage_information.request.wIndex) {
                        case 0:
                              state.control_pipe.stalled = value;
                              state.control_pipe.data_toggle = USB_PID_DATA0;
                              state.control_pipe.last_data_acked = 1;
                              break;
                        case USB_ENDPT_INTERRUPT_IN:
                              state.interrupt_in_pipe.stalled = value;
                              state.interrupt_in_pipe.data_toggle = USB_PID_DATA0;
                              state.interrupt_in_pipe.last_data_acked = 1;
                              break;
                        case USB_ENDPT_BULK_OUT:
                              state.bulk_out_pipe.stalled = value;
                              state.bulk_out_pipe.data_toggle = USB_PID_DATA0;
                              state.bulk_out_pipe.last_data_acked = 1;
                              break;
                        case USB_ENDPT_BULK_IN:
                              state.bulk_in_pipe.stalled = value;
                              state.bulk_in_pipe.data_toggle = USB_PID_DATA0;
                              state.bulk_in_pipe.last_data_acked = 1;
                              break;
                        }
                  }

#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  if (state.control_transfer_stage_information.request.bRequest
                   == USB_DEV_REQ_SET_FEATURE) {
                        faum_cont(FAUM_LOG_DEBUG, " SET\n");
                  } else {
                        faum_cont(FAUM_LOG_DEBUG, " CLEARED\n");
                  }
#endif
                  break;
                  
            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 U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring invalid %s request\n",
                              cim_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe.stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_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_GET_DESCRIPTOR:
                  {
                  unsigned char descriptor_type =
                        state.control_transfer_stage_information.request.wValue >> 8;
                  unsigned char descriptor_index =
                        state.control_transfer_stage_information.request.wValue & 0x00FF;

                  /* check sanity */
                  if (state.control_transfer_stage_information.request.bmRequestType != 0x80
                   || (descriptor_type != USB_DESCRIPTOR_TYPE_DEVICE
                    && descriptor_type != USB_DESCRIPTOR_TYPE_CONFIGURATION
                    && descriptor_type != USB_DESCRIPTOR_TYPE_STRING)) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring invalid %s request\n",
                              cim_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe.stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "requested descriptor is %s (index %d, langid %d) with %d bytes length\n",
                        cim_usb_debug_descriptortype2str(descriptor_type),
                        descriptor_index,
                        state.control_transfer_stage_information.request.wIndex,
                        state.control_transfer_stage_information.request.wLength);
#endif
                  if (descriptor_type == USB_DESCRIPTOR_TYPE_DEVICE
                   && descriptor_index == 0) {
                        state.control_transfer_stage_information.data =
                              (char *) &device_descriptor_pl2303;
                        state.control_transfer_stage_information.data_length =
                              MIN(sizeof(device_descriptor_pl2303),
                                  state.control_transfer_stage_information.request.wLength);
                        state.control_transfer_stage_information.data_sent = 0;
                  } else if (descriptor_type == USB_DESCRIPTOR_TYPE_CONFIGURATION
                        && descriptor_index == 0) {
                        state.control_transfer_stage_information.data =
                              (char *) &combined_descriptor_pl2303;
                        state.control_transfer_stage_information.data_length =
                              MIN(sizeof(combined_descriptor_pl2303),
                                  state.control_transfer_stage_information.request.wLength);
                        state.control_transfer_stage_information.data_sent = 0;
                  } else {
                        state.control_pipe.stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }

                  }

                  break;

            /* SET_DESCRIPTOR not implemented (optional) */

            case USB_DEV_REQ_GET_CONFIGURATION:
                  {
                  unsigned char configuration;

                  /* check sanity */
                  if (state.control_transfer_stage_information.request.bmRequestType != 0x80
                   || state.control_transfer_stage_information.request.wIndex != 0
                   || state.control_transfer_stage_information.request.wLength != 1) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring invalid %s request\n",
                              cim_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe.stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }
                  /* this is easy, we only have one configuration */
                  configuration =
                        state.usb_state == STATE_CONFIGURED ? 1 : 0;
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "telling we have configuration %d\n",
                        configuration);
#endif
                  /* send one byte answer */
                  sendbuffer[0] = configuration;
                  state.control_transfer_stage_information.data = sendbuffer;
                  state.control_transfer_stage_information.data_length = 1;
                  state.control_transfer_stage_information.data_sent = 0;
                  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 U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring invalid %s request\n",
                              cim_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 U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_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
                   */
                  if (state.usb_state == STATE_ADDRESS
                   && configuration == 1) {
                        /* configured */
                        SET_USB_DEVSTATE(STATE_CONFIGURED);

                        /* reset endpoint state */
                        state.control_pipe.data_toggle = USB_PID_DATA0;
                        state.control_pipe.last_data_acked = 1;
                        state.interrupt_in_pipe.data_toggle = USB_PID_DATA0;
                        state.interrupt_in_pipe.last_data_acked = 1;
                        state.bulk_out_pipe.data_toggle = USB_PID_DATA0;
                        state.bulk_out_pipe.last_data_acked = 1;
                        state.bulk_in_pipe.data_toggle = USB_PID_DATA0;
                        state.bulk_in_pipe.last_data_acked = 1;
                  } else if (state.usb_state == STATE_CONFIGURED
                   && configuration == 0) {
                        /* deconfigured */
                        SET_USB_DEVSTATE(STATE_ADDRESS);
                  } else {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "cannot move to CONFIGURED state, not yet in ADDRESS state\n");
#endif
                  }
                  break;
                  }

            case USB_DEV_REQ_GET_INTERFACE:
                  /* check sanity */
                  if (state.control_transfer_stage_information.request.bmRequestType != 0x81
                   || state.control_transfer_stage_information.request.wValue != 0
                   || state.control_transfer_stage_information.request.wLength != 1) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring invalid %s request\n",
                              cim_usb_debug_defaultrequest2str(
                               state.control_transfer_stage_information.request.bRequest));
#endif
                        state.control_pipe.stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }
                  /* this is easy, we only have one alternate setting for
                   * the one interface */
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "interface %d, telling we have AlternateSetting %d\n",
                        state.control_transfer_stage_information.request.wIndex & 0x00FF,
                        0);
#endif
                  /* send one byte answer */
                  sendbuffer[0] = 0;
                  state.control_transfer_stage_information.data = sendbuffer;
                  state.control_transfer_stage_information.data_length = 1;
                  state.control_transfer_stage_information.data_sent = 0;
                  break;

            case USB_DEV_REQ_SET_INTERFACE:
                  {
                  unsigned char alternate_setting;

                  /* check sanity */
                  if (state.control_transfer_stage_information.request.bmRequestType != 0x01
                   || state.control_transfer_stage_information.request.wLength != 0) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring invalid %s request\n",
                              cim_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 U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "interface %d, setting AlternateSetting %d\n",
                        state.control_transfer_stage_information.request.wIndex,
                        alternate_setting);
#endif

                  /* reset endpoint state */
                  state.control_pipe.data_toggle = USB_PID_DATA0;
                  state.control_pipe.last_data_acked = 1;
                  state.bulk_out_pipe.data_toggle = USB_PID_DATA0;
                  state.bulk_out_pipe.last_data_acked = 1;
                  state.bulk_in_pipe.data_toggle = USB_PID_DATA0;
                  state.bulk_in_pipe.last_data_acked = 1;

                  break;
                  }

            /* SYNCH_FRAME not implemented (optional) */

            default:
                  faum_log(FAUM_LOG_WARNING, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "ignoring standard device request %X\n",
                        state.control_transfer_stage_information.request.bRequest);
                  state.control_pipe.stalled = 1;
                  usb_send_handshake(USB_PID_STALL);
                  return;
            }

advance_stage:
            /* 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) {
            /* only vendor-specific host-to-device requests -- just ACK */
      } 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 (length != 0) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                        faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                              "ignoring status stage data packet with %d > 0 bytes length\n",
                              length);
#endif
                        state.control_pipe.stalled = 1;
                        usb_send_handshake(USB_PID_STALL);
                        return;
                  }

                  state.control_transfer_stage_information.stage = NO_STAGE;
            } else {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_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 bulk out endpoint */
static void
usb_handle_packet_usb_data_bulk_out(
      int pid,
      unsigned int length,
      uint8_t *data
)
{
      /* TODO: does the real PL2303 behave like this? */

      /* check data toggle */
      if (pid != state.bulk_out_pipe.data_toggle) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "data packet with wrong data toggle (%s; expected %s)\n",
                  cim_usb_debug_pid2str(pid),
                  cim_usb_debug_pid2str(state.bulk_out_pipe.data_toggle));
#endif
            usb_send_handshake(USB_PID_ACK);
            return;
      }

#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "pushing %d bytes of data to serial interface\n",
            length);
#endif
      while (0 < length) {
            sig_serial_send(cpssp_port_serial, (void *) 1, *data++);
            length--;
      }
      
      state.bulk_out_pipe.data_toggle =
            usb_toggle_data(state.bulk_out_pipe.data_toggle);
      usb_send_handshake(USB_PID_ACK);
}

/* handle DATA0/DATA1 packet */
static void
usb_handle_packet_usb_data(
      void *_cpssp,
      int pid,
      unsigned int length,
      uint8_t *data,
      uint16_t crc16
)
{
      if (state.usb_state < STATE_DEFAULT) {
            /* unable to handle USB packets in these states */
            return;
      }

      if (state.addr != state.last_token.addr) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "data packet not meant for me\n");
#endif
            return;
      }
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "%s packet received with %d bytes",
            cim_usb_debug_pid2str(pid), length);
      usb_debug_data(data, length);
#endif
      
      if (state.last_token.endp == 0) {
            usb_handle_packet_usb_data_control(pid, length, data);
            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;
      }
      
      switch (state.last_token.endp) {
      case USB_ENDPT_NR_INTERRUPT_IN:
      case USB_ENDPT_NR_BULK_IN:
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "data packet cannot go into an IN endpoint\n");
#endif
            /* FIXME: stall bulk in pipe instead? */
            state.control_pipe.stalled = 1;
            usb_send_handshake(USB_PID_STALL);
            break;
      case USB_ENDPT_NR_BULK_OUT:
            usb_handle_packet_usb_data_bulk_out(pid, length, data);
            break;
      default:
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "data packet for unknown endpoint\n");
#endif
            state.control_pipe.stalled = 1;
            usb_send_handshake(USB_PID_STALL);
      }
}

/* handle handshake packet for control endpoint (0) */
static void
usb_handle_packet_usb_handshake_control(int pid)
{
      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 (pid == USB_PID_ACK) {
                  state.control_transfer_stage_information.data_sent +=
                        MIN(USB_PACKETSIZE_CONTROL0,
                              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 U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_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;
      }
      /* TODO: lots more to do here! */
}

/* handle handshake packet for interrupt in endpoint */
static void
usb_handle_packet_usb_handshake_interrupt_in(int pid)
{
      if (pid == USB_PID_ACK) {
            state.interrupt_in_pipe.data_toggle =
                  usb_toggle_data(state.interrupt_in_pipe.data_toggle);
            state.interrupt_in_pipe.last_data_acked = 1;
      }
}

/* handle handshake packet for bulk in endpoint */
static void
usb_handle_packet_usb_handshake_bulk_in(int pid)
{
      if (pid == USB_PID_ACK) {
            state.bulk_in_pipe.data_toggle =
                  usb_toggle_data(state.bulk_in_pipe.data_toggle);
            state.bulk_in_pipe.last_data_acked = 1;
      }
}

/* handle handshake packet for bulk out endpoint */
static void
usb_handle_packet_usb_handshake_bulk_out(int pid)
{
      if (pid == USB_PID_ACK) {
            state.bulk_out_pipe.data_toggle =
                  usb_toggle_data(state.bulk_out_pipe.data_toggle);
            state.bulk_out_pipe.last_data_acked = 1;
      }
}

/* handle handshake packet */
static void
usb_handle_packet_usb_handshake(void *_cpssp, int pid)
{
      if (state.usb_state < STATE_DEFAULT) {
            /* unable to handle USB packets in these states */
            return;
      }

      if (state.addr != state.last_token.addr) {
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "handshake packet not meant for me\n");
#endif
            return;
      }
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "%s packet received\n",
            cim_usb_debug_pid2str(pid));
#endif
      
      if (state.last_token.endp == 0) {
            usb_handle_packet_usb_handshake_control(pid);
            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;
      }
      
      switch (state.last_token.endp) {
      case USB_ENDPT_NR_INTERRUPT_IN:
            usb_handle_packet_usb_handshake_interrupt_in(pid);
            break;
      case USB_ENDPT_NR_BULK_IN:
            usb_handle_packet_usb_handshake_bulk_in(pid);
            break;
      case USB_ENDPT_NR_BULK_OUT:
            usb_handle_packet_usb_handshake_bulk_out(pid);
            break;
      default:
#if U2S_DEBUGMASK & U2S_DEBUG_DUMP_PACKETS
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "data packet for unknown endpoint\n");
#endif
            state.control_pipe.stalled = 1;
            usb_send_handshake(USB_PID_STALL);
      }
}

/* 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, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "%s: invalid data toggle %X\n",
                  __FUNCTION__, toggle);
            return USB_PID_DATA0;
      }
}

/* push serial data to USB */
void 
usb_push_data(const char *data, unsigned int length)
{
      ringbuffer_put(&serial_buffer, data, length);

      /* FIXME: DEBUG HACK! */
      /* write(1, data, length); */
}

#if 0
/* reconfigure CIM */
void
usb_reconfig(void)
{
      int new_peer_count;
#if U2S_DEBUGMASK & U2S_DEBUG_CIM
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "reconfiguring USB bridge\n");
#endif
      cim_reconfig(dci);
      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 U2S_DEBUGMASK & U2S_DEBUG_CIM
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "telling USB we are bus powered and a fullspeed device\n");
#endif
            SET_USB_DEVSTATE(STATE_ATTACHED);
            cim_usb_send_linestate(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);
#if U2S_DEBUGMASK & U2S_DEBUG_CIM
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "detached from USB bus\n");
#endif
      }
}
#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);
}

static void
serial_recv(void *_cpssp, uint8_t byte)
{
      usb_push_data(&byte, sizeof(byte));
}

static void
usb_power_set(void *s, unsigned int val)
{
      static int switched_on_before = 0;

      if (val) {
            usb_reset_state();
            SET_USB_DEVSTATE(STATE_POWERED);

#if U2S_DEBUGMASK & U2S_DEBUG_CIM
            faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                  "answering we are bus powered and a fullspeed device\n");
#endif

            /*
             * Low speed devices only support control and interrupt
             * endpoints; we have bulk endpoints, therefore we
             * _must_ be a fullspeed device.
             */

            /* Send answer: we are bus powered now. */
            sig_usb_bus_speed_set(cpssp_port_usb_main, (void *) 1,
                        USB_SPEED_FULL);

            switched_on_before = 1;
      } else {
#if U2S_DEBUGMASK & U2S_DEBUG_CIM
            if (state.usb_state != STATE_OFF
             && state.usb_state != STATE_ATTACHED) {
                  faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
                        "no power on USB anymore, powering down\n");
            }
#endif
            SET_USB_DEVSTATE(STATE_ATTACHED);
      }
}

void
usb2serial_init(
      unsigned int nr,
      struct sig_serial *port_serial,
      struct sig_usb_bus *port_usb
)
{
      static const struct sig_serial_funcs serial_funcs = {
            .recv = serial_recv,
      };
      static const struct sig_boolean_funcs usb_power_funcs = {
            .set = usb_power_set,
      };
      static const struct sig_usb_bus_main_funcs usb_main_funcs = {
            /* .power_set = usb_power_set, FIXME */
            .reset_set = usb_reset_set,
            .speed_set = NULL,
            .recv_token = usb_handle_packet_usb_token,
            .recv_sof = NULL,
            .recv_data = usb_handle_packet_usb_data,
            .recv_handshake = usb_handle_packet_usb_handshake,
      };

      cpssp_port_serial = port_serial;
      cpssp_port_usb_power = port_usb->power;
      cpssp_port_usb_main = port_usb->bus;

      /* Call */
      sig_serial_connect(port_serial, (void *) 1, &serial_funcs);
      sig_usb_bus_main_connect(port_usb->bus, (void *) 1, &usb_main_funcs);

      /* Out */

      /* In */
      sig_boolean_connect_in(port_usb->power, (void *) 1, &usb_power_funcs);
}

void
usb2serial_create(unsigned int nr, const char *name)
{
      static unsigned int count = 0;

      assert(count == 0); /* FIXME */
      count++;

      assert(sizeof(device_descriptor_pl2303) == 18);
      assert(sizeof(combined_descriptor_pl2303) == 39);

      ringbuffer_create(&serial_buffer, RINGBUFFER_SIZE, 1);
      
      state.usb_state = 0;
      usb_reset_state();
}

void
usb2serial_destroy(unsigned int nr)
{
      ringbuffer_destroy(&serial_buffer);

#if U2S_DEBUGMASK & U2S_DEBUG_STATISTICS
      faum_log(FAUM_LOG_DEBUG, U2S_LOG_TYPE, U2S_LOG_NAME,
            "token count on USB = %lu\n"
            "token count for me = %lu\n"
            "token count CTL0 = %lu\n"
            "token count INT_IN = %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_int_in,
            statistics.token_count_bulk_in,
            statistics.token_count_bulk_out);
#endif
}

Generated by  Doxygen 1.6.0   Back to index