/* * 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_USB_DEBUG 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); }