summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/ppc/qemu/methods.c2
-rw-r--r--config/examples/ppc_config.xml3
-rw-r--r--drivers/Kconfig20
-rw-r--r--drivers/build.xml4
-rw-r--r--drivers/pci.c11
-rw-r--r--drivers/pci.h1
-rw-r--r--drivers/pci_database.c20
-rw-r--r--drivers/pci_database.h1
-rw-r--r--drivers/usb.c587
-rw-r--r--drivers/usb.h357
-rw-r--r--drivers/usbhid.c579
-rw-r--r--drivers/usbohci.c926
-rw-r--r--drivers/usbohci.h45
-rw-r--r--drivers/usbohci_private.h270
-rw-r--r--drivers/usbohci_rh.c212
-rw-r--r--include/drivers/pci.h1
-rw-r--r--include/drivers/usb.h8
17 files changed, 3046 insertions, 1 deletions
diff --git a/arch/ppc/qemu/methods.c b/arch/ppc/qemu/methods.c
index 8219cff9..fd993daa 100644
--- a/arch/ppc/qemu/methods.c
+++ b/arch/ppc/qemu/methods.c
@@ -25,6 +25,7 @@
#include "qemu/qemu.h"
#include "libopenbios/ofmem.h"
#include "arch/ppc/processor.h"
+#include "drivers/usb.h"
/************************************************************************/
/* RTAS (run-time abstraction services) */
@@ -112,6 +113,7 @@ DECLARE_NODE( ciface, 0, 0, "+/openprom/client-services" );
static void
ciface_quiesce( unsigned long args[], unsigned long ret[] )
{
+ usb_exit();
#if 0
unsigned long msr;
/* This seems to be the correct thing to do - but I'm not sure */
diff --git a/config/examples/ppc_config.xml b/config/examples/ppc_config.xml
index 621b65d9..4c14eb6c 100644
--- a/config/examples/ppc_config.xml
+++ b/config/examples/ppc_config.xml
@@ -80,3 +80,6 @@
<option name="CONFIG_DRIVER_ESCC" type="boolean" value="true"/>
<option name="CONFIG_DRIVER_FW_CFG" type="boolean" value="true"/>
<option name="CONFIG_FW_CFG_ADDR" type="integer" value="0xf0000510"/>
+ <option name="CONFIG_DRIVER_USB" type="boolean" value="true"/>
+ <option name="CONFIG_DEBUG_USB" type="boolean" value="false"/>
+ <option name="CONFIG_USB_HID" type="boolean" value="true"/>
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 7be334b1..3bebc029 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -36,4 +36,24 @@ config DEBUG_IDE
help
Debug IDE driver
+config DRIVER_USB
+ bool "USB Support"
+ default n
+ help
+ If you want to be able to use USB devices, enable this option.
+
+config DEBUG_USB
+ depends DRIVER_USB
+ bool "Debug USB driver"
+ default n
+ help
+ Debug USB driver
+
+config USB_HID
+ depends DRIVER_USB
+ bool "USB driver for HID devices"
+ default n
+ help
+ If you want to be able to use USB keyboard, enable this option.
+
endmenu
diff --git a/drivers/build.xml b/drivers/build.xml
index edec6b51..bd1abd35 100644
--- a/drivers/build.xml
+++ b/drivers/build.xml
@@ -22,6 +22,10 @@
<object source="pc_serial.c" condition="DRIVER_PC_SERIAL"/>
<object source="escc.c" condition="DRIVER_ESCC"/>
<object source="fw_cfg.c" condition="DRIVER_FW_CFG"/>
+ <object source="usb.c" condition="DRIVER_USB"/>
+ <object source="usbhid.c" condition="USB_HID"/>
+ <object source="usbohci.c" condition="DRIVER_USB"/>
+ <object source="usbohci_rh.c" condition="DRIVER_USB"/>
</library>
<dictionary name="openbios" target="forth">
diff --git a/drivers/pci.c b/drivers/pci.c
index 68eb892e..eca322d1 100644
--- a/drivers/pci.c
+++ b/drivers/pci.c
@@ -34,6 +34,9 @@
#include "cuda.h"
#include "macio.h"
#endif
+#ifdef CONFIG_DRIVER_USB
+#include "drivers/usb.h"
+#endif
#if defined (CONFIG_DEBUG_PCI)
# define PCI_DPRINTF(format, ...) printk(format, ## __VA_ARGS__)
@@ -925,6 +928,14 @@ int i82378_config_cb(const pci_config_t *config)
return 0;
}
+int usb_ohci_config_cb(const pci_config_t *config)
+{
+#ifdef CONFIG_DRIVER_USB
+ ob_usb_ohci_init(config->path, 0x80000000 | config->dev);
+#endif
+ return 0;
+}
+
static void ob_pci_add_properties(phandle_t phandle,
pci_addr addr, const pci_dev_t *pci_dev,
const pci_config_t *config, int num_bars)
diff --git a/drivers/pci.h b/drivers/pci.h
index ab8f1843..84a2b2cf 100644
--- a/drivers/pci.h
+++ b/drivers/pci.h
@@ -7,6 +7,7 @@
#define PCI_COMMAND 0x04
#define PCI_COMMAND_IO 0x01
#define PCI_COMMAND_MEMORY 0x02
+#define PCI_COMMAND_BUS_MASTER 0x04
#define PCI_STATUS 0x06 /* 16 bits */
#define PCI_STATUS_CAP_LIST 0x10 /* Support Capability List */
diff --git a/drivers/pci_database.c b/drivers/pci_database.c
index 0bd854a5..0070a78b 100644
--- a/drivers/pci_database.c
+++ b/drivers/pci_database.c
@@ -845,6 +845,24 @@ static const pci_subclass_t cpu_subclass[] = {
},
};
+static const pci_dev_t usb_devices[] = {
+#if defined(CONFIG_QEMU)
+ {
+ PCI_VENDOR_ID_APPLE, PCI_DEVICE_ID_APPLE_KEYL_USB,
+ "usb", "usb", NULL,
+ "pci106b,3f\0pciclass,0c0310\0",
+ 1, 0, 0,
+ NULL, NULL,
+ },
+#endif
+ {
+ 0xFFFF, 0xFFFF,
+ NULL, NULL, NULL, NULL,
+ -1, -1, -1,
+ NULL, NULL,
+ },
+};
+
static const pci_iface_t usb_iface[] = {
{
0x00, "UHCI USB controller", NULL,
@@ -852,7 +870,7 @@ static const pci_iface_t usb_iface[] = {
},
{
0x10, "OHCI USB controller", NULL,
- NULL, NULL, NULL,
+ usb_devices, &usb_ohci_config_cb, NULL,
},
{
0x20, "EHCI USB controller", NULL,
diff --git a/drivers/pci_database.h b/drivers/pci_database.h
index 16b385bf..7f053d80 100644
--- a/drivers/pci_database.h
+++ b/drivers/pci_database.h
@@ -38,6 +38,7 @@ extern int sabre_config_cb(const pci_config_t *config);
extern int bridge_config_cb(const pci_config_t *config);
extern int ebus_config_cb(const pci_config_t *config);
extern int i82378_config_cb(const pci_config_t *config);
+extern int usb_ohci_config_cb(const pci_config_t *config);
static inline int pci_compat_len(const pci_dev_t *dev)
{
diff --git a/drivers/usb.c b/drivers/usb.c
new file mode 100644
index 00000000..c6e37174
--- /dev/null
+++ b/drivers/usb.c
@@ -0,0 +1,587 @@
+/*
+ * 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);
+}
diff --git a/drivers/usb.h b/drivers/usb.h
new file mode 100644
index 00000000..2e23a137
--- /dev/null
+++ b/drivers/usb.h
@@ -0,0 +1,357 @@
+/*
+ * Driver for USB ported from CoreBoot
+ *
+ * Copyright (C) 2014 BALATON Zoltan
+ *
+ * This file was part of the libpayload project.
+ *
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef __USB_H
+#define __USB_H
+#include <drivers/pci.h>
+
+typedef enum { host_to_device = 0, device_to_host = 1 } dev_req_dir;
+typedef enum { standard_type = 0, class_type = 1, vendor_type =
+ 2, reserved_type = 3
+} dev_req_type;
+typedef enum { dev_recp = 0, iface_recp = 1, endp_recp = 2, other_recp = 3
+} dev_req_recp;
+
+typedef enum {
+ GET_STATUS = 0,
+ CLEAR_FEATURE = 1,
+ SET_FEATURE = 3,
+ SET_ADDRESS = 5,
+ GET_DESCRIPTOR = 6,
+ SET_DESCRIPTOR = 7,
+ GET_CONFIGURATION = 8,
+ SET_CONFIGURATION = 9,
+ GET_INTERFACE = 10,
+ SET_INTERFACE = 11,
+ SYNCH_FRAME = 12
+} bRequest_Codes;
+
+typedef enum {
+ ENDPOINT_HALT = 0,
+ DEVICE_REMOTE_WAKEUP = 1,
+ TEST_MODE = 2
+} feature_selectors;
+
+enum {
+ audio_device = 0x01,
+ comm_device = 0x02,
+ hid_device = 0x03,
+ physical_device = 0x05,
+ imaging_device = 0x06,
+ printer_device = 0x07,
+ msc_device = 0x08,
+ hub_device = 0x09,
+ cdc_device = 0x0a,
+ ccid_device = 0x0b,
+ security_device = 0x0d,
+ video_device = 0x0e,
+ healthcare_device = 0x0f,
+ diagnostic_device = 0xdc,
+ wireless_device = 0xe0,
+ misc_device = 0xef,
+};
+
+enum { hid_subclass_none = 0, hid_subclass_boot = 1 };
+
+enum {
+ hid_boot_proto_none = 0,
+ hid_boot_proto_keyboard = 1,
+ hid_boot_proto_mouse = 2
+};
+
+typedef struct {
+ union {
+ struct {
+#ifdef CONFIG_BIG_ENDIAN
+ dev_req_dir data_dir:1;
+ dev_req_type req_type:2;
+ dev_req_recp req_recp:5;
+#else
+ dev_req_recp req_recp:5;
+ dev_req_type req_type:2;
+ dev_req_dir data_dir:1;
+#endif
+ } __attribute__ ((packed));
+ unsigned char bmRequestType;
+ } __attribute__ ((packed));
+ unsigned char bRequest;
+ unsigned short wValue;
+ unsigned short wIndex;
+ unsigned short wLength;
+} __attribute__ ((packed)) dev_req_t;
+
+struct usbdev_hc;
+typedef struct usbdev_hc hci_t;
+
+struct usbdev;
+typedef struct usbdev usbdev_t;
+
+typedef enum { SETUP, IN, OUT } direction_t;
+typedef enum { CONTROL = 0, ISOCHRONOUS = 1, BULK = 2, INTERRUPT = 3
+} endpoint_type;
+
+typedef struct {
+ usbdev_t *dev;
+ int endpoint;
+ direction_t direction;
+ int toggle;
+ int maxpacketsize;
+ endpoint_type type;
+ int interval; /* expressed as binary logarithm of the number
+ of microframes (i.e. t = 125us * 2^interval) */
+} endpoint_t;
+
+enum { FULL_SPEED = 0, LOW_SPEED = 1, HIGH_SPEED = 2, SUPER_SPEED = 3 };
+
+struct usbdev {
+ hci_t *controller;
+ endpoint_t endpoints[32];
+ int num_endp;
+ int address; // usb address
+ int hub; // hub, device is attached to
+ int port; // port where device is attached
+ int speed; // 1: lowspeed, 0: fullspeed, 2: highspeed
+ u32 quirks; // quirks field. got to love usb
+ void *data;
+ u8 *descriptor;
+ u8 *configuration;
+ void (*init) (usbdev_t *dev);
+ void (*destroy) (usbdev_t *dev);
+ void (*poll) (usbdev_t *dev);
+};
+
+typedef enum { OHCI = 0, UHCI = 1, EHCI = 2, XHCI = 3} hc_type;
+
+struct usbdev_hc {
+ hci_t *next;
+ u32 reg_base;
+ hc_type type;
+ usbdev_t *devices[128]; // dev 0 is root hub, 127 is last addressable
+
+ /* start(): Resume operation. */
+ void (*start) (hci_t *controller);
+ /* stop(): Stop operation but keep controller initialized. */
+ void (*stop) (hci_t *controller);
+ /* reset(): Perform a controller reset. The controller needs to
+ be (re)initialized afterwards to work (again). */
+ void (*reset) (hci_t *controller);
+ /* init(): Initialize a (previously reset) controller
+ to a working state. */
+ void (*init) (hci_t *controller);
+ /* shutdown(): Stop operation, detach host controller and shutdown
+ this driver instance. After calling shutdown() any
+ other usage of this hci_t* is invalid. */
+ void (*shutdown) (hci_t *controller);
+
+ int (*bulk) (endpoint_t *ep, int size, u8 *data, int finalize);
+ int (*control) (usbdev_t *dev, direction_t pid, int dr_length,
+ void *devreq, int data_length, u8 *data);
+ void* (*create_intr_queue) (endpoint_t *ep, int reqsize, int reqcount, int reqtiming);
+ void (*destroy_intr_queue) (endpoint_t *ep, void *queue);
+ u8* (*poll_intr_queue) (void *queue);
+ void *instance;
+
+ /* set_address(): Tell the usb device its address and
+ return it. xHCI controllers want to
+ do this by themself. Also, the usbdev
+ structure has to be allocated and
+ initialized. */
+ int (*set_address) (hci_t *controller, int speed, int hubport, int hubaddr);
+ /* finish_device_config(): Another hook for xHCI,
+ returns 0 on success. */
+ int (*finish_device_config) (usbdev_t *dev);
+ /* destroy_device(): Finally, destroy all structures that
+ were allocated during set_address()
+ and finish_device_config(). */
+ void (*destroy_device) (hci_t *controller, int devaddr);
+};
+
+typedef struct {
+ unsigned char bDescLength;
+ unsigned char bDescriptorType;
+ unsigned char bNbrPorts;
+ union {
+ struct {
+#ifdef CONFIG_BIG_ENDIAN
+ unsigned long:8;
+ unsigned long arePortIndicatorsSupported:1;
+ unsigned long ttThinkTime:2;
+ unsigned long overcurrentProtectionMode:2;
+ unsigned long isCompoundDevice:1;
+ unsigned long logicalPowerSwitchingMode:2;
+#else
+ unsigned long logicalPowerSwitchingMode:2;
+ unsigned long isCompoundDevice:1;
+ unsigned long overcurrentProtectionMode:2;
+ unsigned long ttThinkTime:2;
+ unsigned long arePortIndicatorsSupported:1;
+ unsigned long:8;
+#endif
+ } __attribute__ ((packed));
+ unsigned short wHubCharacteristics;
+ } __attribute__ ((packed));
+ unsigned char bPowerOn2PwrGood;
+ unsigned char bHubContrCurrent;
+ char DeviceRemovable[];
+} __attribute__ ((packed)) hub_descriptor_t;
+
+typedef struct {
+ unsigned char bLength;
+ unsigned char bDescriptorType;
+ unsigned short bcdUSB;
+ unsigned char bDeviceClass;
+ unsigned char bDeviceSubClass;
+ unsigned char bDeviceProtocol;
+ unsigned char bMaxPacketSize0;
+ unsigned short idVendor;
+ unsigned short idProduct;
+ unsigned short bcdDevice;
+ unsigned char iManufacturer;
+ unsigned char iProduct;
+ unsigned char iSerialNumber;
+ unsigned char bNumConfigurations;
+} __attribute__ ((packed)) device_descriptor_t;
+
+typedef struct {
+ unsigned char bLength;
+ unsigned char bDescriptorType;
+ unsigned short wTotalLength;
+ unsigned char bNumInterfaces;
+ unsigned char bConfigurationValue;
+ unsigned char iConfiguration;
+ unsigned char bmAttributes;
+ unsigned char bMaxPower;
+} __attribute__ ((packed)) configuration_descriptor_t;
+
+typedef struct {
+ unsigned char bLength;
+ unsigned char bDescriptorType;
+ unsigned char bInterfaceNumber;
+ unsigned char bAlternateSetting;
+ unsigned char bNumEndpoints;
+ unsigned char bInterfaceClass;
+ unsigned char bInterfaceSubClass;
+ unsigned char bInterfaceProtocol;
+ unsigned char iInterface;
+} __attribute__ ((packed)) interface_descriptor_t;
+
+typedef struct {
+ unsigned char bLength;
+ unsigned char bDescriptorType;
+ unsigned char bEndpointAddress;
+ unsigned char bmAttributes;
+ unsigned short wMaxPacketSize;
+ unsigned char bInterval;
+} __attribute__ ((packed)) endpoint_descriptor_t;
+
+typedef struct {
+ unsigned char bLength;
+ unsigned char bDescriptorType;
+ unsigned short bcdHID;
+ unsigned char bCountryCode;
+ unsigned char bNumDescriptors;
+ unsigned char bReportDescriptorType;
+ unsigned short wReportDescriptorLength;
+} __attribute__ ((packed)) hid_descriptor_t;
+
+hci_t *new_controller (void);
+void detach_controller (hci_t *controller);
+void usb_poll (void);
+void init_device_entry (hci_t *controller, int num);
+
+void set_feature (usbdev_t *dev, int endp, int feature, int rtype);
+void get_status (usbdev_t *dev, int endp, int rtype, int len, void *data);
+void set_configuration (usbdev_t *dev);
+int clear_feature (usbdev_t *dev, int endp, int feature, int rtype);
+int clear_stall (endpoint_t *ep);
+
+void usb_hub_init (usbdev_t *dev);
+void usb_hid_init (usbdev_t *dev);
+void usb_msc_init (usbdev_t *dev);
+void usb_generic_init (usbdev_t *dev);
+
+u8 *get_descriptor (usbdev_t *dev, unsigned char bmRequestType,
+ int descType, int descIdx, int langID);
+
+static inline unsigned char
+gen_bmRequestType (dev_req_dir dir, dev_req_type type, dev_req_recp recp)
+{
+ return (dir << 7) | (type << 5) | recp;
+}
+
+/* default "set address" handler */
+int generic_set_address (hci_t *controller, int speed, int hubport, int hubaddr);
+
+void usb_detach_device(hci_t *controller, int devno);
+int usb_attach_device(hci_t *controller, int hubaddress, int port, int speed);
+
+u32 usb_quirk_check(u16 vendor, u16 device);
+int usb_interface_check(u16 vendor, u16 device);
+
+#define USB_QUIRK_MSC_FORCE_PROTO_SCSI (1 << 0)
+#define USB_QUIRK_MSC_FORCE_PROTO_ATAPI (1 << 1)
+#define USB_QUIRK_MSC_FORCE_PROTO_UFI (1 << 2)
+#define USB_QUIRK_MSC_FORCE_PROTO_RBC (1 << 3)
+#define USB_QUIRK_MSC_FORCE_TRANS_BBB (1 << 4)
+#define USB_QUIRK_MSC_FORCE_TRANS_CBI (1 << 5)
+#define USB_QUIRK_MSC_FORCE_TRANS_CBI_I (1 << 6)
+#define USB_QUIRK_MSC_NO_TEST_UNIT_READY (1 << 7)
+#define USB_QUIRK_MSC_SHORT_INQUIRY (1 << 8)
+#define USB_QUIRK_TEST (1 << 31)
+#define USB_QUIRK_NONE 0
+
+#ifdef CONFIG_DEBUG_USB
+#define usb_debug(fmt, args...) do { printk(fmt , ##args); } while (0)
+#else
+#define usb_debug(fmt, args...)
+#endif
+
+/**
+ * To be implemented by libpayload-client. It's called by the USB stack
+ * when a new USB device is found which isn't claimed by a built in driver,
+ * so the client has the chance to know about it.
+ *
+ * @param dev descriptor for the USB device
+ */
+void __attribute__((weak)) usb_generic_create (usbdev_t *dev);
+
+/**
+ * To be implemented by libpayload-client. It's called by the USB stack
+ * when it finds out that a USB device is removed which wasn't claimed by a
+ * built in driver.
+ *
+ * @param dev descriptor for the USB device
+ */
+void __attribute__((weak)) usb_generic_remove (usbdev_t *dev);
+
+#endif
diff --git a/drivers/usbhid.c b/drivers/usbhid.c
new file mode 100644
index 00000000..a423278a
--- /dev/null
+++ b/drivers/usbhid.c
@@ -0,0 +1,579 @@
+/*
+ * Driver for HID devices 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 "libopenbios/bindings.h"
+#include <libc/string.h>
+#include "libc/byteorder.h"
+#include "libc/vsprintf.h"
+#include "drivers/usb.h"
+#include "usb.h"
+
+DECLARE_UNNAMED_NODE(usb_kbd, INSTALL_OPEN, sizeof(int));
+
+static void
+keyboard_open(int *idx)
+{
+ RET(-1);
+}
+
+static void
+keyboard_close(int *idx)
+{
+}
+
+static void keyboard_read(void);
+
+NODE_METHODS( usb_kbd ) = {
+ { "open", keyboard_open },
+ { "close", keyboard_close },
+ { "read", keyboard_read },
+};
+
+#ifdef CONFIG_USB_DEBUG
+static const char *boot_protos[3] = { "(none)", "keyboard", "mouse" };
+#endif
+typedef enum { hid_proto_boot = 0, hid_proto_report = 1 } hid_proto;
+enum { GET_REPORT = 0x1, GET_IDLE = 0x2, GET_PROTOCOL = 0x3, SET_REPORT =
+ 0x9, SET_IDLE = 0xa, SET_PROTOCOL = 0xb
+};
+
+typedef union {
+ struct {
+ u8 modifiers;
+ u8 repeats;
+ u8 keys[6];
+ };
+ u8 buffer[8];
+} usb_hid_keyboard_event_t;
+
+typedef struct {
+ void* queue;
+ hid_descriptor_t *descriptor;
+
+ usb_hid_keyboard_event_t previous;
+ int lastkeypress;
+ int repeat_delay;
+} usbhid_inst_t;
+
+#define HID_INST(dev) ((usbhid_inst_t*)(dev)->data)
+
+static void
+usb_hid_destroy (usbdev_t *dev)
+{
+ if (HID_INST(dev)->queue) {
+ int i;
+ for (i = 0; i <= dev->num_endp; i++) {
+ if (dev->endpoints[i].endpoint == 0)
+ continue;
+ if (dev->endpoints[i].type != INTERRUPT)
+ continue;
+ if (dev->endpoints[i].direction != IN)
+ continue;
+ break;
+ }
+ dev->controller->destroy_intr_queue(
+ &dev->endpoints[i], HID_INST(dev)->queue);
+ HID_INST(dev)->queue = NULL;
+ }
+ free (dev->data);
+}
+
+/* keybuffer is global to all USB keyboards */
+static int keycount;
+#define KEYBOARD_BUFFER_SIZE 16
+static short keybuffer[KEYBOARD_BUFFER_SIZE];
+
+const char *countries[36][2] = {
+ { "unknown", "us" },
+ { "Arabic", "ae" },
+ { "Belgian", "be" },
+ { "Canadian-Bilingual", "ca" },
+ { "Canadian-French", "ca" },
+ { "Czech Republic", "cz" },
+ { "Danish", "dk" },
+ { "Finnish", "fi" },
+ { "French", "fr" },
+ { "German", "de" },
+ { "Greek", "gr" },
+ { "Hebrew", "il" },
+ { "Hungary", "hu" },
+ { "International (ISO)", "iso" },
+ { "Italian", "it" },
+ { "Japan (Katakana)", "jp" },
+ { "Korean", "us" },
+ { "Latin American", "us" },
+ { "Netherlands/Dutch", "nl" },
+ { "Norwegian", "no" },
+ { "Persian (Farsi)", "ir" },
+ { "Poland", "pl" },
+ { "Portuguese", "pt" },
+ { "Russia", "ru" },
+ { "Slovakia", "sl" },
+ { "Spanish", "es" },
+ { "Swedish", "se" },
+ { "Swiss/French", "ch" },
+ { "Swiss/German", "ch" },
+ { "Switzerland", "ch" },
+ { "Taiwan", "tw" },
+ { "Turkish-Q", "tr" },
+ { "UK", "uk" },
+ { "US", "us" },
+ { "Yugoslavia", "yu" },
+ { "Turkish-F", "tr" },
+ /* 36 - 255: Reserved */
+};
+
+
+
+struct layout_maps {
+ const char *country;
+ const short map[4][0x80];
+};
+
+static const struct layout_maps *map;
+
+#define KEY_BREAK 0x101 /* Not on PC KBD */
+#define KEY_DOWN 0x102 /* Down arrow key */
+#define KEY_UP 0x103 /* Up arrow key */
+#define KEY_LEFT 0x104 /* Left arrow key */
+#define KEY_RIGHT 0x105 /* Right arrow key */
+#define KEY_HOME 0x106 /* home key */
+#define KEY_BACKSPACE 0x107 /* not on pc */
+#define KEY_F0 0x108 /* function keys; 64 reserved */
+#define KEY_F(n) (KEY_F0 + (n))
+
+#define KEY_DC 0x14a /* delete character */
+#define KEY_IC 0x14b /* insert char or enter ins mode */
+
+#define KEY_NPAGE 0x152 /* next page */
+#define KEY_PPAGE 0x153 /* previous page */
+
+#define KEY_ENTER 0x157 /* enter or send (unreliable) */
+
+#define KEY_PRINT 0x15a /* print/copy */
+
+#define KEY_END 0x166 /* end key */
+
+static const struct layout_maps keyboard_layouts[] = {
+// #ifdef CONFIG_PC_KEYBOARD_LAYOUT_US
+{ .country = "us", .map = {
+ { /* No modifier */
+ -1, -1, -1, -1, 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ /* 0x10 */
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z', '1', '2',
+ /* 0x20 */
+ '3', '4', '5', '6', '7', '8', '9', '0',
+ '\n', '\e', '\b', '\t', ' ', '-', '=', '[',
+ /* 0x30 */
+ ']', '\\', -1, ';', '\'', '`', ',', '.',
+ '/', -1 /* CapsLk */, KEY_F(1), KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5), KEY_F(6),
+ /* 0x40 */
+ KEY_F(7), KEY_F(8), KEY_F(9), KEY_F(10), KEY_F(11), KEY_F(12), KEY_PRINT, -1 /* ScrLk */,
+ KEY_BREAK, KEY_IC, KEY_HOME, KEY_PPAGE, KEY_DC, KEY_END, KEY_NPAGE, KEY_RIGHT,
+ /* 50 */
+ KEY_LEFT, KEY_DOWN, KEY_UP, -1 /*NumLck*/, '/', '*', '-' /* = ? */, '+',
+ KEY_ENTER, KEY_END, KEY_DOWN, KEY_NPAGE, KEY_LEFT, -1, KEY_RIGHT, KEY_HOME,
+ /* 60 */
+ KEY_UP, KEY_PPAGE, -1, KEY_DC, -1 /* < > | */, -1 /* Win Key Right */, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 70 */
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* Shift modifier */
+ -1, -1, -1, -1, 'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+ /* 0x10 */
+ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@',
+ /* 0x20 */
+ '#', '$', '%', '^', '&', '*', '(', ')',
+ '\n', '\e', '\b', '\t', ' ', '_', '+', '{',
+ /* 0x30 */
+ '}', '|', -1, ':', '"', '~', '<', '>',
+ '?', -1 /* CapsLk */, KEY_F(1), KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5), KEY_F(6),
+ /* 0x40 */
+ KEY_F(7), KEY_F(8), KEY_F(9), KEY_F(10), KEY_F(11), KEY_F(12), KEY_PRINT, -1 /* ScrLk */,
+ KEY_BREAK, KEY_IC, KEY_HOME, KEY_PPAGE, KEY_DC, KEY_END, KEY_NPAGE, KEY_RIGHT,
+ /* 50 */
+ KEY_LEFT, KEY_DOWN, KEY_UP, -1 /*NumLck*/, '/', '*', '-' /* = ? */, '+',
+ KEY_ENTER, KEY_END, KEY_DOWN, KEY_NPAGE, KEY_LEFT, -1, KEY_RIGHT, KEY_HOME,
+ /* 60 */
+ KEY_UP, KEY_PPAGE, -1, KEY_DC, -1 /* < > | */, -1 /* Win Key Right */, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 70 */
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* Alt */
+ -1, -1, -1, -1, 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ /* 0x10 */
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z', '1', '2',
+ /* 0x20 */
+ '3', '4', '5', '6', '7', '8', '9', '0',
+ '\n', '\e', '\b', '\t', ' ', '-', '=', '[',
+ /* 0x30 */
+ ']', '\\', -1, ';', '\'', '`', ',', '.',
+ '/', -1 /* CapsLk */, KEY_F(1), KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5), KEY_F(6),
+ /* 0x40 */
+ KEY_F(7), KEY_F(8), KEY_F(9), KEY_F(10), KEY_F(11), KEY_F(12), KEY_PRINT, -1 /* ScrLk */,
+ KEY_BREAK, KEY_IC, KEY_HOME, KEY_PPAGE, KEY_DC, KEY_END, KEY_NPAGE, KEY_RIGHT,
+ /* 50 */
+ KEY_LEFT, KEY_DOWN, KEY_UP, -1 /*NumLck*/, '/', '*', '-' /* = ? */, '+',
+ KEY_ENTER, KEY_END, KEY_DOWN, KEY_NPAGE, KEY_LEFT, -1, KEY_RIGHT, KEY_HOME,
+ /* 60 */
+ KEY_UP, KEY_PPAGE, -1, KEY_DC, -1 /* < > | */, -1 /* Win Key Right */, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 70 */
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* Shift+Alt modifier */
+ -1, -1, -1, -1, 'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+ /* 0x10 */
+ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@',
+ /* 0x20 */
+ '#', '$', '%', '^', '&', '*', '(', ')',
+ '\n', '\e', '\b', '\t', ' ', '-', '=', '[',
+ /* 0x30 */
+ ']', '\\', -1, ':', '\'', '`', ',', '.',
+ '/', -1 /* CapsLk */, KEY_F(1), KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5), KEY_F(6),
+ /* 0x40 */
+ KEY_F(7), KEY_F(8), KEY_F(9), KEY_F(10), KEY_F(11), KEY_F(12), KEY_PRINT, -1 /* ScrLk */,
+ KEY_BREAK, KEY_IC, KEY_HOME, KEY_PPAGE, KEY_DC, KEY_END, KEY_NPAGE, KEY_RIGHT,
+ /* 50 */
+ KEY_LEFT, KEY_DOWN, KEY_UP, -1 /*NumLck*/, '/', '*', '-' /* = ? */, '+',
+ KEY_ENTER, KEY_END, KEY_DOWN, KEY_NPAGE, KEY_LEFT, -1, KEY_RIGHT, KEY_HOME,
+ /* 60 */
+ KEY_UP, KEY_PPAGE, -1, KEY_DC, -1 /* < > | */, -1 /* Win Key Right */, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 70 */
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ }
+}},
+//#endif
+};
+
+#define MOD_SHIFT (1 << 0)
+#define MOD_ALT (1 << 1)
+#define MOD_CTRL (1 << 2)
+
+static void usb_hid_keyboard_queue(int ch) {
+ /* ignore key presses if buffer full */
+ if (keycount < KEYBOARD_BUFFER_SIZE)
+ keybuffer[keycount++] = ch;
+}
+
+#define KEYBOARD_REPEAT_MS 30
+#define INITIAL_REPEAT_DELAY 10
+#define REPEAT_DELAY 2
+
+static void
+usb_hid_process_keyboard_event(usbhid_inst_t *const inst,
+ const usb_hid_keyboard_event_t *const current)
+{
+ const usb_hid_keyboard_event_t *const previous = &inst->previous;
+
+ int i, keypress = 0, modifiers = 0;
+
+ if (current->modifiers & 0x01) /* Left-Ctrl */ modifiers |= MOD_CTRL;
+ if (current->modifiers & 0x02) /* Left-Shift */ modifiers |= MOD_SHIFT;
+ if (current->modifiers & 0x04) /* Left-Alt */ modifiers |= MOD_ALT;
+ if (current->modifiers & 0x08) /* Left-GUI */ ;
+ if (current->modifiers & 0x10) /* Right-Ctrl */ modifiers |= MOD_CTRL;
+ if (current->modifiers & 0x20) /* Right-Shift */ modifiers |= MOD_SHIFT;
+ if (current->modifiers & 0x40) /* Right-AltGr */ modifiers |= MOD_ALT;
+ if (current->modifiers & 0x80) /* Right-GUI */ ;
+
+ /* Did the event change at all? */
+ if (inst->lastkeypress &&
+ !memcmp(current, previous, sizeof(*current))) {
+ /* No. Then it's a key repeat event. */
+ if (inst->repeat_delay) {
+ inst->repeat_delay--;
+ } else {
+ usb_hid_keyboard_queue(inst->lastkeypress);
+ inst->repeat_delay = REPEAT_DELAY;
+ }
+
+ return;
+ }
+
+ inst->lastkeypress = 0;
+
+ for (i=0; i<6; i++) {
+ int j;
+ int skip = 0;
+ // No more keys? skip
+ if (current->keys[i] == 0)
+ return;
+
+ for (j=0; j<6; j++) {
+ if (current->keys[i] == previous->keys[j]) {
+ skip = 1;
+ break;
+ }
+ }
+ if (skip)
+ continue;
+
+
+ /* Mask off MOD_CTRL */
+ keypress = map->map[modifiers & 0x03][current->keys[i]];
+
+ if (modifiers & MOD_CTRL) {
+ switch (keypress) {
+ case 'a' ... 'z':
+ keypress &= 0x1f;
+ break;
+ default:
+ continue;
+ }
+ }
+
+ if (keypress == -1) {
+ /* Debug: Print unknown keys */
+ usb_debug ("usbhid: <%x> %x [ %x %x %x %x %x %x ] %d\n",
+ current->modifiers, current->repeats,
+ current->keys[0], current->keys[1],
+ current->keys[2], current->keys[3],
+ current->keys[4], current->keys[5], i);
+
+ /* Unknown key? Try next one in the queue */
+ continue;
+ }
+
+ usb_hid_keyboard_queue(keypress);
+
+ /* Remember for authentic key repeat */
+ inst->lastkeypress = keypress;
+ inst->repeat_delay = INITIAL_REPEAT_DELAY;
+ }
+}
+
+static void
+usb_hid_poll (usbdev_t *dev)
+{
+ usb_hid_keyboard_event_t current;
+ const u8 *buf;
+
+ while ((buf=dev->controller->poll_intr_queue (HID_INST(dev)->queue))) {
+ memcpy(&current.buffer, buf, 8);
+ usb_hid_process_keyboard_event(HID_INST(dev), &current);
+ HID_INST(dev)->previous = current;
+ }
+}
+
+static void
+usb_hid_set_idle (usbdev_t *dev, interface_descriptor_t *interface, u16 duration)
+{
+ dev_req_t dr;
+ dr.data_dir = host_to_device;
+ dr.req_type = class_type;
+ dr.req_recp = iface_recp;
+ dr.bRequest = SET_IDLE;
+ dr.wValue = __cpu_to_le16((duration >> 2) << 8);
+ dr.wIndex = __cpu_to_le16(interface->bInterfaceNumber);
+ dr.wLength = 0;
+ dev->controller->control (dev, OUT, sizeof (dev_req_t), &dr, 0, 0);
+}
+
+static void
+usb_hid_set_protocol (usbdev_t *dev, interface_descriptor_t *interface, hid_proto proto)
+{
+ dev_req_t dr;
+ dr.data_dir = host_to_device;
+ dr.req_type = class_type;
+ dr.req_recp = iface_recp;
+ dr.bRequest = SET_PROTOCOL;
+ dr.wValue = __cpu_to_le16(proto);
+ dr.wIndex = __cpu_to_le16(interface->bInterfaceNumber);
+ dr.wLength = 0;
+ dev->controller->control (dev, OUT, sizeof (dev_req_t), &dr, 0, 0);
+}
+
+static int usb_hid_set_layout (const char *country)
+{
+ /* FIXME should be per keyboard */
+ int i;
+
+ for (i=0; i<sizeof(keyboard_layouts)/sizeof(keyboard_layouts[0]); i++) {
+ if (strncmp(keyboard_layouts[i].country, country,
+ strlen(keyboard_layouts[i].country)))
+ continue;
+
+ /* Found, changing keyboard layout */
+ map = &keyboard_layouts[i];
+ usb_debug(" Keyboard layout '%s'\n", map->country);
+ return 0;
+ }
+
+ usb_debug(" Keyboard layout '%s' not found, using '%s'\n",
+ country, map->country);
+
+ /* Nothing found, not changed */
+ return -1;
+}
+
+void
+usb_hid_init (usbdev_t *dev)
+{
+ configuration_descriptor_t *cd = (configuration_descriptor_t*)dev->configuration;
+ interface_descriptor_t *interface = (interface_descriptor_t*)(((char *) cd) + cd->bLength);
+
+ if (interface->bInterfaceSubClass == hid_subclass_boot) {
+ u8 countrycode = 0;
+ usb_debug (" supports boot interface..\n");
+ usb_debug (" it's a %s\n",
+ boot_protos[interface->bInterfaceProtocol]);
+ switch (interface->bInterfaceProtocol) {
+ case hid_boot_proto_keyboard:
+ dev->data = malloc (sizeof (usbhid_inst_t));
+ if (!dev->data) {
+ printk("Not enough memory for USB HID device.\n");
+ return;
+ }
+ memset(&HID_INST(dev)->previous, 0x00,
+ sizeof(HID_INST(dev)->previous));
+ usb_debug (" configuring...\n");
+ usb_hid_set_protocol(dev, interface, hid_proto_boot);
+ usb_hid_set_idle(dev, interface, KEYBOARD_REPEAT_MS);
+ usb_debug (" activating...\n");
+#if 0
+ HID_INST (dev)->descriptor =
+ (hid_descriptor_t *)
+ get_descriptor(dev, gen_bmRequestType
+ (device_to_host, standard_type, iface_recp),
+ 0x21, 0, 0);
+ countrycode = HID_INST(dev)->descriptor->bCountryCode;
+#endif
+ /* 35 countries defined: */
+ if (countrycode > 35)
+ countrycode = 0;
+ usb_debug (" Keyboard has %s layout (country code %02x)\n",
+ countries[countrycode][0], countrycode);
+
+ /* Set keyboard layout accordingly */
+ usb_hid_set_layout(countries[countrycode][1]);
+
+ // only add here, because we only support boot-keyboard HID devices
+ dev->destroy = usb_hid_destroy;
+ dev->poll = usb_hid_poll;
+ int i;
+ for (i = 0; i <= dev->num_endp; i++) {
+ if (dev->endpoints[i].endpoint == 0)
+ continue;
+ if (dev->endpoints[i].type != INTERRUPT)
+ continue;
+ if (dev->endpoints[i].direction != IN)
+ continue;
+ break;
+ }
+ usb_debug (" found endpoint %x for interrupt-in\n", i);
+ /* 20 buffers of 8 bytes, for every 10 msecs */
+ HID_INST(dev)->queue = dev->controller->create_intr_queue (&dev->endpoints[i], 8, 20, 10);
+ keycount = 0;
+ usb_debug (" configuration done.\n");
+ break;
+ default:
+ usb_debug("NOTICE: HID interface protocol %d%s not supported.\n",
+ interface->bInterfaceProtocol,
+ (interface->bInterfaceProtocol == hid_boot_proto_mouse ?
+ " (USB mouse)" : ""));
+ break;
+ }
+ }
+}
+
+static int usbhid_havechar (void)
+{
+ return (keycount != 0);
+}
+
+static int usbhid_getchar (void)
+{
+ short ret;
+
+ if (keycount == 0)
+ return 0;
+ ret = keybuffer[0];
+ memmove(keybuffer, keybuffer + 1, --keycount);
+
+ return (int)ret;
+}
+
+/* ( addr len -- actual ) */
+static void keyboard_read(void)
+{
+ char *addr;
+ int len, key, i;
+
+ usb_poll();
+ len=POP();
+ addr=(char *)cell2pointer(POP());
+
+ for (i = 0; i < len; i++) {
+ if (!usbhid_havechar())
+ break;
+ key = usbhid_getchar();
+ *addr++ = (char)key;
+ }
+ PUSH(i);
+}
+
+void ob_usb_hid_add_keyboard(const char *path)
+{
+ char name[128];
+ phandle_t aliases;
+
+ snprintf(name, sizeof(name), "%s/keyboard", path);
+ usb_debug("Found keyboard at %s\n", name);
+ REGISTER_NAMED_NODE(usb_kbd, name);
+
+ push_str(name);
+ fword("find-device");
+
+ push_str("keyboard");
+ fword("device-type");
+
+ aliases = find_dev("/aliases");
+ set_property(aliases, "adb-keyboard", name, strlen(name) + 1);
+}
diff --git a/drivers/usbohci.c b/drivers/usbohci.c
new file mode 100644
index 00000000..774164b0
--- /dev/null
+++ b/drivers/usbohci.c
@@ -0,0 +1,926 @@
+/*
+ * Driver for USB OHCI ported from CoreBoot
+ *
+ * Copyright (C) 2014 BALATON Zoltan
+ *
+ * This file was part of the libpayload project.
+ *
+ * Copyright (C) 2010 Patrick Georgi
+ *
+ * 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.
+ */
+
+//#define USB_DEBUG_ED
+
+#include "config.h"
+#include <asm/io.h>
+#include <libopenbios/ofmem.h>
+#include "timer.h"
+#include "drivers/pci.h"
+#include "pci.h"
+#include <drivers/usb.h>
+#include "usbohci_private.h"
+#include "usbohci.h"
+
+static void ohci_start (hci_t *controller);
+static void ohci_stop (hci_t *controller);
+static void ohci_reset (hci_t *controller);
+static void ohci_shutdown (hci_t *controller);
+static int ohci_bulk (endpoint_t *ep, int size, u8 *data, int finalize);
+static int ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq,
+ int dalen, u8 *data);
+static void* ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming);
+static void ohci_destroy_intr_queue (endpoint_t *ep, void *queue);
+static u8* ohci_poll_intr_queue (void *queue);
+static void ohci_process_done_queue(ohci_t *ohci, int spew_debug);
+
+#ifdef USB_DEBUG_ED
+static void
+dump_td (td_t *cur)
+{
+ usb_debug("+---------------------------------------------------+\n");
+ if (((__le32_to_cpu(cur->config) & (3UL << 19)) >> 19) == 0)
+ usb_debug("|..[SETUP]..........................................|\n");
+ else if (((__le32_to_cpu(cur->config) & (3UL << 8)) >> 8) == 2)
+ usb_debug("|..[IN].............................................|\n");
+ else if (((__le32_to_cpu(cur->config) & (3UL << 8)) >> 8) == 1)
+ usb_debug("|..[OUT]............................................|\n");
+ else
+ usb_debug("|..[]...............................................|\n");
+ usb_debug("|:|============ OHCI TD at [0x%08lx] ==========|:|\n", virt_to_phys(cur));
+ usb_debug("|:| ERRORS = [%ld] | CONFIG = [0x%08x] | |:|\n",
+ 3 - ((__le32_to_cpu(cur->config) & (3UL << 26)) >> 26), __le32_to_cpu(cur->config));
+ usb_debug("|:+-----------------------------------------------+:|\n");
+ usb_debug("|:| C | Condition Code | [%02ld] |:|\n",
+ (__le32_to_cpu(cur->config) & (0xFUL << 28)) >> 28);
+ usb_debug("|:| O | Direction/PID | [%ld] |:|\n",
+ (__le32_to_cpu(cur->config) & (3UL << 19)) >> 19);
+ usb_debug("|:| N | Buffer Rounding | [%ld] |:|\n",
+ (__le32_to_cpu(cur->config) & (1UL << 18)) >> 18);
+ usb_debug("|:| F | Delay Intterrupt | [%ld] |:|\n",
+ (__le32_to_cpu(cur->config) & (7UL << 21)) >> 21);
+ usb_debug("|:| I | Data Toggle | [%ld] |:|\n",
+ (__le32_to_cpu(cur->config) & (3UL << 24)) >> 24);
+ usb_debug("|:| G | Error Count | [%ld] |:|\n",
+ (__le32_to_cpu(cur->config) & (3UL << 26)) >> 26);
+ usb_debug("|:+-----------------------------------------------+:|\n");
+ usb_debug("|:| Current Buffer Pointer [0x%08x] |:|\n", __le32_to_cpu(cur->current_buffer_pointer));
+ usb_debug("|:+-----------------------------------------------+:|\n");
+ usb_debug("|:| Next TD [0x%08x] |:|\n", __le32_to_cpu(cur->next_td));
+ usb_debug("|:+-----------------------------------------------+:|\n");
+ usb_debug("|:| Current Buffer End [0x%08x] |:|\n", __le32_to_cpu(cur->buffer_end));
+ usb_debug("|:|-----------------------------------------------|:|\n");
+ usb_debug("|...................................................|\n");
+ usb_debug("+---------------------------------------------------+\n");
+}
+
+static void
+dump_ed (ed_t *cur)
+{
+ td_t *tmp_td = NULL;
+ usb_debug("+===================================================+\n");
+ usb_debug("| ############# OHCI ED at [0x%08lx] ########### |\n", virt_to_phys(cur));
+ usb_debug("+---------------------------------------------------+\n");
+ usb_debug("| Next Endpoint Descriptor [0x%08lx] |\n", __le32_to_cpu(cur->next_ed) & ~0xFUL);
+ usb_debug("+---------------------------------------------------+\n");
+ usb_debug("| | @ 0x%08x : |\n", __le32_to_cpu(cur->config));
+ usb_debug("| C | Maximum Packet Length | [%04ld] |\n",
+ ((__le32_to_cpu(cur->config) & (0x3fffUL << 16)) >> 16));
+ usb_debug("| O | Function Address | [%04d] |\n",
+ __le32_to_cpu(cur->config) & 0x7F);
+ usb_debug("| N | Endpoint Number | [%02ld] |\n",
+ (__le32_to_cpu(cur->config) & (0xFUL << 7)) >> 7);
+ usb_debug("| F | Endpoint Direction | [%ld] |\n",
+ ((__le32_to_cpu(cur->config) & (3UL << 11)) >> 11));
+ usb_debug("| I | Endpoint Speed | [%ld] |\n",
+ ((__le32_to_cpu(cur->config) & (1UL << 13)) >> 13));
+ usb_debug("| G | Skip | [%ld] |\n",
+ ((__le32_to_cpu(cur->config) & (1UL << 14)) >> 14));
+ usb_debug("| | Format | [%ld] |\n",
+ ((__le32_to_cpu(cur->config) & (1UL << 15)) >> 15));
+ usb_debug("+---------------------------------------------------+\n");
+ usb_debug("| TD Queue Tail Pointer [0x%08lx] |\n",
+ __le32_to_cpu(cur->tail_pointer) & ~0xFUL);
+ usb_debug("+---------------------------------------------------+\n");
+ usb_debug("| TD Queue Head Pointer [0x%08lx] |\n",
+ __le32_to_cpu(cur->head_pointer) & ~0xFUL);
+ usb_debug("| CarryToggleBit [%d] Halted [%d] |\n",
+ (u16)(__le32_to_cpu(cur->head_pointer) & 0x2UL)>>1, (u16)(__le32_to_cpu(cur->head_pointer) & 0x1UL));
+
+ tmp_td = (td_t *)phys_to_virt((__le32_to_cpu(cur->head_pointer) & ~0xFUL));
+ if ((__le32_to_cpu(cur->head_pointer) & ~0xFUL) != (__le32_to_cpu(cur->tail_pointer) & ~0xFUL)) {
+ usb_debug("|:::::::::::::::::: OHCI TD CHAIN ::::::::::::::::::|\n");
+ while (virt_to_phys(tmp_td) != (__le32_to_cpu(cur->tail_pointer) & ~0xFUL))
+ {
+ dump_td(tmp_td);
+ tmp_td = (td_t *)phys_to_virt((__le32_to_cpu(tmp_td->next_td) & ~0xFUL));
+ }
+ usb_debug("|:::::::::::::::: EOF OHCI TD CHAIN ::::::::::::::::|\n");
+ usb_debug("+---------------------------------------------------+\n");
+ } else {
+ usb_debug("+---------------------------------------------------+\n");
+ }
+}
+#endif
+
+static void
+ohci_reset (hci_t *controller)
+{
+ if (controller == NULL)
+ return;
+
+ OHCI_INST(controller)->opreg->HcCommandStatus = __cpu_to_le32(HostControllerReset);
+ mdelay(2); /* wait 2ms */
+ OHCI_INST(controller)->opreg->HcControl = 0;
+ mdelay(10); /* wait 10ms */
+}
+
+static void
+ohci_reinit (hci_t *controller)
+{
+}
+
+hci_t *
+ohci_init (void *bar)
+{
+ int i;
+
+ hci_t *controller = new_controller ();
+
+ if (!controller) {
+ printk("Could not create USB controller instance.\n");
+ return NULL;
+ }
+
+ controller->instance = malloc (sizeof (ohci_t));
+ if(!controller->instance) {
+ printk("Not enough memory creating USB controller instance.\n");
+ return NULL;
+ }
+
+ controller->type = OHCI;
+
+ controller->start = ohci_start;
+ controller->stop = ohci_stop;
+ controller->reset = ohci_reset;
+ controller->init = ohci_reinit;
+ controller->shutdown = ohci_shutdown;
+ controller->bulk = ohci_bulk;
+ controller->control = ohci_control;
+ controller->set_address = generic_set_address;
+ controller->finish_device_config = NULL;
+ controller->destroy_device = NULL;
+ controller->create_intr_queue = ohci_create_intr_queue;
+ controller->destroy_intr_queue = ohci_destroy_intr_queue;
+ controller->poll_intr_queue = ohci_poll_intr_queue;
+ for (i = 0; i < 128; i++) {
+ controller->devices[i] = 0;
+ }
+ init_device_entry (controller, 0);
+ OHCI_INST (controller)->roothub = controller->devices[0];
+
+ controller->reg_base = (u32)(unsigned long)bar;
+ OHCI_INST (controller)->opreg = (opreg_t*)phys_to_virt(controller->reg_base);
+ usb_debug("OHCI Version %x.%x\n",
+ (READ_OPREG(OHCI_INST(controller), HcRevision) >> 4) & 0xf,
+ READ_OPREG(OHCI_INST(controller), HcRevision) & 0xf);
+
+ if ((READ_OPREG(OHCI_INST(controller), HcControl) & HostControllerFunctionalStateMask) == USBReset) {
+ /* cold boot */
+ OHCI_INST (controller)->opreg->HcControl &= __cpu_to_le32(~RemoteWakeupConnected);
+ OHCI_INST (controller)->opreg->HcFmInterval =
+ __cpu_to_le32((11999 * FrameInterval) | ((((11999 - 210)*6)/7) * FSLargestDataPacket));
+ /* TODO: right value for PowerOnToPowerGoodTime ? */
+ OHCI_INST (controller)->opreg->HcRhDescriptorA =
+ __cpu_to_le32(NoPowerSwitching | NoOverCurrentProtection | (10 * PowerOnToPowerGoodTime));
+ OHCI_INST (controller)->opreg->HcRhDescriptorB = __cpu_to_le32(0 * DeviceRemovable);
+ udelay(100); /* TODO: reset asserting according to USB spec */
+ } else if ((READ_OPREG(OHCI_INST(controller), HcControl) & HostControllerFunctionalStateMask) != USBOperational) {
+ OHCI_INST (controller)->opreg->HcControl =
+ __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcControl) & ~HostControllerFunctionalStateMask)
+ | USBResume);
+ udelay(100); /* TODO: resume time according to USB spec */
+ }
+ int interval = OHCI_INST (controller)->opreg->HcFmInterval;
+
+ OHCI_INST (controller)->opreg->HcCommandStatus = __cpu_to_le32(HostControllerReset);
+ udelay (10); /* at most 10us for reset to complete. State must be set to Operational within 2ms (5.1.1.4) */
+ OHCI_INST (controller)->opreg->HcFmInterval = interval;
+ ofmem_posix_memalign((void **)&(OHCI_INST (controller)->hcca), 256, 256);
+ memset((void*)OHCI_INST (controller)->hcca, 0, 256);
+
+ usb_debug("HCCA addr %p\n", OHCI_INST(controller)->hcca);
+ /* Initialize interrupt table. */
+ u32 *const intr_table = OHCI_INST(controller)->hcca->HccaInterruptTable;
+ ed_t *const periodic_ed;
+ ofmem_posix_memalign((void **)&periodic_ed, sizeof(ed_t), sizeof(ed_t));
+ memset((void *)periodic_ed, 0, sizeof(*periodic_ed));
+ for (i = 0; i < 32; ++i)
+ intr_table[i] = __cpu_to_le32(virt_to_phys(periodic_ed));
+ OHCI_INST (controller)->periodic_ed = periodic_ed;
+
+ OHCI_INST (controller)->opreg->HcHCCA = __cpu_to_le32(virt_to_phys(OHCI_INST(controller)->hcca));
+ /* Make sure periodic schedule is enabled. */
+ OHCI_INST (controller)->opreg->HcControl |= __cpu_to_le32(PeriodicListEnable);
+ OHCI_INST (controller)->opreg->HcControl &= __cpu_to_le32(~IsochronousEnable); // unused by this driver
+ // disable everything, contrary to what OHCI spec says in 5.1.1.4, as we don't need IRQs
+ OHCI_INST (controller)->opreg->HcInterruptEnable = __cpu_to_le32(1<<31);
+ OHCI_INST (controller)->opreg->HcInterruptDisable = __cpu_to_le32(~(1<<31));
+ OHCI_INST (controller)->opreg->HcInterruptStatus = __cpu_to_le32(~0);
+ OHCI_INST (controller)->opreg->HcPeriodicStart =
+ __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcFmInterval) & FrameIntervalMask) / 10 * 9);
+ OHCI_INST (controller)->opreg->HcControl = __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcControl)
+ & ~HostControllerFunctionalStateMask) | USBOperational);
+
+ mdelay(100);
+
+ controller->devices[0]->controller = controller;
+ controller->devices[0]->init = ohci_rh_init;
+ controller->devices[0]->init (controller->devices[0]);
+ return controller;
+}
+
+hci_t *
+ohci_pci_init (pci_addr addr)
+{
+ u32 reg_base;
+ uint16_t cmd;
+
+ cmd = pci_config_read16(addr, PCI_COMMAND);
+ cmd |= PCI_COMMAND_BUS_MASTER;
+ pci_config_write16(addr, PCI_COMMAND, cmd);
+
+ /* regarding OHCI spec, Appendix A, BAR_OHCI register description, Table A-4
+ * BASE ADDRESS only [31-12] bits. All other usually 0, but not all.
+ * OHCI mandates MMIO, so bit 0 is clear */
+ reg_base = pci_config_read32 (addr, PCI_BASE_ADDR_0) & 0xfffff000;
+
+ return ohci_init((void *)(unsigned long)reg_base);
+}
+
+static void
+ohci_shutdown (hci_t *controller)
+{
+ if (controller == 0)
+ return;
+ detach_controller (controller);
+ ohci_stop(controller);
+ OHCI_INST (controller)->roothub->destroy (OHCI_INST (controller)->
+ roothub);
+ controller->reset (controller);
+ free ((void *)OHCI_INST (controller)->periodic_ed);
+ free (OHCI_INST (controller));
+ free (controller);
+}
+
+static void
+ohci_start (hci_t *controller)
+{
+// TODO: turn on all operation of OHCI, but assume that it's initialized.
+}
+
+static void
+ohci_stop (hci_t *controller)
+{
+// TODO: turn off all operation of OHCI
+}
+
+static int
+wait_for_ed(usbdev_t *dev, ed_t *head, int pages)
+{
+ usb_debug("Waiting for %d pages on dev %p with head %p\n", pages, dev, head);
+ /* wait for results */
+ /* TOTEST: how long to wait?
+ * give 2s per TD (2 pages) plus another 2s for now
+ */
+ int timeout = pages*1000 + 2000;
+ while (((__le32_to_cpu(head->head_pointer) & ~3) != __le32_to_cpu(head->tail_pointer)) &&
+ !(__le32_to_cpu(head->head_pointer) & 1) &&
+ ((__le32_to_cpu((((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3)))->config)
+ & TD_CC_MASK) >= TD_CC_NOACCESS) && timeout--) {
+ /* don't log every ms */
+ if (!(timeout % 100))
+ usb_debug("intst: %x; ctrl: %x; cmdst: %x; head: %x -> %x, tail: %x, condition: %x\n",
+ READ_OPREG(OHCI_INST(dev->controller), HcInterruptStatus),
+ READ_OPREG(OHCI_INST(dev->controller), HcControl),
+ READ_OPREG(OHCI_INST(dev->controller), HcCommandStatus),
+ __le32_to_cpu(head->head_pointer),
+ __le32_to_cpu(((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3))->next_td),
+ __le32_to_cpu(head->tail_pointer),
+ (__le32_to_cpu(((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3))->config) & TD_CC_MASK) >> TD_CC_SHIFT);
+ mdelay(1);
+ }
+ if (timeout < 0)
+ usb_debug("Error: ohci: endpoint "
+ "descriptor processing timed out.\n");
+ /* Clear the done queue. */
+ ohci_process_done_queue(OHCI_INST(dev->controller), 1);
+
+ if (__le32_to_cpu(head->head_pointer) & 1) {
+ usb_debug("HALTED!\n");
+ return 1;
+ }
+ return 0;
+}
+
+static void
+ohci_free_ed (ed_t *const head)
+{
+ /* In case the transfer canceled, we have to free unprocessed TDs. */
+ while ((__le32_to_cpu(head->head_pointer) & ~0x3) != __le32_to_cpu(head->tail_pointer)) {
+ /* Save current TD pointer. */
+ td_t *const cur_td =
+ (td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~0x3);
+ /* Advance head pointer. */
+ head->head_pointer = cur_td->next_td;
+ /* Free current TD. */
+ free((void *)cur_td);
+ }
+
+ /* Always free the dummy TD */
+ if ((__le32_to_cpu(head->head_pointer) & ~0x3) == __le32_to_cpu(head->tail_pointer))
+ free(phys_to_virt(__le32_to_cpu(head->head_pointer) & ~0x3));
+ /* and the ED. */
+ free((void *)head);
+}
+
+static int
+ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen,
+ unsigned char *data)
+{
+ td_t *cur;
+
+ // pages are specified as 4K in OHCI, so don't use getpagesize()
+ int first_page = (unsigned long)data / 4096;
+ int last_page = (unsigned long)(data+dalen-1)/4096;
+ if (last_page < first_page) last_page = first_page;
+ int pages = (dalen==0)?0:(last_page - first_page + 1);
+
+ /* First TD. */
+ td_t *const first_td;
+ ofmem_posix_memalign((void **)&first_td, sizeof(td_t), sizeof(td_t));
+ memset((void *)first_td, 0, sizeof(*first_td));
+ cur = first_td;
+
+ cur->config = __cpu_to_le32(TD_DIRECTION_SETUP |
+ TD_DELAY_INTERRUPT_NOINTR |
+ TD_TOGGLE_FROM_TD |
+ TD_TOGGLE_DATA0 |
+ TD_CC_NOACCESS);
+ cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(devreq));
+ cur->buffer_end = __cpu_to_le32(virt_to_phys((char *)devreq + drlen - 1));
+
+ while (pages > 0) {
+ /* One more TD. */
+ td_t *const next;
+ ofmem_posix_memalign((void **)&next, sizeof(td_t), sizeof(td_t));
+ memset((void *)next, 0, sizeof(*next));
+ /* Linked to the previous. */
+ cur->next_td = __cpu_to_le32(virt_to_phys(next));
+ /* Advance to the new TD. */
+ cur = next;
+
+ cur->config = __cpu_to_le32((dir == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) |
+ TD_DELAY_INTERRUPT_NOINTR |
+ TD_TOGGLE_FROM_ED |
+ TD_CC_NOACCESS);
+ cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(data));
+ pages--;
+ int consumed = (4096 - ((unsigned long)data % 4096));
+ if (consumed >= dalen) {
+ // end of data is within same page
+ cur->buffer_end = __cpu_to_le32(virt_to_phys(data + dalen - 1));
+ dalen = 0;
+ /* assert(pages == 0); */
+ } else {
+ dalen -= consumed;
+ data += consumed;
+ pages--;
+ int second_page_size = dalen;
+ if (dalen > 4096) {
+ second_page_size = 4096;
+ }
+ cur->buffer_end = __cpu_to_le32(virt_to_phys(data + second_page_size - 1));
+ dalen -= second_page_size;
+ data += second_page_size;
+ }
+ }
+
+ /* One more TD. */
+ td_t *const next_td;
+ ofmem_posix_memalign((void **)&next_td, sizeof(td_t), sizeof(td_t));
+ memset((void *)next_td, 0, sizeof(*next_td));
+ /* Linked to the previous. */
+ cur->next_td = __cpu_to_le32(virt_to_phys(next_td));
+ /* Advance to the new TD. */
+ cur = next_td;
+ cur->config = __cpu_to_le32((dir == IN ? TD_DIRECTION_OUT : TD_DIRECTION_IN) |
+ TD_DELAY_INTERRUPT_ZERO | /* Write done head after this TD. */
+ TD_TOGGLE_FROM_TD |
+ TD_TOGGLE_DATA1 |
+ TD_CC_NOACCESS);
+ cur->current_buffer_pointer = 0;
+ cur->buffer_end = 0;
+
+ /* Final dummy TD. */
+ td_t *const final_td;
+ ofmem_posix_memalign((void **)&final_td, sizeof(td_t), sizeof(td_t));
+ memset((void *)final_td, 0, sizeof(*final_td));
+ /* Linked to the previous. */
+ cur->next_td = __cpu_to_le32(virt_to_phys(final_td));
+
+ /* Data structures */
+ ed_t *head;
+ ofmem_posix_memalign((void **)&head, sizeof(ed_t), sizeof(ed_t));
+ memset((void*)head, 0, sizeof(*head));
+ head->config = __cpu_to_le32((dev->address << ED_FUNC_SHIFT) |
+ (0 << ED_EP_SHIFT) |
+ (OHCI_FROM_TD << ED_DIR_SHIFT) |
+ (dev->speed?ED_LOWSPEED:0) |
+ (dev->endpoints[0].maxpacketsize << ED_MPS_SHIFT));
+ head->tail_pointer = __cpu_to_le32(virt_to_phys(final_td));
+ head->head_pointer = __cpu_to_le32(virt_to_phys(first_td));
+
+ usb_debug("ohci_control(): doing transfer with %x. first_td at %x\n",
+ __le32_to_cpu(head->config) & ED_FUNC_MASK, __le32_to_cpu(head->head_pointer));
+#ifdef USB_DEBUG_ED
+ dump_ed(head);
+#endif
+
+ /* activate schedule */
+ OHCI_INST(dev->controller)->opreg->HcControlHeadED = __cpu_to_le32(virt_to_phys(head));
+ OHCI_INST(dev->controller)->opreg->HcControl |= __cpu_to_le32(ControlListEnable);
+ OHCI_INST(dev->controller)->opreg->HcCommandStatus = __cpu_to_le32(ControlListFilled);
+
+ int failure = wait_for_ed(dev, head,
+ (dalen==0)?0:(last_page - first_page + 1));
+ /* Wait some frames before and one after disabling list access. */
+ mdelay(4);
+ OHCI_INST(dev->controller)->opreg->HcControl &= __cpu_to_le32(~ControlListEnable);
+ mdelay(1);
+
+ /* free memory */
+ ohci_free_ed(head);
+
+ return failure;
+}
+
+/* finalize == 1: if data is of packet aligned size, add a zero length packet */
+static int
+ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize)
+{
+ int i;
+ usb_debug("bulk: %x bytes from %p, finalize: %x, maxpacketsize: %x\n", dalen, data, finalize, ep->maxpacketsize);
+
+ td_t *cur, *next;
+
+ // pages are specified as 4K in OHCI, so don't use getpagesize()
+ int first_page = (unsigned long)data / 4096;
+ int last_page = (unsigned long)(data+dalen-1)/4096;
+ if (last_page < first_page) last_page = first_page;
+ int pages = (dalen==0)?0:(last_page - first_page + 1);
+ int td_count = (pages+1)/2;
+
+ if (finalize && ((dalen % ep->maxpacketsize) == 0)) {
+ td_count++;
+ }
+
+ /* First TD. */
+ td_t *const first_td;
+ ofmem_posix_memalign((void **)&first_td, sizeof(td_t), sizeof(td_t));
+ memset((void *)first_td, 0, sizeof(*first_td));
+ cur = next = first_td;
+
+ for (i = 0; i < td_count; ++i) {
+ /* Advance to next TD. */
+ cur = next;
+ cur->config = __cpu_to_le32((ep->direction == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) |
+ TD_DELAY_INTERRUPT_NOINTR |
+ TD_TOGGLE_FROM_ED |
+ TD_CC_NOACCESS);
+ cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(data));
+ pages--;
+ if (dalen == 0) {
+ /* magic TD for empty packet transfer */
+ cur->current_buffer_pointer = 0;
+ cur->buffer_end = 0;
+ /* assert((pages == 0) && finalize); */
+ }
+ int consumed = (4096 - ((unsigned long)data % 4096));
+ if (consumed >= dalen) {
+ // end of data is within same page
+ cur->buffer_end = __cpu_to_le32(virt_to_phys(data + dalen - 1));
+ dalen = 0;
+ /* assert(pages == finalize); */
+ } else {
+ dalen -= consumed;
+ data += consumed;
+ pages--;
+ int second_page_size = dalen;
+ if (dalen > 4096) {
+ second_page_size = 4096;
+ }
+ cur->buffer_end = __cpu_to_le32(virt_to_phys(data + second_page_size - 1));
+ dalen -= second_page_size;
+ data += second_page_size;
+ }
+ /* One more TD. */
+ ofmem_posix_memalign((void **)&next, sizeof(td_t), sizeof(td_t));
+ memset((void *)next, 0, sizeof(*next));
+ /* Linked to the previous. */
+ cur->next_td = __cpu_to_le32(virt_to_phys(next));
+ }
+
+ /* Write done head after last TD. */
+ cur->config &= __cpu_to_le32(~TD_DELAY_INTERRUPT_MASK);
+ /* Advance to final, dummy TD. */
+ cur = next;
+
+ /* Data structures */
+ ed_t *head;
+ ofmem_posix_memalign((void **)&head, sizeof(ed_t), sizeof(ed_t));
+ memset((void*)head, 0, sizeof(*head));
+ head->config = __cpu_to_le32((ep->dev->address << ED_FUNC_SHIFT) |
+ ((ep->endpoint & 0xf) << ED_EP_SHIFT) |
+ (((ep->direction==IN)?OHCI_IN:OHCI_OUT) << ED_DIR_SHIFT) |
+ (ep->dev->speed?ED_LOWSPEED:0) |
+ (ep->maxpacketsize << ED_MPS_SHIFT));
+ head->tail_pointer = __cpu_to_le32(virt_to_phys(cur));
+ head->head_pointer = __cpu_to_le32(virt_to_phys(first_td) | (ep->toggle?ED_TOGGLE:0));
+
+ usb_debug("doing bulk transfer with %x(%x). first_td at %lx, last %lx\n",
+ __le32_to_cpu(head->config) & ED_FUNC_MASK,
+ (__le32_to_cpu(head->config) & ED_EP_MASK) >> ED_EP_SHIFT,
+ virt_to_phys(first_td), virt_to_phys(cur));
+
+ /* activate schedule */
+ OHCI_INST(ep->dev->controller)->opreg->HcBulkHeadED = __cpu_to_le32(virt_to_phys(head));
+ OHCI_INST(ep->dev->controller)->opreg->HcControl |= __cpu_to_le32(BulkListEnable);
+ OHCI_INST(ep->dev->controller)->opreg->HcCommandStatus = __cpu_to_le32(BulkListFilled);
+
+ int failure = wait_for_ed(ep->dev, head,
+ (dalen==0)?0:(last_page - first_page + 1));
+ /* Wait some frames before and one after disabling list access. */
+ mdelay(4);
+ OHCI_INST(ep->dev->controller)->opreg->HcControl &= __cpu_to_le32(~BulkListEnable);
+ mdelay(1);
+
+ ep->toggle = __le32_to_cpu(head->head_pointer) & ED_TOGGLE;
+
+ /* free memory */
+ ohci_free_ed(head);
+
+ if (failure) {
+ /* try cleanup */
+ clear_stall(ep);
+ }
+
+ return failure;
+}
+
+
+struct _intr_queue;
+
+struct _intrq_td {
+ volatile td_t td;
+ u8 *data;
+ struct _intrq_td *next;
+ struct _intr_queue *intrq;
+};
+
+struct _intr_queue {
+ volatile ed_t ed;
+ struct _intrq_td *head;
+ struct _intrq_td *tail;
+ u8 *data;
+ int reqsize;
+ endpoint_t *endp;
+ unsigned int remaining_tds;
+ int destroy;
+};
+
+typedef struct _intrq_td intrq_td_t;
+typedef struct _intr_queue intr_queue_t;
+
+#define INTRQ_TD_FROM_TD(x) ((intrq_td_t *)x)
+
+static void
+ohci_fill_intrq_td(intrq_td_t *const td, intr_queue_t *const intrq,
+ u8 *const data)
+{
+ memset(td, 0, sizeof(*td));
+ td->td.config = __cpu_to_le32(TD_QUEUETYPE_INTR |
+ (intrq->endp->direction == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) |
+ TD_DELAY_INTERRUPT_ZERO |
+ TD_TOGGLE_FROM_ED |
+ TD_CC_NOACCESS);
+ td->td.current_buffer_pointer = __cpu_to_le32(virt_to_phys(data));
+ td->td.buffer_end = __cpu_to_le32(virt_to_phys(data) + intrq->reqsize - 1);
+ td->intrq = intrq;
+ td->data = data;
+}
+
+/* create and hook-up an intr queue into device schedule */
+static void *
+ohci_create_intr_queue(endpoint_t *const ep, const int reqsize,
+ const int reqcount, const int reqtiming)
+{
+ int i;
+ intrq_td_t *first_td = NULL, *last_td = NULL;
+
+ if (reqsize > 4096)
+ return NULL;
+
+ intr_queue_t *const intrq;
+ ofmem_posix_memalign((void **)&intrq, sizeof(intrq->ed), sizeof(*intrq));
+ memset(intrq, 0, sizeof(*intrq));
+ intrq->data = (u8 *)malloc(reqcount * reqsize);
+ intrq->reqsize = reqsize;
+ intrq->endp = ep;
+
+ /* Create #reqcount TDs. */
+ u8 *cur_data = intrq->data;
+ for (i = 0; i < reqcount; ++i) {
+ intrq_td_t *const td;
+ ofmem_posix_memalign((void **)&td, sizeof(td->td), sizeof(*td));
+ ++intrq->remaining_tds;
+ ohci_fill_intrq_td(td, intrq, cur_data);
+ cur_data += reqsize;
+ if (!first_td)
+ first_td = td;
+ else
+ last_td->td.next_td = __cpu_to_le32(virt_to_phys(&td->td));
+ last_td = td;
+ }
+
+ /* Create last, dummy TD. */
+ intrq_td_t *dummy_td;
+ ofmem_posix_memalign((void **)&dummy_td, sizeof(dummy_td->td), sizeof(*dummy_td));
+ memset(dummy_td, 0, sizeof(*dummy_td));
+ dummy_td->intrq = intrq;
+ if (last_td)
+ last_td->td.next_td = __cpu_to_le32(virt_to_phys(&dummy_td->td));
+ last_td = dummy_td;
+
+ /* Initialize ED. */
+ intrq->ed.config = __cpu_to_le32((ep->dev->address << ED_FUNC_SHIFT) |
+ ((ep->endpoint & 0xf) << ED_EP_SHIFT) |
+ (((ep->direction == IN) ? OHCI_IN : OHCI_OUT) << ED_DIR_SHIFT) |
+ (ep->dev->speed ? ED_LOWSPEED : 0) |
+ (ep->maxpacketsize << ED_MPS_SHIFT));
+ intrq->ed.tail_pointer = __cpu_to_le32(virt_to_phys(last_td));
+ intrq->ed.head_pointer = __cpu_to_le32(virt_to_phys(first_td) | (ep->toggle ? ED_TOGGLE : 0));
+
+#ifdef USB_DEBUG_ED
+ dump_ed(&intrq->ed);
+#endif
+ /* Insert ED into periodic table. */
+ int nothing_placed = 1;
+ ohci_t *const ohci = OHCI_INST(ep->dev->controller);
+ u32 *const intr_table = ohci->hcca->HccaInterruptTable;
+ const u32 dummy_ptr = __cpu_to_le32(virt_to_phys(ohci->periodic_ed));
+ for (i = 0; i < 32; i += reqtiming) {
+ /* Advance to the next free position. */
+ while ((i < 32) && (intr_table[i] != dummy_ptr)) ++i;
+ if (i < 32) {
+ usb_debug("Placed endpoint %lx to %d\n", virt_to_phys(&intrq->ed), i);
+ intr_table[i] = __cpu_to_le32(virt_to_phys(&intrq->ed));
+ nothing_placed = 0;
+ }
+ }
+ if (nothing_placed) {
+ usb_debug("Error: Failed to place ohci interrupt endpoint "
+ "descriptor into periodic table: no space left\n");
+ ohci_destroy_intr_queue(ep, intrq);
+ return NULL;
+ }
+
+ return intrq;
+}
+
+/* remove queue from device schedule, dropping all data that came in */
+static void
+ohci_destroy_intr_queue(endpoint_t *const ep, void *const q_)
+{
+ intr_queue_t *const intrq = (intr_queue_t *)q_;
+
+ int i;
+
+ /* Remove interrupt queue from periodic table. */
+ ohci_t *const ohci = OHCI_INST(ep->dev->controller);
+ u32 *const intr_table = ohci->hcca->HccaInterruptTable;
+ for (i=0; i < 32; ++i) {
+ if (intr_table[i] == __cpu_to_le32(virt_to_phys(intrq)))
+ intr_table[i] = __cpu_to_le32(virt_to_phys(ohci->periodic_ed));
+ }
+ /* Wait for frame to finish. */
+ mdelay(1);
+
+ /* Free unprocessed TDs. */
+ while ((__le32_to_cpu(intrq->ed.head_pointer) & ~0x3) != __le32_to_cpu(intrq->ed.tail_pointer)) {
+ td_t *const cur_td = (td_t *)phys_to_virt(__le32_to_cpu(intrq->ed.head_pointer) & ~0x3);
+ intrq->ed.head_pointer = cur_td->next_td;
+ free(INTRQ_TD_FROM_TD(cur_td));
+ --intrq->remaining_tds;
+ }
+ /* Free final, dummy TD. */
+ free(phys_to_virt(__le32_to_cpu(intrq->ed.head_pointer) & ~0x3));
+ /* Free data buffer. */
+ free(intrq->data);
+
+ /* Free TDs already fetched from the done queue. */
+ ohci_process_done_queue(ohci, 1);
+ while (intrq->head) {
+ intrq_td_t *const cur_td = (intrq_td_t *const )__le32_to_cpu(intrq->head);
+ intrq->head = intrq->head->next;
+ free(cur_td);
+ --intrq->remaining_tds;
+ }
+
+ /* Mark interrupt queue to be destroyed.
+ ohci_process_done_queue() will free the remaining TDs
+ and finish the interrupt queue off once all TDs are gone. */
+ intrq->destroy = 1;
+
+ /* Save data toggle. */
+ ep->toggle = __le32_to_cpu(intrq->ed.head_pointer) & ED_TOGGLE;
+}
+
+/* read one intr-packet from queue, if available. extend the queue for new input.
+ return NULL if nothing new available.
+ Recommended use: while (data=poll_intr_queue(q)) process(data);
+ */
+static u8 *
+ohci_poll_intr_queue(void *const q_)
+{
+ intr_queue_t *const intrq = (intr_queue_t *)q_;
+
+ u8 *data = NULL;
+
+ /* Process done queue first, then check if we have work to do. */
+ ohci_process_done_queue(OHCI_INST(intrq->endp->dev->controller), 0);
+
+ if (intrq->head) {
+ /* Save pointer to processed TD and advance. */
+ intrq_td_t *const cur_td = intrq->head;
+ intrq->head = cur_td->next;
+
+ /* Return data buffer of this TD. */
+ data = cur_td->data;
+
+ /* Requeue this TD (i.e. copy to dummy and requeue as dummy). */
+ intrq_td_t *const dummy_td =
+ INTRQ_TD_FROM_TD(phys_to_virt(__le32_to_cpu(intrq->ed.tail_pointer)));
+ ohci_fill_intrq_td(dummy_td, intrq, data);
+ /* Reset all but intrq pointer (i.e. init as dummy). */
+ memset(cur_td, 0, sizeof(*cur_td));
+ cur_td->intrq = intrq;
+ /* Insert into interrupt queue as dummy. */
+ dummy_td->td.next_td = __le32_to_cpu(virt_to_phys(&cur_td->td));
+ intrq->ed.tail_pointer = __le32_to_cpu(virt_to_phys(&cur_td->td));
+ }
+
+ return data;
+}
+
+static void
+ohci_process_done_queue(ohci_t *const ohci, const int spew_debug)
+{
+ int i, j;
+
+ /* Temporary queue of interrupt queue TDs (to reverse order). */
+ intrq_td_t *temp_tdq = NULL;
+
+ /* Check if done head has been written. */
+ if (!(READ_OPREG(ohci, HcInterruptStatus) & WritebackDoneHead))
+ return;
+ /* Fetch current done head.
+ Lsb is only interesting for hw interrupts. */
+ u32 phys_done_queue = __le32_to_cpu(ohci->hcca->HccaDoneHead) & ~1;
+ /* Tell host controller, he may overwrite the done head pointer. */
+ ohci->opreg->HcInterruptStatus = __cpu_to_le32(WritebackDoneHead);
+
+ i = 0;
+ /* Process done queue (it's in reversed order). */
+ while (phys_done_queue) {
+ td_t *const done_td = (td_t *)phys_to_virt(phys_done_queue);
+
+ /* Advance pointer to next TD. */
+ phys_done_queue = __le32_to_cpu(done_td->next_td);
+
+ switch (__le32_to_cpu(done_td->config) & TD_QUEUETYPE_MASK) {
+ case TD_QUEUETYPE_ASYNC:
+ /* Free processed async TDs. */
+ free((void *)done_td);
+ break;
+ case TD_QUEUETYPE_INTR: {
+ intrq_td_t *const td = INTRQ_TD_FROM_TD(done_td);
+ intr_queue_t *const intrq = td->intrq;
+ /* Check if the corresponding interrupt
+ queue is still beeing processed. */
+ if (intrq->destroy) {
+ /* Free this TD, and */
+ free(td);
+ --intrq->remaining_tds;
+ /* the interrupt queue if it has no more TDs. */
+ if (!intrq->remaining_tds)
+ free(intrq);
+ usb_debug("Freed TD from orphaned interrupt "
+ "queue, %d TDs remain.\n",
+ intrq->remaining_tds);
+ } else {
+ /* Save done TD to be processed. */
+ td->next = temp_tdq;
+ temp_tdq = td;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ ++i;
+ }
+ if (spew_debug)
+ usb_debug("Processed %d done TDs.\n", i);
+
+ j = 0;
+ /* Process interrupt queue TDs in right order. */
+ while (temp_tdq) {
+ /* Save pointer of current TD and advance. */
+ intrq_td_t *const cur_td = temp_tdq;
+ temp_tdq = temp_tdq->next;
+
+ /* The interrupt queue for the current TD. */
+ intr_queue_t *const intrq = cur_td->intrq;
+ /* Append to interrupt queue. */
+ if (!intrq->head) {
+ /* First element. */
+ intrq->head = intrq->tail = cur_td;
+ } else {
+ /* Insert at tail. */
+ intrq->tail->next = cur_td;
+ intrq->tail = cur_td;
+ }
+ /* It's always the last element. */
+ cur_td->next = NULL;
+ ++j;
+ }
+ if (spew_debug)
+ usb_debug("processed %d done tds, %d intr tds thereof.\n", i, j);
+}
+
+int ob_usb_ohci_init (const char *path, uint32_t addr)
+{
+ hci_t *ctrl;
+ int i;
+
+ usb_debug("ohci_init: %s addr = %x\n", path, addr);
+ ctrl = ohci_pci_init(addr);
+ if (!ctrl)
+ return 0;
+
+ /* Init ports */
+ usb_poll();
+
+ /* Look for a keyboard */
+ for (i = 0; i < 128; i++) {
+ if (ctrl->devices[i] && ctrl->devices[i]->configuration) {
+ configuration_descriptor_t *cd;
+ interface_descriptor_t *intf;
+
+ cd = (configuration_descriptor_t *)ctrl->devices[i]->configuration;
+ intf = (interface_descriptor_t *)(ctrl->devices[i]->configuration + cd->bLength);
+ usb_debug("Device at port %d is class %d\n", i, intf->bInterfaceClass);
+ if (intf->bInterfaceClass == hid_device &&
+ intf->bInterfaceSubClass == hid_subclass_boot &&
+ intf->bInterfaceProtocol == hid_boot_proto_keyboard ) {
+ break;
+ }
+ }
+ }
+ if ( i < 128 )
+ ob_usb_hid_add_keyboard(path);
+
+ return 1;
+}
diff --git a/drivers/usbohci.h b/drivers/usbohci.h
new file mode 100644
index 00000000..69033287
--- /dev/null
+++ b/drivers/usbohci.h
@@ -0,0 +1,45 @@
+/*
+ * Driver for USB OHCI ported from CoreBoot
+ *
+ * Copyright (C) 2014 BALATON Zoltan
+ *
+ * This file was part of the libpayload project.
+ *
+ * Copyright (C) 2010 Patrick Georgi
+ *
+ * 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.
+ */
+
+#ifndef __OHCI_H
+#define __OHCI_H
+
+#include "config.h"
+#include "usbohci_private.h"
+
+hci_t *ohci_pci_init (u32 addr);
+hci_t *ohci_init (void *bar);
+
+void ohci_rh_init (usbdev_t *dev);
+
+#endif
diff --git a/drivers/usbohci_private.h b/drivers/usbohci_private.h
new file mode 100644
index 00000000..b3a723e2
--- /dev/null
+++ b/drivers/usbohci_private.h
@@ -0,0 +1,270 @@
+/*
+ * Driver for USB OHCI ported from CoreBoot
+ *
+ * Copyright (C) 2014 BALATON Zoltan
+ *
+ * This file was part of the libpayload project.
+ *
+ * Copyright (C) 2010 Patrick Georgi
+ *
+ * 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.
+ */
+
+#ifndef __OHCI_PRIVATE_H
+#define __OHCI_PRIVATE_H
+
+#include "libc/byteorder.h"
+#include "usb.h"
+
+#define READ_OPREG(ohci, field) (__le32_to_cpu((ohci)->opreg->field))
+#define MASK(startbit, lenbit) (((1<<(lenbit))-1)<<(startbit))
+
+ // FIXME: fake
+ typedef enum { CMD} reg;
+
+ enum {
+ NumberDownstreamPorts = 1<<0,
+ PowerSwitchingMode = 1<<8,
+ NoPowerSwitching = 1<<9,
+ DeviceType = 1<<10,
+ OverCurrentProtectionMode = 1<<11,
+ NoOverCurrentProtection = 1<<12,
+ PowerOnToPowerGoodTime = 1<<24
+ } HcRhDescriptorAReg;
+
+ enum {
+ NumberDownstreamPortsMask = MASK(0, 8),
+ PowerOnToPowerGoodTimeMask = MASK(24, 8)
+ } HcRhDescriptorAMask;
+
+ enum {
+ DeviceRemovable = 1<<0,
+ PortPowerControlMask = 1<<16
+ } HcRhDescriptorBReg;
+
+ enum {
+ CurrentConnectStatus = 1<<0,
+ PortEnableStatus = 1<<1,
+ PortSuspendStatus = 1<<2,
+ PortOverCurrentIndicator = 1<<3,
+ PortResetStatus = 1<<4,
+ PortPowerStatus = 1<<8,
+ LowSpeedDeviceAttached = 1<<9,
+ ConnectStatusChange = 1<<16,
+ PortEnableStatusChange = 1<<17,
+ PortSuspendStatusChange = 1<<18,
+ PortOverCurrentIndicatorChange = 1<<19,
+ PortResetStatusChange = 1<<20
+ } HcRhPortStatusRead;
+ enum {
+ ClearPortEnable = 1<<0,
+ SetPortEnable = 1<<1,
+ SetPortSuspend = 1<<2,
+ ClearSuspendStatus = 1<<3,
+ SetPortReset = 1<<4,
+ SetPortPower = 1<<8,
+ ClearPortPower = 1<<9,
+ } HcRhPortStatusSet;
+
+ enum {
+ LocalPowerStatus = 1<<0,
+ OverCurrentIndicator = 1<<1,
+ DeviceRemoteWakeupEnable = 1<<15,
+ LocalPowerStatusChange = 1<<16,
+ OverCurrentIndicatorChange = 1<<17,
+ ClearRemoteWakeupEnable = 1<<31
+ } HcRhStatusReg;
+
+ enum {
+ FrameInterval = 1<<0,
+ FSLargestDataPacket = 1<<16,
+ FrameIntervalToggle = 1<<31
+ } HcFmIntervalOffset;
+ enum {
+ FrameIntervalMask = MASK(0, 14),
+ FSLargestDataPacketMask = MASK(16, 15),
+ FrameIntervalToggleMask = MASK(31, 1)
+ } HcFmIntervalMask;
+
+ enum {
+ ControlBulkServiceRatio = 1<<0,
+ PeriodicListEnable = 1<<2,
+ IsochronousEnable = 1<<3,
+ ControlListEnable = 1<<4,
+ BulkListEnable = 1<<5,
+ HostControllerFunctionalState = 1<<6,
+ InterruptRouting = 1<<8,
+ RemoteWakeupConnected = 1<<9,
+ RemoteWakeupEnable = 1<<10
+ } HcControlReg;
+
+ enum {
+ ControlBulkServiceRatioMask = MASK(0, 2),
+ HostControllerFunctionalStateMask = MASK(6, 2)
+ } HcControlMask;
+
+ enum {
+ USBReset = 0*HostControllerFunctionalState,
+ USBResume = 1*HostControllerFunctionalState,
+ USBOperational = 2*HostControllerFunctionalState,
+ USBSuspend = 3*HostControllerFunctionalState
+ };
+
+ enum {
+ HostControllerReset = 1<<0,
+ ControlListFilled = 1<<1,
+ BulkListFilled = 1<<2,
+ OwnershipChangeRequest = 1<<3,
+ SchedulingOverrunCount = 1<<16
+ } HcCommandStatusReg;
+
+ enum {
+ SchedulingOverrunCountMask = MASK(16, 2)
+ } HcCommandStatusMask;
+
+ enum {
+ FrameRemaining = 1<<0,
+ FrameRemainingToggle = 1<<31
+ } HcFmRemainingReg;
+
+ enum {
+ SchedulingOverrung = 1<<0,
+ WritebackDoneHead = 1<<1,
+ StartofFrame = 1<<2,
+ ResumeDetected = 1<<3,
+ UnrecoverableError = 1<<4,
+ FrameNumberOverflow = 1<<5,
+ RootHubStatusChange = 1<<6,
+ OwnershipChange = 1<<30
+ } HcInterruptStatusReg;
+
+ typedef struct {
+ // Control and Status Partition
+ volatile u32 HcRevision;
+ volatile u32 HcControl;
+ volatile u32 HcCommandStatus;
+ volatile u32 HcInterruptStatus;
+ volatile u32 HcInterruptEnable;
+ volatile u32 HcInterruptDisable;
+
+ // Memory Pointer Partition
+ volatile u32 HcHCCA;
+ volatile u32 HcPeriodCurrentED;
+ volatile u32 HcControlHeadED;
+ volatile u32 HcControlCurrentED;
+ volatile u32 HcBulkHeadED;
+ volatile u32 HcBulkCurrentED;
+ volatile u32 HcDoneHead;
+
+ // Frame Counter Partition
+ volatile u32 HcFmInterval;
+ volatile u32 HcFmRemaining;
+ volatile u32 HcFmNumber;
+ volatile u32 HcPeriodicStart;
+ volatile u32 HcLSThreshold;
+
+ // Root Hub Partition
+ volatile u32 HcRhDescriptorA;
+ volatile u32 HcRhDescriptorB;
+ volatile u32 HcRhStatus;
+ /* all bits in HcRhPortStatus registers are R/WC, so
+ _DO NOT_ use |= to set the bits,
+ this clears the entire state */
+ volatile u32 HcRhPortStatus[];
+ } __attribute__ ((packed)) opreg_t;
+
+ typedef struct { /* should be 256 bytes according to spec */
+ u32 HccaInterruptTable[32];
+ volatile u16 HccaFrameNumber;
+ volatile u16 HccaPad1;
+ volatile u32 HccaDoneHead;
+ u8 reserved[116]; /* pad according to spec */
+ u8 what[4]; /* really pad to 256 as spec only covers 252 */
+ } __attribute__ ((packed)) hcca_t;
+
+ typedef volatile struct {
+ u32 config;
+ u32 tail_pointer;
+ u32 head_pointer;
+ u32 next_ed;
+ } __attribute__ ((packed)) ed_t;
+#define ED_HALTED 1
+#define ED_TOGGLE 2
+
+#define ED_FUNC_SHIFT 0
+#define ED_FUNC_MASK MASK(0, 7)
+#define ED_EP_SHIFT 7
+#define ED_EP_MASK MASK(7, 4)
+#define ED_DIR_SHIFT 11
+#define ED_DIR_MASK MASK(11, 2)
+#define ED_LOWSPEED (1 << 13)
+#define ED_MPS_SHIFT 16
+
+ typedef volatile struct {
+ u32 config;
+ u32 current_buffer_pointer;
+ u32 next_td;
+ u32 buffer_end;
+ } __attribute__ ((packed)) td_t;
+/*
+ * Bits 0 through 17 of .config won't be interpreted by the host controller
+ * (HC) and, after processing the TD, the HC has to ensure those bits have
+ * the same state as before. So we are free to use those bits for our own
+ * purpose.
+ */
+#define TD_QUEUETYPE_SHIFT 0
+#define TD_QUEUETYPE_MASK MASK(TD_QUEUETYPE_SHIFT, 2)
+#define TD_QUEUETYPE_ASYNC (0 << TD_QUEUETYPE_SHIFT)
+#define TD_QUEUETYPE_INTR (1 << TD_QUEUETYPE_SHIFT)
+
+#define TD_DIRECTION_SHIFT 19
+#define TD_DIRECTION_MASK MASK(TD_DIRECTION_SHIFT, 2)
+#define TD_DIRECTION_SETUP OHCI_SETUP << TD_DIRECTION_SHIFT
+#define TD_DIRECTION_IN OHCI_IN << TD_DIRECTION_SHIFT
+#define TD_DIRECTION_OUT OHCI_OUT << TD_DIRECTION_SHIFT
+#define TD_DELAY_INTERRUPT_SHIFT 21
+#define TD_DELAY_INTERRUPT_MASK MASK(TD_DELAY_INTERRUPT_SHIFT, 3)
+#define TD_DELAY_INTERRUPT_ZERO 0
+#define TD_DELAY_INTERRUPT_NOINTR (7 << TD_DELAY_INTERRUPT_SHIFT)
+#define TD_TOGGLE_DATA0 0
+#define TD_TOGGLE_DATA1 (1 << 24)
+#define TD_TOGGLE_FROM_ED 0
+#define TD_TOGGLE_FROM_TD (1 << 25)
+#define TD_CC_SHIFT 28
+#define TD_CC_MASK MASK(TD_CC_SHIFT, 4)
+#define TD_CC_NOERR 0
+#define TD_CC_NOACCESS (14 << TD_CC_SHIFT) /* the lower of the two values, so "no access" can be tested with >= */
+
+#define OHCI_INST(controller) ((ohci_t*)((controller)->instance))
+
+ typedef struct ohci {
+ opreg_t *opreg;
+ hcca_t *hcca;
+ usbdev_t *roothub;
+ ed_t *periodic_ed;
+ } ohci_t;
+
+ typedef enum { OHCI_SETUP=0, OHCI_OUT=1, OHCI_IN=2, OHCI_FROM_TD=3 } ohci_pid_t;
+
+#endif
diff --git a/drivers/usbohci_rh.c b/drivers/usbohci_rh.c
new file mode 100644
index 00000000..55503be6
--- /dev/null
+++ b/drivers/usbohci_rh.c
@@ -0,0 +1,212 @@
+/*
+ * Driver for USB OHCI Root Hubs ported from CoreBoot
+ *
+ * Copyright (C) 2014 BALATON Zoltan
+ *
+ * This file was part of the libpayload project.
+ *
+ * Copyright (C) 2010 Patrick Georgi
+ *
+ * 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 "timer.h"
+#include "usbohci_private.h"
+#include "usbohci.h"
+
+typedef struct {
+ int numports;
+ int *port;
+} rh_inst_t;
+
+#define RH_INST(dev) ((rh_inst_t*)(dev)->data)
+
+static void
+ohci_rh_enable_port (usbdev_t *dev, int port)
+{
+ /* Reset RH port should hold 50ms with pulses of at least 10ms and
+ * gaps of at most 3ms (usb20 spec 7.1.7.5).
+ * After reset, the port will be enabled automatically (ohci spec
+ * 7.4.4).
+ */
+ int total_delay = 100; /* 100 * 500us == 50ms */
+ while (total_delay > 0) {
+ if (!(READ_OPREG(OHCI_INST(dev->controller), HcRhPortStatus[port])
+ & CurrentConnectStatus))
+ return;
+
+ /* start reset */
+ OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] =
+ __cpu_to_le32(SetPortReset);
+ int timeout = 200; /* timeout after 200 * 500us == 100ms */
+ while ((READ_OPREG(OHCI_INST(dev->controller), HcRhPortStatus[port])
+ & PortResetStatus)
+ && timeout--) {
+ udelay(500); total_delay--;
+ }
+ if (READ_OPREG(OHCI_INST(dev->controller), HcRhPortStatus[port])
+ & PortResetStatus) {
+ usb_debug("Warning: root-hub port reset timed out.\n");
+ break;
+ }
+ if ((200-timeout) < 20) {
+ usb_debug("Warning: port reset too short: %dms; "
+ "should be at least 10ms.\n",
+ (200-timeout)/2);
+ total_delay = 0; /* can happen on QEMU */
+ }
+ /* clear reset status change */
+ OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] =
+ __cpu_to_le32(PortResetStatusChange);
+ usb_debug ("rh port reset finished after %dms.\n", (200-timeout)/2);
+ }
+}
+
+/* disable root hub */
+static void
+ohci_rh_disable_port (usbdev_t *dev, int port)
+{
+ OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] =
+ __cpu_to_le32(ClearPortEnable); // disable port
+ int timeout = 50; /* timeout after 50 * 100us == 5ms */
+ while ((READ_OPREG(OHCI_INST (dev->controller), HcRhPortStatus[port])
+ & PortEnableStatus)
+ && timeout--) {
+ udelay(100);
+ }
+}
+
+static void
+ohci_rh_scanport (usbdev_t *dev, int port)
+{
+ if (port >= RH_INST(dev)->numports) {
+ usb_debug("Invalid port %d\n", port);
+ return;
+ }
+
+ /* device registered, and device change logged, so something must have happened */
+ if (RH_INST (dev)->port[port] != -1) {
+ usb_detach_device(dev->controller, RH_INST (dev)->port[port]);
+ RH_INST (dev)->port[port] = -1;
+ }
+
+ /* no device attached
+ previously registered devices are detached, nothing left to do */
+ if (!(READ_OPREG(OHCI_INST(dev->controller), HcRhPortStatus[port]) & CurrentConnectStatus))
+ return;
+
+ // clear port state change
+ OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] = __cpu_to_le32(ConnectStatusChange);
+ ohci_rh_enable_port (dev, port);
+
+ mdelay(100); // wait for signal to stabilize
+
+ if (!(READ_OPREG(OHCI_INST(dev->controller), HcRhPortStatus[port]) & PortEnableStatus)) {
+ usb_debug ("port enable failed\n");
+ return;
+ }
+
+ int speed = (READ_OPREG(OHCI_INST(dev->controller), HcRhPortStatus[port]) & LowSpeedDeviceAttached) != 0;
+ RH_INST (dev)->port[port] = usb_attach_device(dev->controller, dev->address, port, speed);
+}
+
+static int
+ohci_rh_report_port_changes (usbdev_t *dev)
+{
+ ohci_t *const ohcic = OHCI_INST (dev->controller);
+
+ int i;
+
+ for (i = 0; i < RH_INST(dev)->numports; i++) {
+ // maybe detach+attach happened between two scans?
+ if (READ_OPREG(ohcic, HcRhPortStatus[i]) & ConnectStatusChange) {
+ ohcic->opreg->HcRhPortStatus[i] = __cpu_to_le32(ConnectStatusChange);
+ usb_debug("attachment change on port %d\n", i);
+ return i;
+ }
+ }
+
+ // no change
+ return -1;
+}
+
+static void
+ohci_rh_destroy (usbdev_t *dev)
+{
+ int i;
+ for (i = 0; i < RH_INST (dev)->numports; i++)
+ ohci_rh_disable_port (dev, i);
+ free (RH_INST (dev));
+}
+
+static void
+ohci_rh_poll (usbdev_t *dev)
+{
+ ohci_t *const ohcic = OHCI_INST (dev->controller);
+
+ int port;
+
+ /* Check if anything changed. */
+ if (!(READ_OPREG(ohcic, HcInterruptStatus) & RootHubStatusChange))
+ return;
+ ohcic->opreg->HcInterruptStatus = __cpu_to_le32(RootHubStatusChange);
+ usb_debug("root hub status change\n");
+
+ /* Scan ports with changed connection status. */
+ while ((port = ohci_rh_report_port_changes (dev)) != -1)
+ ohci_rh_scanport (dev, port);
+}
+
+void
+ohci_rh_init (usbdev_t *dev)
+{
+ int i;
+
+ dev->destroy = ohci_rh_destroy;
+ dev->poll = ohci_rh_poll;
+
+ dev->data = malloc (sizeof (rh_inst_t));
+ if (!dev->data) {
+ printk("Not enough memory for OHCI RH.\n");
+ return;
+ }
+
+ RH_INST (dev)->numports = READ_OPREG(OHCI_INST(dev->controller), HcRhDescriptorA) & NumberDownstreamPortsMask;
+ RH_INST (dev)->port = malloc(sizeof(int) * RH_INST (dev)->numports);
+ usb_debug("%d ports registered\n", RH_INST (dev)->numports);
+
+ for (i = 0; i < RH_INST (dev)->numports; i++) {
+ ohci_rh_enable_port (dev, i);
+ RH_INST (dev)->port[i] = -1;
+ }
+
+ /* we can set them here because a root hub _really_ shouldn't
+ appear elsewhere */
+ dev->address = 0;
+ dev->hub = -1;
+ dev->port = -1;
+
+ usb_debug("rh init done\n");
+}
diff --git a/include/drivers/pci.h b/include/drivers/pci.h
index 1f0af6c9..2eb5685d 100644
--- a/include/drivers/pci.h
+++ b/include/drivers/pci.h
@@ -188,6 +188,7 @@ extern const pci_arch_t *arch;
#define PCI_DEVICE_ID_APPLE_UNI_N_PCI 0x001f
#define PCI_DEVICE_ID_APPLE_UNI_N_AGP 0x0020
#define PCI_DEVICE_ID_APPLE_UNI_N_KEYL 0x0022
+#define PCI_DEVICE_ID_APPLE_KEYL_USB 0x003f
#define PCI_DEVICE_ID_APPLE_U3_AGP 0x004b
#define PCI_VENDOR_ID_SUN 0x108e
diff --git a/include/drivers/usb.h b/include/drivers/usb.h
new file mode 100644
index 00000000..143ed27b
--- /dev/null
+++ b/include/drivers/usb.h
@@ -0,0 +1,8 @@
+#ifndef USB_H
+#define USB_H
+
+int ob_usb_ohci_init(const char *path, uint32_t addr);
+void ob_usb_hid_add_keyboard(const char *path);
+int usb_exit(void);
+
+#endif /* USB_H */