| /* |
| * Driver for USB ported from CoreBoot |
| * |
| * Copyright (C) 2014 BALATON Zoltan |
| * |
| * This file was part of the libpayload project. |
| * |
| * Copyright (C) 2008-2010 coresystems GmbH |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "drivers/usb.h" |
| #include "usb.h" |
| #include "timer.h" |
| #include "libc/byteorder.h" |
| |
| hci_t *usb_hcs = 0; |
| |
| static void usb_nop_init (usbdev_t *dev); |
| |
| static void |
| usb_nop_destroy (usbdev_t *dev) |
| { |
| if (dev->descriptor != 0) |
| free (dev->descriptor); |
| usb_nop_init (dev); |
| dev->address = -1; |
| dev->hub = -1; |
| dev->port = -1; |
| } |
| |
| static void |
| usb_nop_poll (usbdev_t *dev) |
| { |
| return; |
| } |
| |
| static void |
| usb_nop_init (usbdev_t *dev) |
| { |
| dev->descriptor = 0; |
| dev->destroy = usb_nop_destroy; |
| dev->poll = usb_nop_poll; |
| } |
| |
| hci_t * |
| new_controller (void) |
| { |
| hci_t *controller = malloc (sizeof (hci_t)); |
| |
| if (controller) { |
| /* atomic */ |
| controller->next = usb_hcs; |
| usb_hcs = controller; |
| /* atomic end */ |
| } |
| |
| return controller; |
| } |
| |
| void |
| detach_controller (hci_t *controller) |
| { |
| if (controller == NULL) |
| return; |
| if (usb_hcs == controller) { |
| usb_hcs = controller->next; |
| } else { |
| hci_t *it = usb_hcs; |
| while (it != NULL) { |
| if (it->next == controller) { |
| it->next = controller->next; |
| return; |
| } |
| it = it->next; |
| } |
| } |
| } |
| |
| /** |
| * Shut down all controllers |
| */ |
| int |
| usb_exit (void) |
| { |
| while (usb_hcs != NULL) { |
| usb_hcs->shutdown(usb_hcs); |
| } |
| return 0; |
| } |
| |
| /** |
| * Polls all hubs on all USB controllers, to find out about device changes |
| */ |
| void |
| usb_poll (void) |
| { |
| if (usb_hcs == 0) |
| return; |
| hci_t *controller = usb_hcs; |
| while (controller != NULL) { |
| int i; |
| for (i = 0; i < 128; i++) { |
| if (controller->devices[i] != 0) { |
| controller->devices[i]->poll (controller->devices[i]); |
| } |
| } |
| controller = controller->next; |
| } |
| } |
| |
| void |
| init_device_entry (hci_t *controller, int i) |
| { |
| if (controller->devices[i] != 0) |
| usb_debug("warning: device %d reassigned?\n", i); |
| controller->devices[i] = malloc(sizeof(usbdev_t)); |
| controller->devices[i]->controller = controller; |
| controller->devices[i]->address = -1; |
| controller->devices[i]->hub = -1; |
| controller->devices[i]->port = -1; |
| controller->devices[i]->init = usb_nop_init; |
| controller->devices[i]->init (controller->devices[i]); |
| } |
| |
| void |
| set_feature (usbdev_t *dev, int endp, int feature, int rtype) |
| { |
| dev_req_t dr; |
| |
| dr.bmRequestType = rtype; |
| dr.data_dir = host_to_device; |
| dr.bRequest = SET_FEATURE; |
| dr.wValue = __cpu_to_le16(feature); |
| dr.wIndex = __cpu_to_le16(endp); |
| dr.wLength = 0; |
| dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); |
| } |
| |
| void |
| get_status (usbdev_t *dev, int intf, int rtype, int len, void *data) |
| { |
| dev_req_t dr; |
| |
| dr.bmRequestType = rtype; |
| dr.data_dir = device_to_host; |
| dr.bRequest = GET_STATUS; |
| dr.wValue = 0; |
| dr.wIndex = __cpu_to_le16(intf); |
| dr.wLength = __cpu_to_le16(len); |
| dev->controller->control (dev, IN, sizeof (dr), &dr, len, data); |
| } |
| |
| u8 * |
| get_descriptor (usbdev_t *dev, unsigned char bmRequestType, int descType, |
| int descIdx, int langID) |
| { |
| u8 buf[8]; |
| u8 *result; |
| dev_req_t dr; |
| int size; |
| |
| dr.bmRequestType = bmRequestType; |
| dr.data_dir = device_to_host; // always like this for descriptors |
| dr.bRequest = GET_DESCRIPTOR; |
| dr.wValue = __cpu_to_le16((descType << 8) | descIdx); |
| dr.wIndex = __cpu_to_le16(langID); |
| dr.wLength = __cpu_to_le16(8); |
| if (dev->controller->control (dev, IN, sizeof (dr), &dr, 8, buf)) { |
| usb_debug ("getting descriptor size (type %x) failed\n", |
| descType); |
| } |
| |
| if (descType == 1) { |
| device_descriptor_t *dd = (device_descriptor_t *) buf; |
| usb_debug ("maxPacketSize0: %x\n", dd->bMaxPacketSize0); |
| if (dd->bMaxPacketSize0 != 0) |
| dev->endpoints[0].maxpacketsize = dd->bMaxPacketSize0; |
| } |
| |
| /* special case for configuration descriptors: they carry all their |
| subsequent descriptors with them, and keep the entire size at a |
| different location */ |
| size = buf[0]; |
| if (buf[1] == 2) { |
| int realsize = __le16_to_cpu(((unsigned short *) (buf + 2))[0]); |
| size = realsize; |
| } |
| result = malloc (size); |
| memset (result, 0, size); |
| dr.wLength = __cpu_to_le16(size); |
| if (dev->controller-> |
| control (dev, IN, sizeof (dr), &dr, size, result)) { |
| usb_debug ("getting descriptor (type %x, size %x) failed\n", |
| descType, size); |
| } |
| |
| return result; |
| } |
| |
| void |
| set_configuration (usbdev_t *dev) |
| { |
| dev_req_t dr; |
| |
| dr.bmRequestType = 0; |
| dr.bRequest = SET_CONFIGURATION; |
| dr.wValue = __cpu_to_le16(dev->configuration[5]); |
| dr.wIndex = 0; |
| dr.wLength = 0; |
| dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); |
| } |
| |
| int |
| clear_feature (usbdev_t *dev, int endp, int feature, int rtype) |
| { |
| dev_req_t dr; |
| |
| dr.bmRequestType = rtype; |
| dr.data_dir = host_to_device; |
| dr.bRequest = CLEAR_FEATURE; |
| dr.wValue = __cpu_to_le16(feature); |
| dr.wIndex = __cpu_to_le16(endp); |
| dr.wLength = 0; |
| return dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); |
| } |
| |
| int |
| clear_stall (endpoint_t *ep) |
| { |
| usbdev_t *dev = ep->dev; |
| int endp = ep->endpoint; |
| int rtype = gen_bmRequestType (host_to_device, standard_type, |
| endp ? endp_recp : dev_recp); |
| |
| int ret = clear_feature (dev, endp, ENDPOINT_HALT, rtype); |
| ep->toggle = 0; |
| return ret; |
| } |
| |
| /* returns free address or -1 */ |
| static int |
| get_free_address (hci_t *controller) |
| { |
| int i; |
| for (i = 1; i < 128; i++) { |
| if (controller->devices[i] == 0) |
| return i; |
| } |
| usb_debug ("no free address found\n"); |
| return -1; // no free address |
| } |
| |
| int |
| generic_set_address (hci_t *controller, int speed, int hubport, int hubaddr) |
| { |
| int adr = get_free_address (controller); // address to set |
| dev_req_t dr; |
| |
| memset (&dr, 0, sizeof (dr)); |
| dr.data_dir = host_to_device; |
| dr.req_type = standard_type; |
| dr.req_recp = dev_recp; |
| dr.bRequest = SET_ADDRESS; |
| dr.wValue = __cpu_to_le16(adr); |
| dr.wIndex = 0; |
| dr.wLength = 0; |
| |
| init_device_entry(controller, adr); |
| usbdev_t *dev = controller->devices[adr]; |
| // dummy values for registering the address |
| dev->address = 0; |
| dev->hub = hubaddr; |
| dev->port = hubport; |
| dev->speed = speed; |
| dev->endpoints[0].dev = dev; |
| dev->endpoints[0].endpoint = 0; |
| dev->endpoints[0].maxpacketsize = 8; |
| dev->endpoints[0].toggle = 0; |
| dev->endpoints[0].direction = SETUP; |
| mdelay (50); |
| if (dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0)) { |
| return -1; |
| } |
| mdelay (50); |
| |
| return adr; |
| } |
| |
| /* Normalize bInterval to log2 of microframes */ |
| static int |
| usb_decode_interval(const int speed, const endpoint_type type, const unsigned char bInterval) |
| { |
| #define LOG2(a) ((sizeof(unsigned) << 3) - __builtin_clz(a) - 1) |
| switch (speed) { |
| case LOW_SPEED: |
| switch (type) { |
| case ISOCHRONOUS: case INTERRUPT: |
| return LOG2(bInterval) + 3; |
| default: |
| return 0; |
| } |
| case FULL_SPEED: |
| switch (type) { |
| case ISOCHRONOUS: |
| return (bInterval - 1) + 3; |
| case INTERRUPT: |
| return LOG2(bInterval) + 3; |
| default: |
| return 0; |
| } |
| case HIGH_SPEED: |
| switch (type) { |
| case ISOCHRONOUS: case INTERRUPT: |
| return bInterval - 1; |
| default: |
| return LOG2(bInterval); |
| } |
| case SUPER_SPEED: |
| switch (type) { |
| case ISOCHRONOUS: case INTERRUPT: |
| return bInterval - 1; |
| default: |
| return 0; |
| } |
| default: |
| return 0; |
| } |
| #undef LOG2 |
| } |
| |
| static int |
| set_address (hci_t *controller, int speed, int hubport, int hubaddr) |
| { |
| int adr = controller->set_address(controller, speed, hubport, hubaddr); |
| if (adr < 0 || !controller->devices[adr]) { |
| usb_debug ("set_address failed\n"); |
| return -1; |
| } |
| configuration_descriptor_t *cd; |
| device_descriptor_t *dd; |
| |
| usbdev_t *dev = controller->devices[adr]; |
| dev->address = adr; |
| dev->hub = hubaddr; |
| dev->port = hubport; |
| dev->speed = speed; |
| dev->descriptor = get_descriptor (dev, gen_bmRequestType |
| (device_to_host, standard_type, dev_recp), 1, 0, 0); |
| dd = (device_descriptor_t *) dev->descriptor; |
| |
| usb_debug ("* found device (0x%04x:0x%04x, USB %x.%x)", |
| __le16_to_cpu(dd->idVendor), __le16_to_cpu(dd->idProduct), |
| __le16_to_cpu(dd->bcdUSB) >> 8, __le16_to_cpu(dd->bcdUSB) & 0xff); |
| dev->quirks = USB_QUIRK_NONE; |
| |
| usb_debug ("\ndevice has %x configurations\n", dd->bNumConfigurations); |
| if (dd->bNumConfigurations == 0) { |
| /* device isn't usable */ |
| usb_debug ("... no usable configuration!\n"); |
| dev->address = 0; |
| return -1; |
| } |
| |
| dev->configuration = get_descriptor (dev, gen_bmRequestType |
| (device_to_host, standard_type, dev_recp), 2, 0, 0); |
| cd = (configuration_descriptor_t *) dev->configuration; |
| interface_descriptor_t *interface = |
| (interface_descriptor_t *) (((char *) cd) + cd->bLength); |
| { |
| int i; |
| int num = cd->bNumInterfaces; |
| interface_descriptor_t *current = interface; |
| usb_debug ("device has %x interfaces\n", num); |
| if (num > 1) { |
| usb_debug ("\nNOTICE: This driver defaults to using the first interface.\n" |
| "This might be the wrong choice and lead to limited functionality\n" |
| "of the device.\n"); |
| /* we limit to the first interface, as there was no need to |
| * implement something else for the time being. If you need |
| * it, see the SetInterface and GetInterface functions in |
| * the USB specification, and adapt appropriately. |
| */ |
| num = (num > 1) ? 1 : num; |
| } |
| for (i = 0; i < num; i++) { |
| int j; |
| usb_debug (" #%x has %x endpoints, interface %x:%x, protocol %x\n", |
| current->bInterfaceNumber, current->bNumEndpoints, current->bInterfaceClass, current->bInterfaceSubClass, current->bInterfaceProtocol); |
| endpoint_descriptor_t *endp = |
| (endpoint_descriptor_t *) (((char *) current) |
| + current->bLength); |
| /* Skip any non-endpoint descriptor */ |
| if (endp->bDescriptorType != 0x05) |
| endp = (endpoint_descriptor_t *)(((char *)endp) + ((char *)endp)[0]); |
| |
| memset (dev->endpoints, 0, sizeof (dev->endpoints)); |
| dev->num_endp = 1; // 0 always exists |
| dev->endpoints[0].dev = dev; |
| dev->endpoints[0].maxpacketsize = dd->bMaxPacketSize0; |
| dev->endpoints[0].direction = SETUP; |
| dev->endpoints[0].type = CONTROL; |
| dev->endpoints[0].interval = usb_decode_interval(dev->speed, CONTROL, endp->bInterval); |
| for (j = 1; j <= current->bNumEndpoints; j++) { |
| #ifdef CONFIG_DEBUG_USB |
| static const char *transfertypes[4] = { |
| "control", "isochronous", "bulk", "interrupt" |
| }; |
| usb_debug (" #%x: Endpoint %x (%s), max packet size %x, type %s\n", j, endp->bEndpointAddress & 0x7f, ((endp->bEndpointAddress & 0x80) != 0) ? "in" : "out", __le16_to_cpu(endp->wMaxPacketSize), transfertypes[endp->bmAttributes]); |
| #endif |
| endpoint_t *ep = |
| &dev->endpoints[dev->num_endp++]; |
| ep->dev = dev; |
| ep->endpoint = endp->bEndpointAddress; |
| ep->toggle = 0; |
| ep->maxpacketsize = __le16_to_cpu(endp->wMaxPacketSize); |
| ep->direction = |
| ((endp->bEndpointAddress & 0x80) == |
| 0) ? OUT : IN; |
| ep->type = endp->bmAttributes; |
| ep->interval = usb_decode_interval(dev->speed, ep->type, endp->bInterval); |
| endp = (endpoint_descriptor_t |
| *) (((char *) endp) + endp->bLength); |
| } |
| current = (interface_descriptor_t *) endp; |
| } |
| } |
| |
| if (controller->finish_device_config && |
| controller->finish_device_config(dev)) |
| return adr; /* Device isn't configured correctly, |
| only control transfers may work. */ |
| |
| set_configuration(dev); |
| |
| int class = dd->bDeviceClass; |
| if (class == 0) |
| class = interface->bInterfaceClass; |
| |
| usb_debug(", class: "); |
| switch (class) { |
| case audio_device: |
| usb_debug("audio\n"); |
| break; |
| case comm_device: |
| usb_debug("communication\n"); |
| break; |
| case hid_device: |
| usb_debug ("HID\n"); |
| #ifdef CONFIG_USB_HID |
| controller->devices[adr]->init = usb_hid_init; |
| return adr; |
| #else |
| usb_debug ("NOTICE: USB HID support not compiled in\n"); |
| #endif |
| break; |
| case physical_device: |
| usb_debug("physical\n"); |
| break; |
| case imaging_device: |
| usb_debug("camera\n"); |
| break; |
| case printer_device: |
| usb_debug("printer\n"); |
| break; |
| case msc_device: |
| usb_debug ("MSC\n"); |
| #ifdef CONFIG_USB_MSC |
| controller->devices[adr]->init = usb_msc_init; |
| return adr; |
| #else |
| usb_debug ("NOTICE: USB MSC support not compiled in\n"); |
| #endif |
| break; |
| case hub_device: |
| usb_debug ("hub\n"); |
| #ifdef CONFIG_USB_HUB |
| controller->devices[adr]->init = usb_hub_init; |
| return adr; |
| #else |
| usb_debug ("NOTICE: USB hub support not compiled in.\n"); |
| #endif |
| break; |
| case cdc_device: |
| usb_debug("CDC\n"); |
| break; |
| case ccid_device: |
| usb_debug("smartcard / CCID\n"); |
| break; |
| case security_device: |
| usb_debug("content security\n"); |
| break; |
| case video_device: |
| usb_debug("video\n"); |
| break; |
| case healthcare_device: |
| usb_debug("healthcare\n"); |
| break; |
| case diagnostic_device: |
| usb_debug("diagnostic\n"); |
| break; |
| case wireless_device: |
| usb_debug("wireless\n"); |
| break; |
| default: |
| usb_debug("unsupported class %x\n", class); |
| break; |
| } |
| controller->devices[adr]->init = usb_generic_init; |
| return adr; |
| } |
| |
| /* |
| * Should be called by the hub drivers whenever a physical detach occurs |
| * and can be called by usb class drivers if they are unsatisfied with a |
| * malfunctioning device. |
| */ |
| void |
| usb_detach_device(hci_t *controller, int devno) |
| { |
| /* check if device exists, as we may have |
| been called yet by the usb class driver */ |
| if (controller->devices[devno]) { |
| controller->devices[devno]->destroy (controller->devices[devno]); |
| free(controller->devices[devno]); |
| controller->devices[devno] = NULL; |
| if (controller->destroy_device) |
| controller->destroy_device(controller, devno); |
| } |
| } |
| |
| int |
| usb_attach_device(hci_t *controller, int hubaddress, int port, int speed) |
| { |
| #ifdef CONFIG_DEBUG_USB |
| static const char* speeds[] = { "full", "low", "high" }; |
| usb_debug ("%sspeed device\n", (speed <= 2) ? speeds[speed] : "invalid value - no"); |
| #endif |
| int newdev = set_address (controller, speed, port, hubaddress); |
| if (newdev == -1) |
| return -1; |
| usbdev_t *newdev_t = controller->devices[newdev]; |
| // determine responsible driver - current done in set_address |
| newdev_t->init (newdev_t); |
| /* init() may have called usb_detach_device() yet, so check */ |
| return controller->devices[newdev] ? newdev : -1; |
| } |
| |
| static void |
| usb_generic_destroy (usbdev_t *dev) |
| { |
| if (usb_generic_remove) |
| usb_generic_remove(dev); |
| } |
| |
| void |
| usb_generic_init (usbdev_t *dev) |
| { |
| dev->data = NULL; |
| dev->destroy = usb_generic_destroy; |
| |
| if (usb_generic_create) |
| usb_generic_create(dev); |
| } |