/* * (C) Copyright 2003 * Gerry Hamel, geh@ti.com, Texas Instruments * * Based on * linux/drivers/usbd/usbd.c.c - USB Device Core Layer * * Copyright (c) 2000, 2001, 2002 Lineo * Copyright (c) 2001 Hewlett Packard * * By: * Stuart Lynne <sl@lineo.com>, * Tom Rushworth <tbr@lineo.com>, * Bruce Balden <balden@lineo.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include <malloc.h> #include "usbdcore.h" #define MAX_INTERFACES 2 int maxstrings = 20; /* Global variables ************************************************************************** */ struct usb_string_descriptor **usb_strings; int usb_devices; extern struct usb_function_driver ep0_driver; int registered_functions; int registered_devices; char *usbd_device_events[] = { "DEVICE_UNKNOWN", "DEVICE_INIT", "DEVICE_CREATE", "DEVICE_HUB_CONFIGURED", "DEVICE_RESET", "DEVICE_ADDRESS_ASSIGNED", "DEVICE_CONFIGURED", "DEVICE_SET_INTERFACE", "DEVICE_SET_FEATURE", "DEVICE_CLEAR_FEATURE", "DEVICE_DE_CONFIGURED", "DEVICE_BUS_INACTIVE", "DEVICE_BUS_ACTIVITY", "DEVICE_POWER_INTERRUPTION", "DEVICE_HUB_RESET", "DEVICE_DESTROY", "DEVICE_FUNCTION_PRIVATE", }; char *usbd_device_states[] = { "STATE_INIT", "STATE_CREATED", "STATE_ATTACHED", "STATE_POWERED", "STATE_DEFAULT", "STATE_ADDRESSED", "STATE_CONFIGURED", "STATE_UNKNOWN", }; char *usbd_device_requests[] = { "GET STATUS", /* 0 */ "CLEAR FEATURE", /* 1 */ "RESERVED", /* 2 */ "SET FEATURE", /* 3 */ "RESERVED", /* 4 */ "SET ADDRESS", /* 5 */ "GET DESCRIPTOR", /* 6 */ "SET DESCRIPTOR", /* 7 */ "GET CONFIGURATION", /* 8 */ "SET CONFIGURATION", /* 9 */ "GET INTERFACE", /* 10 */ "SET INTERFACE", /* 11 */ "SYNC FRAME", /* 12 */ }; char *usbd_device_descriptors[] = { "UNKNOWN", /* 0 */ "DEVICE", /* 1 */ "CONFIG", /* 2 */ "STRING", /* 3 */ "INTERFACE", /* 4 */ "ENDPOINT", /* 5 */ "DEVICE QUALIFIER", /* 6 */ "OTHER SPEED", /* 7 */ "INTERFACE POWER", /* 8 */ }; char *usbd_device_status[] = { "USBD_OPENING", "USBD_OK", "USBD_SUSPENDED", "USBD_CLOSING", }; /* Descriptor support functions ************************************************************** */ /** * usbd_get_string - find and return a string descriptor * @index: string index to return * * Find an indexed string and return a pointer to a it. */ struct usb_string_descriptor *usbd_get_string (__u8 index) { if (index >= maxstrings) { return NULL; } return usb_strings[index]; } /* Access to device descriptor functions ***************************************************** */ /* * * usbd_device_configuration_instance - find a configuration instance for this device * @device: * @configuration: index to configuration, 0 - N-1 * * Get specifed device configuration. Index should be bConfigurationValue-1. */ static struct usb_configuration_instance *usbd_device_configuration_instance (struct usb_device_instance *device, unsigned int port, unsigned int configuration) { /* XXX */ configuration = configuration ? configuration - 1 : 0; if (configuration >= device->configurations) { return NULL; } return device->configuration_instance_array + configuration; } /* * * usbd_device_interface_instance * @device: * @configuration: index to configuration, 0 - N-1 * @interface: index to interface * * Return the specified interface descriptor for the specified device. */ struct usb_interface_instance *usbd_device_interface_instance (struct usb_device_instance *device, int port, int configuration, int interface) { struct usb_configuration_instance *configuration_instance; if ((configuration_instance = usbd_device_configuration_instance (device, port, configuration)) == NULL) { return NULL; } if (interface >= configuration_instance->interfaces) { return NULL; } return configuration_instance->interface_instance_array + interface; } /* * * usbd_device_alternate_descriptor_list * @device: * @configuration: index to configuration, 0 - N-1 * @interface: index to interface * @alternate: alternate setting * * Return the specified alternate descriptor for the specified device. */ struct usb_alternate_instance *usbd_device_alternate_instance (struct usb_device_instance *device, int port, int configuration, int interface, int alternate) { struct usb_interface_instance *interface_instance; if ((interface_instance = usbd_device_interface_instance (device, port, configuration, interface)) == NULL) { return NULL; } if (alternate >= interface_instance->alternates) { return NULL; } return interface_instance->alternates_instance_array + alternate; } /* * * usbd_device_device_descriptor * @device: which device * @configuration: index to configuration, 0 - N-1 * @port: which port * * Return the specified configuration descriptor for the specified device. */ struct usb_device_descriptor *usbd_device_device_descriptor (struct usb_device_instance *device, int port) { return (device->device_descriptor); } /** * usbd_device_configuration_descriptor * @device: which device * @port: which port * @configuration: index to configuration, 0 - N-1 * * Return the specified configuration descriptor for the specified device. */ struct usb_configuration_descriptor *usbd_device_configuration_descriptor (struct usb_device_instance *device, int port, int configuration) { struct usb_configuration_instance *configuration_instance; if (!(configuration_instance = usbd_device_configuration_instance (device, port, configuration))) { return NULL; } return (configuration_instance->configuration_descriptor); } /** * usbd_device_interface_descriptor * @device: which device * @port: which port * @configuration: index to configuration, 0 - N-1 * @interface: index to interface * @alternate: alternate setting * * Return the specified interface descriptor for the specified device. */ struct usb_interface_descriptor *usbd_device_interface_descriptor (struct usb_device_instance *device, int port, int configuration, int interface, int alternate) { struct usb_interface_instance *interface_instance; if (!(interface_instance = usbd_device_interface_instance (device, port, configuration, interface))) { return NULL; } if ((alternate < 0) || (alternate >= interface_instance->alternates)) { return NULL; } return (interface_instance->alternates_instance_array[alternate].interface_descriptor); } /** * usbd_device_endpoint_descriptor_index * @device: which device * @port: which port * @configuration: index to configuration, 0 - N-1 * @interface: index to interface * @alternate: index setting * @index: which index * * Return the specified endpoint descriptor for the specified device. */ struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor_index (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int index) { struct usb_alternate_instance *alternate_instance; if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) { return NULL; } if (index >= alternate_instance->endpoints) { return NULL; } return *(alternate_instance->endpoints_descriptor_array + index); } /** * usbd_device_endpoint_transfersize * @device: which device * @port: which port * @configuration: index to configuration, 0 - N-1 * @interface: index to interface * @index: which index * * Return the specified endpoint transfer size; */ int usbd_device_endpoint_transfersize (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int index) { struct usb_alternate_instance *alternate_instance; if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) { return 0; } if (index >= alternate_instance->endpoints) { return 0; } return *(alternate_instance->endpoint_transfersize_array + index); } /** * usbd_device_endpoint_descriptor * @device: which device * @port: which port * @configuration: index to configuration, 0 - N-1 * @interface: index to interface * @alternate: alternate setting * @endpoint: which endpoint * * Return the specified endpoint descriptor for the specified device. */ struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int endpoint) { struct usb_endpoint_descriptor *endpoint_descriptor; int i; for (i = 0; !(endpoint_descriptor = usbd_device_endpoint_descriptor_index (device, port, configuration, interface, alternate, i)); i++) { if (endpoint_descriptor->bEndpointAddress == endpoint) { return endpoint_descriptor; } } return NULL; } /** * usbd_endpoint_halted * @device: point to struct usb_device_instance * @endpoint: endpoint to check * * Return non-zero if endpoint is halted. */ int usbd_endpoint_halted (struct usb_device_instance *device, int endpoint) { return (device->status == USB_STATUS_HALT); } /** * usbd_rcv_complete - complete a receive * @endpoint: * @len: * @urb_bad: * * Called from rcv interrupt to complete. */ void usbd_rcv_complete(struct usb_endpoint_instance *endpoint, int len, int urb_bad) { if (endpoint) { struct urb *rcv_urb; /*usbdbg("len: %d urb: %p\n", len, endpoint->rcv_urb); */ /* if we had an urb then update actual_length, dispatch if neccessary */ if ((rcv_urb = endpoint->rcv_urb)) { /*usbdbg("actual: %d buffer: %d\n", */ /*rcv_urb->actual_length, rcv_urb->buffer_length); */ /* check the urb is ok, are we adding data less than the packetsize */ if (!urb_bad && (len <= endpoint->rcv_packetSize)) { /*usbdbg("updating actual_length by %d\n",len); */ /* increment the received data size */ rcv_urb->actual_length += len; } else { usberr(" RECV_ERROR actual: %d buffer: %d urb_bad: %d\n", rcv_urb->actual_length, rcv_urb->buffer_length, urb_bad); rcv_urb->actual_length = 0; rcv_urb->status = RECV_ERROR; } } else { usberr("no rcv_urb!"); } } else { usberr("no endpoint!"); } } /** * usbd_tx_complete - complete a transmit * @endpoint: * @resetart: * * Called from tx interrupt to complete. */ void usbd_tx_complete (struct usb_endpoint_instance *endpoint) { if (endpoint) { struct urb *tx_urb; /* if we have a tx_urb advance or reset, finish if complete */ if ((tx_urb = endpoint->tx_urb)) { int sent = endpoint->last; endpoint->sent += sent; endpoint->last -= sent; if( (endpoint->tx_urb->actual_length - endpoint->sent) <= 0 ) { tx_urb->actual_length = 0; endpoint->sent = 0; endpoint->last = 0; /* Remove from active, save for re-use */ urb_detach(tx_urb); urb_append(&endpoint->done, tx_urb); /*usbdbg("done->next %p, tx_urb %p, done %p", */ /* endpoint->done.next, tx_urb, &endpoint->done); */ endpoint->tx_urb = first_urb_detached(&endpoint->tx); if( endpoint->tx_urb ) { endpoint->tx_queue--; usbdbg("got urb from tx list"); } if( !endpoint->tx_urb ) { /*usbdbg("taking urb from done list"); */ endpoint->tx_urb = first_urb_detached(&endpoint->done); } if( !endpoint->tx_urb ) { usbdbg("allocating new urb for tx_urb"); endpoint->tx_urb = usbd_alloc_urb(tx_urb->device, endpoint); } } } } } /* URB linked list functions ***************************************************** */ /* * Initialize an urb_link to be a single element list. * If the urb_link is being used as a distinguished list head * the list is empty when the head is the only link in the list. */ void urb_link_init (urb_link * ul) { if (ul) { ul->prev = ul->next = ul; } } /* * Detach an urb_link from a list, and set it * up as a single element list, so no dangling * pointers can be followed, and so it can be * joined to another list if so desired. */ void urb_detach (struct urb *urb) { if (urb) { urb_link *ul = &urb->link; ul->next->prev = ul->prev; ul->prev->next = ul->next; urb_link_init (ul); } } /* * Return the first urb_link in a list with a distinguished * head "hd", or NULL if the list is empty. This will also * work as a predicate, returning NULL if empty, and non-NULL * otherwise. */ urb_link *first_urb_link (urb_link * hd) { urb_link *nx; if (NULL != hd && NULL != (nx = hd->next) && nx != hd) { /* There is at least one element in the list */ /* (besides the distinguished head). */ return (nx); } /* The list is empty */ return (NULL); } /* * Return the first urb in a list with a distinguished * head "hd", or NULL if the list is empty. */ struct urb *first_urb (urb_link * hd) { urb_link *nx; if (NULL == (nx = first_urb_link (hd))) { /* The list is empty */ return (NULL); } return (p2surround (struct urb, link, nx)); } /* * Detach and return the first urb in a list with a distinguished * head "hd", or NULL if the list is empty. * */ struct urb *first_urb_detached (urb_link * hd) { struct urb *urb; if ((urb = first_urb (hd))) { urb_detach (urb); } return urb; } /* * Append an urb_link (or a whole list of * urb_links) to the tail of another list * of urb_links. */ void urb_append (urb_link * hd, struct urb *urb) { if (hd && urb) { urb_link *new = &urb->link; /* This allows the new urb to be a list of urbs, */ /* with new pointing at the first, but the link */ /* must be initialized. */ /* Order is important here... */ urb_link *pul = hd->prev; new->prev->next = hd; hd->prev = new->prev; new->prev = pul; pul->next = new; } } /* URB create/destroy functions ***************************************************** */ /** * usbd_alloc_urb - allocate an URB appropriate for specified endpoint * @device: device instance * @endpoint: endpoint * * Allocate an urb structure. The usb device urb structure is used to * contain all data associated with a transfer, including a setup packet for * control transfers. * * NOTE: endpoint_address MUST contain a direction flag. */ struct urb *usbd_alloc_urb (struct usb_device_instance *device, struct usb_endpoint_instance *endpoint) { struct urb *urb; if( !(urb = (struct urb*)malloc(sizeof(struct urb))) ) { usberr(" F A T A L: malloc(%u) FAILED!!!!", sizeof(struct urb)); return NULL; } /* Fill in known fields */ memset(urb, 0, sizeof(struct urb)); urb->endpoint = endpoint; urb->device = device; urb->buffer = (u8*)urb->buffer_data; urb->buffer_length = sizeof(urb->buffer_data); urb_link_init (&urb->link); return urb; } /** * usbd_dealloc_urb - deallocate an URB and associated buffer * @urb: pointer to an urb structure * * Deallocate an urb structure and associated data. */ void usbd_dealloc_urb (struct urb *urb) { if (urb) { free (urb); } } /* Event signaling functions ***************************************************** */ /** * usbd_device_event - called to respond to various usb events * @device: pointer to struct device * @event: event to respond to * * Used by a Bus driver to indicate an event. */ void usbd_device_event_irq (struct usb_device_instance *device, usb_device_event_t event, int data) { usb_device_state_t state; if (!device || !device->bus) { usberr("(%p,%d) NULL device or device->bus", device, event); return; } state = device->device_state; usbinfo("%s", usbd_device_events[event]); switch (event) { case DEVICE_UNKNOWN: break; case DEVICE_INIT: device->device_state = STATE_INIT; break; case DEVICE_CREATE: device->device_state = STATE_ATTACHED; break; case DEVICE_HUB_CONFIGURED: device->device_state = STATE_POWERED; break; case DEVICE_RESET: device->device_state = STATE_DEFAULT; device->address = 0; break; case DEVICE_ADDRESS_ASSIGNED: device->device_state = STATE_ADDRESSED; break; case DEVICE_CONFIGURED: device->device_state = STATE_CONFIGURED; break; case DEVICE_DE_CONFIGURED: device->device_state = STATE_ADDRESSED; break; case DEVICE_BUS_INACTIVE: if (device->status != USBD_CLOSING) { device->status = USBD_SUSPENDED; } break; case DEVICE_BUS_ACTIVITY: if (device->status != USBD_CLOSING) { device->status = USBD_OK; } break; case DEVICE_SET_INTERFACE: break; case DEVICE_SET_FEATURE: break; case DEVICE_CLEAR_FEATURE: break; case DEVICE_POWER_INTERRUPTION: device->device_state = STATE_POWERED; break; case DEVICE_HUB_RESET: device->device_state = STATE_ATTACHED; break; case DEVICE_DESTROY: device->device_state = STATE_UNKNOWN; break; case DEVICE_FUNCTION_PRIVATE: break; default: usbdbg("event %d - not handled",event); break; } /*usbdbg("%s event: %d oldstate: %d newstate: %d status: %d address: %d", device->name, event, state, device->device_state, device->status, device->address); */ /* tell the bus interface driver */ if( device->event ) { /* usbdbg("calling device->event"); */ device->event(device, event, data); } }