blob: 88b7580d480faee7ac4721d35d6be4a68670ac85 [file] [log] [blame]
/*
* 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);
}