diff options
Diffstat (limited to 'hw/usb')
-rw-r--r-- | hw/usb/bus.c | 584 | ||||
-rw-r--r-- | hw/usb/core.c | 663 | ||||
-rw-r--r-- | hw/usb/desc.c | 601 | ||||
-rw-r--r-- | hw/usb/desc.h | 117 | ||||
-rw-r--r-- | hw/usb/dev-audio.c | 714 | ||||
-rw-r--r-- | hw/usb/dev-bluetooth.c | 557 | ||||
-rw-r--r-- | hw/usb/dev-hid.c | 638 | ||||
-rw-r--r-- | hw/usb/dev-hub.c | 549 | ||||
-rw-r--r-- | hw/usb/dev-network.c | 1423 | ||||
-rw-r--r-- | hw/usb/dev-serial.c | 637 | ||||
-rw-r--r-- | hw/usb/dev-smartcard-reader.c | 1365 | ||||
-rw-r--r-- | hw/usb/dev-storage.c | 677 | ||||
-rw-r--r-- | hw/usb/dev-wacom.c | 381 | ||||
-rw-r--r-- | hw/usb/hcd-ehci.c | 2345 | ||||
-rw-r--r-- | hw/usb/hcd-musb.c | 1544 | ||||
-rw-r--r-- | hw/usb/hcd-ohci.c | 1898 | ||||
-rw-r--r-- | hw/usb/hcd-uhci.c | 1408 | ||||
-rw-r--r-- | hw/usb/hcd-xhci.c | 2925 | ||||
-rw-r--r-- | hw/usb/host-bsd.c | 647 | ||||
-rw-r--r-- | hw/usb/host-linux.c | 1913 | ||||
-rw-r--r-- | hw/usb/host-stub.c | 52 | ||||
-rw-r--r-- | hw/usb/libhw.c | 63 | ||||
-rw-r--r-- | hw/usb/redirect.c | 1485 |
23 files changed, 23186 insertions, 0 deletions
diff --git a/hw/usb/bus.c b/hw/usb/bus.c new file mode 100644 index 0000000..d3f8358 --- /dev/null +++ b/hw/usb/bus.c @@ -0,0 +1,584 @@ +#include "hw/hw.h" +#include "hw/usb.h" +#include "hw/qdev.h" +#include "sysemu.h" +#include "monitor.h" +#include "trace.h" + +static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent); + +static char *usb_get_dev_path(DeviceState *dev); +static char *usb_get_fw_dev_path(DeviceState *qdev); +static int usb_qdev_exit(DeviceState *qdev); + +static struct BusInfo usb_bus_info = { + .name = "USB", + .size = sizeof(USBBus), + .print_dev = usb_bus_dev_print, + .get_dev_path = usb_get_dev_path, + .get_fw_dev_path = usb_get_fw_dev_path, + .props = (Property[]) { + DEFINE_PROP_STRING("port", USBDevice, port_path), + DEFINE_PROP_END_OF_LIST() + }, +}; +static int next_usb_bus = 0; +static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses); + +const VMStateDescription vmstate_usb_device = { + .name = "USBDevice", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(addr, USBDevice), + VMSTATE_INT32(state, USBDevice), + VMSTATE_INT32(remote_wakeup, USBDevice), + VMSTATE_INT32(setup_state, USBDevice), + VMSTATE_INT32(setup_len, USBDevice), + VMSTATE_INT32(setup_index, USBDevice), + VMSTATE_UINT8_ARRAY(setup_buf, USBDevice, 8), + VMSTATE_END_OF_LIST(), + } +}; + +void usb_bus_new(USBBus *bus, USBBusOps *ops, DeviceState *host) +{ + qbus_create_inplace(&bus->qbus, &usb_bus_info, host, NULL); + bus->ops = ops; + bus->busnr = next_usb_bus++; + bus->qbus.allow_hotplug = 1; /* Yes, we can */ + QTAILQ_INIT(&bus->free); + QTAILQ_INIT(&bus->used); + QTAILQ_INSERT_TAIL(&busses, bus, next); +} + +USBBus *usb_bus_find(int busnr) +{ + USBBus *bus; + + if (-1 == busnr) + return QTAILQ_FIRST(&busses); + QTAILQ_FOREACH(bus, &busses, next) { + if (bus->busnr == busnr) + return bus; + } + return NULL; +} + +static int usb_device_init(USBDevice *dev) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->init) { + return klass->init(dev); + } + return 0; +} + +USBDevice *usb_device_find_device(USBDevice *dev, uint8_t addr) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->find_device) { + return klass->find_device(dev, addr); + } + return NULL; +} + +static void usb_device_handle_destroy(USBDevice *dev) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->handle_destroy) { + klass->handle_destroy(dev); + } +} + +void usb_device_cancel_packet(USBDevice *dev, USBPacket *p) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->cancel_packet) { + klass->cancel_packet(dev, p); + } +} + +void usb_device_handle_attach(USBDevice *dev) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->handle_attach) { + klass->handle_attach(dev); + } +} + +void usb_device_handle_reset(USBDevice *dev) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->handle_reset) { + klass->handle_reset(dev); + } +} + +int usb_device_handle_control(USBDevice *dev, USBPacket *p, int request, + int value, int index, int length, uint8_t *data) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->handle_control) { + return klass->handle_control(dev, p, request, value, index, length, + data); + } + return -ENOSYS; +} + +int usb_device_handle_data(USBDevice *dev, USBPacket *p) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->handle_data) { + return klass->handle_data(dev, p); + } + return -ENOSYS; +} + +const char *usb_device_get_product_desc(USBDevice *dev) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + return klass->product_desc; +} + +const USBDesc *usb_device_get_usb_desc(USBDevice *dev) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + return klass->usb_desc; +} + +void usb_device_set_interface(USBDevice *dev, int interface, + int alt_old, int alt_new) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->set_interface) { + klass->set_interface(dev, interface, alt_old, alt_new); + } +} + +static int usb_qdev_init(DeviceState *qdev) +{ + USBDevice *dev = USB_DEVICE(qdev); + int rc; + + pstrcpy(dev->product_desc, sizeof(dev->product_desc), + usb_device_get_product_desc(dev)); + dev->auto_attach = 1; + QLIST_INIT(&dev->strings); + usb_ep_init(dev); + rc = usb_claim_port(dev); + if (rc != 0) { + return rc; + } + rc = usb_device_init(dev); + if (rc != 0) { + usb_release_port(dev); + return rc; + } + if (dev->auto_attach) { + rc = usb_device_attach(dev); + if (rc != 0) { + usb_qdev_exit(qdev); + return rc; + } + } + return 0; +} + +static int usb_qdev_exit(DeviceState *qdev) +{ + USBDevice *dev = USB_DEVICE(qdev); + + if (dev->attached) { + usb_device_detach(dev); + } + usb_device_handle_destroy(dev); + if (dev->port) { + usb_release_port(dev); + } + return 0; +} + +typedef struct LegacyUSBFactory +{ + const char *name; + const char *usbdevice_name; + USBDevice *(*usbdevice_init)(USBBus *bus, const char *params); +} LegacyUSBFactory; + +static GSList *legacy_usb_factory; + +void usb_legacy_register(const char *typename, const char *usbdevice_name, + USBDevice *(*usbdevice_init)(USBBus *bus, + const char *params)) +{ + if (usbdevice_name) { + LegacyUSBFactory *f = g_malloc0(sizeof(*f)); + f->name = typename; + f->usbdevice_name = usbdevice_name; + f->usbdevice_init = usbdevice_init; + legacy_usb_factory = g_slist_append(legacy_usb_factory, f); + } +} + +USBDevice *usb_create(USBBus *bus, const char *name) +{ + DeviceState *dev; + + dev = qdev_create(&bus->qbus, name); + return USB_DEVICE(dev); +} + +USBDevice *usb_create_simple(USBBus *bus, const char *name) +{ + USBDevice *dev = usb_create(bus, name); + int rc; + + if (!dev) { + error_report("Failed to create USB device '%s'", name); + return NULL; + } + rc = qdev_init(&dev->qdev); + if (rc < 0) { + error_report("Failed to initialize USB device '%s'", name); + return NULL; + } + return dev; +} + +static void usb_fill_port(USBPort *port, void *opaque, int index, + USBPortOps *ops, int speedmask) +{ + port->opaque = opaque; + port->index = index; + port->ops = ops; + port->speedmask = speedmask; + usb_port_location(port, NULL, index + 1); +} + +void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index, + USBPortOps *ops, int speedmask) +{ + usb_fill_port(port, opaque, index, ops, speedmask); + QTAILQ_INSERT_TAIL(&bus->free, port, next); + bus->nfree++; +} + +int usb_register_companion(const char *masterbus, USBPort *ports[], + uint32_t portcount, uint32_t firstport, + void *opaque, USBPortOps *ops, int speedmask) +{ + USBBus *bus; + int i; + + QTAILQ_FOREACH(bus, &busses, next) { + if (strcmp(bus->qbus.name, masterbus) == 0) { + break; + } + } + + if (!bus || !bus->ops->register_companion) { + qerror_report(QERR_INVALID_PARAMETER_VALUE, "masterbus", + "an USB masterbus"); + if (bus) { + error_printf_unless_qmp( + "USB bus '%s' does not allow companion controllers\n", + masterbus); + } + return -1; + } + + for (i = 0; i < portcount; i++) { + usb_fill_port(ports[i], opaque, i, ops, speedmask); + } + + return bus->ops->register_companion(bus, ports, portcount, firstport); +} + +void usb_port_location(USBPort *downstream, USBPort *upstream, int portnr) +{ + if (upstream) { + snprintf(downstream->path, sizeof(downstream->path), "%s.%d", + upstream->path, portnr); + } else { + snprintf(downstream->path, sizeof(downstream->path), "%d", portnr); + } +} + +void usb_unregister_port(USBBus *bus, USBPort *port) +{ + if (port->dev) + qdev_free(&port->dev->qdev); + QTAILQ_REMOVE(&bus->free, port, next); + bus->nfree--; +} + +int usb_claim_port(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port; + + assert(dev->port == NULL); + + if (dev->port_path) { + QTAILQ_FOREACH(port, &bus->free, next) { + if (strcmp(port->path, dev->port_path) == 0) { + break; + } + } + if (port == NULL) { + error_report("Error: usb port %s (bus %s) not found (in use?)", + dev->port_path, bus->qbus.name); + return -1; + } + } else { + if (bus->nfree == 1 && strcmp(object_get_typename(OBJECT(dev)), "usb-hub") != 0) { + /* Create a new hub and chain it on */ + usb_create_simple(bus, "usb-hub"); + } + if (bus->nfree == 0) { + error_report("Error: tried to attach usb device %s to a bus " + "with no free ports", dev->product_desc); + return -1; + } + port = QTAILQ_FIRST(&bus->free); + } + trace_usb_port_claim(bus->busnr, port->path); + + QTAILQ_REMOVE(&bus->free, port, next); + bus->nfree--; + + dev->port = port; + port->dev = dev; + + QTAILQ_INSERT_TAIL(&bus->used, port, next); + bus->nused++; + return 0; +} + +void usb_release_port(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port = dev->port; + + assert(port != NULL); + trace_usb_port_release(bus->busnr, port->path); + + QTAILQ_REMOVE(&bus->used, port, next); + bus->nused--; + + dev->port = NULL; + port->dev = NULL; + + QTAILQ_INSERT_TAIL(&bus->free, port, next); + bus->nfree++; +} + +int usb_device_attach(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port = dev->port; + + assert(port != NULL); + assert(!dev->attached); + trace_usb_port_attach(bus->busnr, port->path); + + if (!(port->speedmask & dev->speedmask)) { + error_report("Warning: speed mismatch trying to attach " + "usb device %s to bus %s", + dev->product_desc, bus->qbus.name); + return -1; + } + + dev->attached++; + usb_attach(port); + + return 0; +} + +int usb_device_detach(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port = dev->port; + + assert(port != NULL); + assert(dev->attached); + trace_usb_port_detach(bus->busnr, port->path); + + usb_detach(port); + dev->attached--; + return 0; +} + +int usb_device_delete_addr(int busnr, int addr) +{ + USBBus *bus; + USBPort *port; + USBDevice *dev; + + bus = usb_bus_find(busnr); + if (!bus) + return -1; + + QTAILQ_FOREACH(port, &bus->used, next) { + if (port->dev->addr == addr) + break; + } + if (!port) + return -1; + dev = port->dev; + + qdev_free(&dev->qdev); + return 0; +} + +static const char *usb_speed(unsigned int speed) +{ + static const char *txt[] = { + [ USB_SPEED_LOW ] = "1.5", + [ USB_SPEED_FULL ] = "12", + [ USB_SPEED_HIGH ] = "480", + [ USB_SPEED_SUPER ] = "5000", + }; + if (speed >= ARRAY_SIZE(txt)) + return "?"; + return txt[speed]; +} + +static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) +{ + USBDevice *dev = USB_DEVICE(qdev); + USBBus *bus = usb_bus_from_device(dev); + + monitor_printf(mon, "%*saddr %d.%d, port %s, speed %s, name %s%s\n", + indent, "", bus->busnr, dev->addr, + dev->port ? dev->port->path : "-", + usb_speed(dev->speed), dev->product_desc, + dev->attached ? ", attached" : ""); +} + +static char *usb_get_dev_path(DeviceState *qdev) +{ + USBDevice *dev = USB_DEVICE(qdev); + return g_strdup(dev->port->path); +} + +static char *usb_get_fw_dev_path(DeviceState *qdev) +{ + USBDevice *dev = USB_DEVICE(qdev); + char *fw_path, *in; + ssize_t pos = 0, fw_len; + long nr; + + fw_len = 32 + strlen(dev->port->path) * 6; + fw_path = g_malloc(fw_len); + in = dev->port->path; + while (fw_len - pos > 0) { + nr = strtol(in, &in, 10); + if (in[0] == '.') { + /* some hub between root port and device */ + pos += snprintf(fw_path + pos, fw_len - pos, "hub@%ld/", nr); + in++; + } else { + /* the device itself */ + pos += snprintf(fw_path + pos, fw_len - pos, "%s@%ld", + qdev_fw_name(qdev), nr); + break; + } + } + return fw_path; +} + +void usb_info(Monitor *mon) +{ + USBBus *bus; + USBDevice *dev; + USBPort *port; + + if (QTAILQ_EMPTY(&busses)) { + monitor_printf(mon, "USB support not enabled\n"); + return; + } + + QTAILQ_FOREACH(bus, &busses, next) { + QTAILQ_FOREACH(port, &bus->used, next) { + dev = port->dev; + if (!dev) + continue; + monitor_printf(mon, " Device %d.%d, Port %s, Speed %s Mb/s, Product %s\n", + bus->busnr, dev->addr, port->path, usb_speed(dev->speed), + dev->product_desc); + } + } +} + +/* handle legacy -usbdevice cmd line option */ +USBDevice *usbdevice_create(const char *cmdline) +{ + USBBus *bus = usb_bus_find(-1 /* any */); + LegacyUSBFactory *f = NULL; + GSList *i; + char driver[32]; + const char *params; + int len; + + params = strchr(cmdline,':'); + if (params) { + params++; + len = params - cmdline; + if (len > sizeof(driver)) + len = sizeof(driver); + pstrcpy(driver, len, cmdline); + } else { + params = ""; + pstrcpy(driver, sizeof(driver), cmdline); + } + + for (i = legacy_usb_factory; i; i = i->next) { + f = i->data; + if (strcmp(f->usbdevice_name, driver) == 0) { + break; + } + } + if (i == NULL) { +#if 0 + /* no error because some drivers are not converted (yet) */ + error_report("usbdevice %s not found", driver); +#endif + return NULL; + } + + if (!f->usbdevice_init) { + if (*params) { + error_report("usbdevice %s accepts no params", driver); + return NULL; + } + return usb_create_simple(bus, f->name); + } + return f->usbdevice_init(bus, params); +} + +static void usb_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->bus_info = &usb_bus_info; + k->init = usb_qdev_init; + k->unplug = qdev_simple_unplug_cb; + k->exit = usb_qdev_exit; +} + +static TypeInfo usb_device_type_info = { + .name = TYPE_USB_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(USBDevice), + .abstract = true, + .class_size = sizeof(USBDeviceClass), + .class_init = usb_device_class_init, +}; + +static void usb_register_types(void) +{ + type_register_static(&usb_device_type_info); +} + +type_init(usb_register_types) diff --git a/hw/usb/core.c b/hw/usb/core.c new file mode 100644 index 0000000..494989a --- /dev/null +++ b/hw/usb/core.c @@ -0,0 +1,663 @@ +/* + * QEMU USB emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * 2008 Generic packet handler rewrite by Max Krasnyansky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "hw/usb.h" +#include "iov.h" +#include "trace.h" + +void usb_attach(USBPort *port) +{ + USBDevice *dev = port->dev; + + assert(dev != NULL); + assert(dev->attached); + assert(dev->state == USB_STATE_NOTATTACHED); + port->ops->attach(port); + dev->state = USB_STATE_ATTACHED; + usb_device_handle_attach(dev); +} + +void usb_detach(USBPort *port) +{ + USBDevice *dev = port->dev; + + assert(dev != NULL); + assert(dev->state != USB_STATE_NOTATTACHED); + port->ops->detach(port); + dev->state = USB_STATE_NOTATTACHED; +} + +void usb_port_reset(USBPort *port) +{ + USBDevice *dev = port->dev; + + assert(dev != NULL); + usb_detach(port); + usb_attach(port); + usb_device_reset(dev); +} + +void usb_device_reset(USBDevice *dev) +{ + if (dev == NULL || !dev->attached) { + return; + } + dev->remote_wakeup = 0; + dev->addr = 0; + dev->state = USB_STATE_DEFAULT; + usb_device_handle_reset(dev); +} + +void usb_wakeup(USBEndpoint *ep) +{ + USBDevice *dev = ep->dev; + USBBus *bus = usb_bus_from_device(dev); + + if (dev->remote_wakeup && dev->port && dev->port->ops->wakeup) { + dev->port->ops->wakeup(dev->port); + } + if (bus->ops->wakeup_endpoint) { + bus->ops->wakeup_endpoint(bus, ep); + } +} + +/**********************/ + +/* generic USB device helpers (you are not forced to use them when + writing your USB device driver, but they help handling the + protocol) +*/ + +#define SETUP_STATE_IDLE 0 +#define SETUP_STATE_SETUP 1 +#define SETUP_STATE_DATA 2 +#define SETUP_STATE_ACK 3 +#define SETUP_STATE_PARAM 4 + +static int do_token_setup(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int ret = 0; + + if (p->iov.size != 8) { + return USB_RET_STALL; + } + + usb_packet_copy(p, s->setup_buf, p->iov.size); + s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + s->setup_index = 0; + + request = (s->setup_buf[0] << 8) | s->setup_buf[1]; + value = (s->setup_buf[3] << 8) | s->setup_buf[2]; + index = (s->setup_buf[5] << 8) | s->setup_buf[4]; + + if (s->setup_buf[0] & USB_DIR_IN) { + ret = usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (ret == USB_RET_ASYNC) { + s->setup_state = SETUP_STATE_SETUP; + return USB_RET_ASYNC; + } + if (ret < 0) + return ret; + + if (ret < s->setup_len) + s->setup_len = ret; + s->setup_state = SETUP_STATE_DATA; + } else { + if (s->setup_len > sizeof(s->data_buf)) { + fprintf(stderr, + "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", + s->setup_len, sizeof(s->data_buf)); + return USB_RET_STALL; + } + if (s->setup_len == 0) + s->setup_state = SETUP_STATE_ACK; + else + s->setup_state = SETUP_STATE_DATA; + } + + return ret; +} + +static int do_token_in(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int ret = 0; + + assert(p->ep->nr == 0); + + request = (s->setup_buf[0] << 8) | s->setup_buf[1]; + value = (s->setup_buf[3] << 8) | s->setup_buf[2]; + index = (s->setup_buf[5] << 8) | s->setup_buf[4]; + + switch(s->setup_state) { + case SETUP_STATE_ACK: + if (!(s->setup_buf[0] & USB_DIR_IN)) { + ret = usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (ret == USB_RET_ASYNC) { + return USB_RET_ASYNC; + } + s->setup_state = SETUP_STATE_IDLE; + if (ret > 0) + return 0; + return ret; + } + + /* return 0 byte */ + return 0; + + case SETUP_STATE_DATA: + if (s->setup_buf[0] & USB_DIR_IN) { + int len = s->setup_len - s->setup_index; + if (len > p->iov.size) { + len = p->iov.size; + } + usb_packet_copy(p, s->data_buf + s->setup_index, len); + s->setup_index += len; + if (s->setup_index >= s->setup_len) + s->setup_state = SETUP_STATE_ACK; + return len; + } + + s->setup_state = SETUP_STATE_IDLE; + return USB_RET_STALL; + + default: + return USB_RET_STALL; + } +} + +static int do_token_out(USBDevice *s, USBPacket *p) +{ + assert(p->ep->nr == 0); + + switch(s->setup_state) { + case SETUP_STATE_ACK: + if (s->setup_buf[0] & USB_DIR_IN) { + s->setup_state = SETUP_STATE_IDLE; + /* transfer OK */ + } else { + /* ignore additional output */ + } + return 0; + + case SETUP_STATE_DATA: + if (!(s->setup_buf[0] & USB_DIR_IN)) { + int len = s->setup_len - s->setup_index; + if (len > p->iov.size) { + len = p->iov.size; + } + usb_packet_copy(p, s->data_buf + s->setup_index, len); + s->setup_index += len; + if (s->setup_index >= s->setup_len) + s->setup_state = SETUP_STATE_ACK; + return len; + } + + s->setup_state = SETUP_STATE_IDLE; + return USB_RET_STALL; + + default: + return USB_RET_STALL; + } +} + +static int do_parameter(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int i, ret = 0; + + for (i = 0; i < 8; i++) { + s->setup_buf[i] = p->parameter >> (i*8); + } + + s->setup_state = SETUP_STATE_PARAM; + s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + s->setup_index = 0; + + request = (s->setup_buf[0] << 8) | s->setup_buf[1]; + value = (s->setup_buf[3] << 8) | s->setup_buf[2]; + index = (s->setup_buf[5] << 8) | s->setup_buf[4]; + + if (s->setup_len > sizeof(s->data_buf)) { + fprintf(stderr, + "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", + s->setup_len, sizeof(s->data_buf)); + return USB_RET_STALL; + } + + if (p->pid == USB_TOKEN_OUT) { + usb_packet_copy(p, s->data_buf, s->setup_len); + } + + ret = usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (ret < 0) { + return ret; + } + + if (ret < s->setup_len) { + s->setup_len = ret; + } + if (p->pid == USB_TOKEN_IN) { + usb_packet_copy(p, s->data_buf, s->setup_len); + } + + return ret; +} + +/* ctrl complete function for devices which use usb_generic_handle_packet and + may return USB_RET_ASYNC from their handle_control callback. Device code + which does this *must* call this function instead of the normal + usb_packet_complete to complete their async control packets. */ +void usb_generic_async_ctrl_complete(USBDevice *s, USBPacket *p) +{ + if (p->result < 0) { + s->setup_state = SETUP_STATE_IDLE; + } + + switch (s->setup_state) { + case SETUP_STATE_SETUP: + if (p->result < s->setup_len) { + s->setup_len = p->result; + } + s->setup_state = SETUP_STATE_DATA; + p->result = 8; + break; + + case SETUP_STATE_ACK: + s->setup_state = SETUP_STATE_IDLE; + p->result = 0; + break; + + case SETUP_STATE_PARAM: + if (p->result < s->setup_len) { + s->setup_len = p->result; + } + if (p->pid == USB_TOKEN_IN) { + p->result = 0; + usb_packet_copy(p, s->data_buf, s->setup_len); + } + break; + + default: + break; + } + usb_packet_complete(s, p); +} + +/* XXX: fix overflow */ +int set_usb_string(uint8_t *buf, const char *str) +{ + int len, i; + uint8_t *q; + + q = buf; + len = strlen(str); + *q++ = 2 * len + 2; + *q++ = 3; + for(i = 0; i < len; i++) { + *q++ = str[i]; + *q++ = 0; + } + return q - buf; +} + +USBDevice *usb_find_device(USBPort *port, uint8_t addr) +{ + USBDevice *dev = port->dev; + + if (dev == NULL || !dev->attached || dev->state != USB_STATE_DEFAULT) { + return NULL; + } + if (dev->addr == addr) { + return dev; + } + return usb_device_find_device(dev, addr); +} + +static int usb_process_one(USBPacket *p) +{ + USBDevice *dev = p->ep->dev; + + if (p->ep->nr == 0) { + /* control pipe */ + if (p->parameter) { + return do_parameter(dev, p); + } + switch (p->pid) { + case USB_TOKEN_SETUP: + return do_token_setup(dev, p); + case USB_TOKEN_IN: + return do_token_in(dev, p); + case USB_TOKEN_OUT: + return do_token_out(dev, p); + default: + return USB_RET_STALL; + } + } else { + /* data pipe */ + return usb_device_handle_data(dev, p); + } +} + +/* Hand over a packet to a device for processing. Return value + USB_RET_ASYNC indicates the processing isn't finished yet, the + driver will call usb_packet_complete() when done processing it. */ +int usb_handle_packet(USBDevice *dev, USBPacket *p) +{ + int ret; + + if (dev == NULL) { + return USB_RET_NODEV; + } + assert(dev == p->ep->dev); + assert(dev->state == USB_STATE_DEFAULT); + assert(p->state == USB_PACKET_SETUP); + assert(p->ep != NULL); + + if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline) { + ret = usb_process_one(p); + if (ret == USB_RET_ASYNC) { + usb_packet_set_state(p, USB_PACKET_ASYNC); + QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue); + } else { + p->result = ret; + usb_packet_set_state(p, USB_PACKET_COMPLETE); + } + } else { + ret = USB_RET_ASYNC; + usb_packet_set_state(p, USB_PACKET_QUEUED); + QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue); + } + return ret; +} + +/* Notify the controller that an async packet is complete. This should only + be called for packets previously deferred by returning USB_RET_ASYNC from + handle_packet. */ +void usb_packet_complete(USBDevice *dev, USBPacket *p) +{ + USBEndpoint *ep = p->ep; + int ret; + + assert(p->state == USB_PACKET_ASYNC); + assert(QTAILQ_FIRST(&ep->queue) == p); + usb_packet_set_state(p, USB_PACKET_COMPLETE); + QTAILQ_REMOVE(&ep->queue, p, queue); + dev->port->ops->complete(dev->port, p); + + while (!QTAILQ_EMPTY(&ep->queue)) { + p = QTAILQ_FIRST(&ep->queue); + if (p->state == USB_PACKET_ASYNC) { + break; + } + assert(p->state == USB_PACKET_QUEUED); + ret = usb_process_one(p); + if (ret == USB_RET_ASYNC) { + usb_packet_set_state(p, USB_PACKET_ASYNC); + break; + } + p->result = ret; + usb_packet_set_state(p, USB_PACKET_COMPLETE); + QTAILQ_REMOVE(&ep->queue, p, queue); + dev->port->ops->complete(dev->port, p); + } +} + +/* Cancel an active packet. The packed must have been deferred by + returning USB_RET_ASYNC from handle_packet, and not yet + completed. */ +void usb_cancel_packet(USBPacket * p) +{ + bool callback = (p->state == USB_PACKET_ASYNC); + assert(usb_packet_is_inflight(p)); + usb_packet_set_state(p, USB_PACKET_CANCELED); + QTAILQ_REMOVE(&p->ep->queue, p, queue); + if (callback) { + usb_device_cancel_packet(p->ep->dev, p); + } +} + + +void usb_packet_init(USBPacket *p) +{ + qemu_iovec_init(&p->iov, 1); +} + +void usb_packet_set_state(USBPacket *p, USBPacketState state) +{ + static const char *name[] = { + [USB_PACKET_UNDEFINED] = "undef", + [USB_PACKET_SETUP] = "setup", + [USB_PACKET_QUEUED] = "queued", + [USB_PACKET_ASYNC] = "async", + [USB_PACKET_COMPLETE] = "complete", + [USB_PACKET_CANCELED] = "canceled", + }; + USBDevice *dev = p->ep->dev; + USBBus *bus = usb_bus_from_device(dev); + + trace_usb_packet_state_change(bus->busnr, dev->port->path, p->ep->nr, + p, name[p->state], name[state]); + p->state = state; +} + +void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep) +{ + assert(!usb_packet_is_inflight(p)); + p->pid = pid; + p->ep = ep; + p->result = 0; + p->parameter = 0; + qemu_iovec_reset(&p->iov); + usb_packet_set_state(p, USB_PACKET_SETUP); +} + +void usb_packet_addbuf(USBPacket *p, void *ptr, size_t len) +{ + qemu_iovec_add(&p->iov, ptr, len); +} + +void usb_packet_copy(USBPacket *p, void *ptr, size_t bytes) +{ + assert(p->result >= 0); + assert(p->result + bytes <= p->iov.size); + switch (p->pid) { + case USB_TOKEN_SETUP: + case USB_TOKEN_OUT: + iov_to_buf(p->iov.iov, p->iov.niov, ptr, p->result, bytes); + break; + case USB_TOKEN_IN: + iov_from_buf(p->iov.iov, p->iov.niov, ptr, p->result, bytes); + break; + default: + fprintf(stderr, "%s: invalid pid: %x\n", __func__, p->pid); + abort(); + } + p->result += bytes; +} + +void usb_packet_skip(USBPacket *p, size_t bytes) +{ + assert(p->result >= 0); + assert(p->result + bytes <= p->iov.size); + if (p->pid == USB_TOKEN_IN) { + iov_clear(p->iov.iov, p->iov.niov, p->result, bytes); + } + p->result += bytes; +} + +void usb_packet_cleanup(USBPacket *p) +{ + assert(!usb_packet_is_inflight(p)); + qemu_iovec_destroy(&p->iov); +} + +void usb_ep_init(USBDevice *dev) +{ + int ep; + + dev->ep_ctl.nr = 0; + dev->ep_ctl.type = USB_ENDPOINT_XFER_CONTROL; + dev->ep_ctl.ifnum = 0; + dev->ep_ctl.dev = dev; + dev->ep_ctl.pipeline = false; + QTAILQ_INIT(&dev->ep_ctl.queue); + for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) { + dev->ep_in[ep].nr = ep + 1; + dev->ep_out[ep].nr = ep + 1; + dev->ep_in[ep].pid = USB_TOKEN_IN; + dev->ep_out[ep].pid = USB_TOKEN_OUT; + dev->ep_in[ep].type = USB_ENDPOINT_XFER_INVALID; + dev->ep_out[ep].type = USB_ENDPOINT_XFER_INVALID; + dev->ep_in[ep].ifnum = 0; + dev->ep_out[ep].ifnum = 0; + dev->ep_in[ep].dev = dev; + dev->ep_out[ep].dev = dev; + dev->ep_in[ep].pipeline = false; + dev->ep_out[ep].pipeline = false; + QTAILQ_INIT(&dev->ep_in[ep].queue); + QTAILQ_INIT(&dev->ep_out[ep].queue); + } +} + +void usb_ep_dump(USBDevice *dev) +{ + static const char *tname[] = { + [USB_ENDPOINT_XFER_CONTROL] = "control", + [USB_ENDPOINT_XFER_ISOC] = "isoc", + [USB_ENDPOINT_XFER_BULK] = "bulk", + [USB_ENDPOINT_XFER_INT] = "int", + }; + int ifnum, ep, first; + + fprintf(stderr, "Device \"%s\", config %d\n", + dev->product_desc, dev->configuration); + for (ifnum = 0; ifnum < 16; ifnum++) { + first = 1; + for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) { + if (dev->ep_in[ep].type != USB_ENDPOINT_XFER_INVALID && + dev->ep_in[ep].ifnum == ifnum) { + if (first) { + first = 0; + fprintf(stderr, " Interface %d, alternative %d\n", + ifnum, dev->altsetting[ifnum]); + } + fprintf(stderr, " Endpoint %d, IN, %s, %d max\n", ep, + tname[dev->ep_in[ep].type], + dev->ep_in[ep].max_packet_size); + } + if (dev->ep_out[ep].type != USB_ENDPOINT_XFER_INVALID && + dev->ep_out[ep].ifnum == ifnum) { + if (first) { + first = 0; + fprintf(stderr, " Interface %d, alternative %d\n", + ifnum, dev->altsetting[ifnum]); + } + fprintf(stderr, " Endpoint %d, OUT, %s, %d max\n", ep, + tname[dev->ep_out[ep].type], + dev->ep_out[ep].max_packet_size); + } + } + } + fprintf(stderr, "--\n"); +} + +struct USBEndpoint *usb_ep_get(USBDevice *dev, int pid, int ep) +{ + struct USBEndpoint *eps; + + if (dev == NULL) { + return NULL; + } + eps = (pid == USB_TOKEN_IN) ? dev->ep_in : dev->ep_out; + if (ep == 0) { + return &dev->ep_ctl; + } + assert(pid == USB_TOKEN_IN || pid == USB_TOKEN_OUT); + assert(ep > 0 && ep <= USB_MAX_ENDPOINTS); + return eps + ep - 1; +} + +uint8_t usb_ep_get_type(USBDevice *dev, int pid, int ep) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + return uep->type; +} + +void usb_ep_set_type(USBDevice *dev, int pid, int ep, uint8_t type) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + uep->type = type; +} + +uint8_t usb_ep_get_ifnum(USBDevice *dev, int pid, int ep) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + return uep->ifnum; +} + +void usb_ep_set_ifnum(USBDevice *dev, int pid, int ep, uint8_t ifnum) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + uep->ifnum = ifnum; +} + +void usb_ep_set_max_packet_size(USBDevice *dev, int pid, int ep, + uint16_t raw) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + int size, microframes; + + size = raw & 0x7ff; + switch ((raw >> 11) & 3) { + case 1: + microframes = 2; + break; + case 2: + microframes = 3; + break; + default: + microframes = 1; + break; + } + uep->max_packet_size = size * microframes; +} + +int usb_ep_get_max_packet_size(USBDevice *dev, int pid, int ep) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + return uep->max_packet_size; +} + +void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + uep->pipeline = enabled; +} diff --git a/hw/usb/desc.c b/hw/usb/desc.c new file mode 100644 index 0000000..9847a75 --- /dev/null +++ b/hw/usb/desc.c @@ -0,0 +1,601 @@ +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "trace.h" + +/* ------------------------------------------------------------------ */ + +static uint8_t usb_lo(uint16_t val) +{ + return val & 0xff; +} + +static uint8_t usb_hi(uint16_t val) +{ + return (val >> 8) & 0xff; +} + +int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, + uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x12; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_DEVICE; + + dest[0x02] = usb_lo(dev->bcdUSB); + dest[0x03] = usb_hi(dev->bcdUSB); + dest[0x04] = dev->bDeviceClass; + dest[0x05] = dev->bDeviceSubClass; + dest[0x06] = dev->bDeviceProtocol; + dest[0x07] = dev->bMaxPacketSize0; + + dest[0x08] = usb_lo(id->idVendor); + dest[0x09] = usb_hi(id->idVendor); + dest[0x0a] = usb_lo(id->idProduct); + dest[0x0b] = usb_hi(id->idProduct); + dest[0x0c] = usb_lo(id->bcdDevice); + dest[0x0d] = usb_hi(id->bcdDevice); + dest[0x0e] = id->iManufacturer; + dest[0x0f] = id->iProduct; + dest[0x10] = id->iSerialNumber; + + dest[0x11] = dev->bNumConfigurations; + + return bLength; +} + +int usb_desc_device_qualifier(const USBDescDevice *dev, + uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x0a; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_DEVICE_QUALIFIER; + + dest[0x02] = usb_lo(dev->bcdUSB); + dest[0x03] = usb_hi(dev->bcdUSB); + dest[0x04] = dev->bDeviceClass; + dest[0x05] = dev->bDeviceSubClass; + dest[0x06] = dev->bDeviceProtocol; + dest[0x07] = dev->bMaxPacketSize0; + dest[0x08] = dev->bNumConfigurations; + dest[0x09] = 0; /* reserved */ + + return bLength; +} + +int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x09; + uint16_t wTotalLength = 0; + int i, rc; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_CONFIG; + dest[0x04] = conf->bNumInterfaces; + dest[0x05] = conf->bConfigurationValue; + dest[0x06] = conf->iConfiguration; + dest[0x07] = conf->bmAttributes; + dest[0x08] = conf->bMaxPower; + wTotalLength += bLength; + + /* handle grouped interfaces if any*/ + for (i = 0; i < conf->nif_groups; i++) { + rc = usb_desc_iface_group(&(conf->if_groups[i]), + dest + wTotalLength, + len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + } + + /* handle normal (ungrouped / no IAD) interfaces if any */ + for (i = 0; i < conf->nif; i++) { + rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + } + + dest[0x02] = usb_lo(wTotalLength); + dest[0x03] = usb_hi(wTotalLength); + return wTotalLength; +} + +int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, + size_t len) +{ + int pos = 0; + int i = 0; + + /* handle interface association descriptor */ + uint8_t bLength = 0x08; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_INTERFACE_ASSOC; + dest[0x02] = iad->bFirstInterface; + dest[0x03] = iad->bInterfaceCount; + dest[0x04] = iad->bFunctionClass; + dest[0x05] = iad->bFunctionSubClass; + dest[0x06] = iad->bFunctionProtocol; + dest[0x07] = iad->iFunction; + pos += bLength; + + /* handle associated interfaces in this group */ + for (i = 0; i < iad->nif; i++) { + int rc = usb_desc_iface(&(iad->ifs[i]), dest + pos, len - pos); + if (rc < 0) { + return rc; + } + pos += rc; + } + + return pos; +} + +int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x09; + int i, rc, pos = 0; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_INTERFACE; + dest[0x02] = iface->bInterfaceNumber; + dest[0x03] = iface->bAlternateSetting; + dest[0x04] = iface->bNumEndpoints; + dest[0x05] = iface->bInterfaceClass; + dest[0x06] = iface->bInterfaceSubClass; + dest[0x07] = iface->bInterfaceProtocol; + dest[0x08] = iface->iInterface; + pos += bLength; + + for (i = 0; i < iface->ndesc; i++) { + rc = usb_desc_other(iface->descs + i, dest + pos, len - pos); + if (rc < 0) { + return rc; + } + pos += rc; + } + + for (i = 0; i < iface->bNumEndpoints; i++) { + rc = usb_desc_endpoint(iface->eps + i, dest + pos, len - pos); + if (rc < 0) { + return rc; + } + pos += rc; + } + + return pos; +} + +int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len) +{ + uint8_t bLength = ep->is_audio ? 0x09 : 0x07; + uint8_t extralen = ep->extra ? ep->extra[0] : 0; + + if (len < bLength + extralen) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_ENDPOINT; + dest[0x02] = ep->bEndpointAddress; + dest[0x03] = ep->bmAttributes; + dest[0x04] = usb_lo(ep->wMaxPacketSize); + dest[0x05] = usb_hi(ep->wMaxPacketSize); + dest[0x06] = ep->bInterval; + if (ep->is_audio) { + dest[0x07] = ep->bRefresh; + dest[0x08] = ep->bSynchAddress; + } + if (ep->extra) { + memcpy(dest + bLength, ep->extra, extralen); + } + + return bLength + extralen; +} + +int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len) +{ + int bLength = desc->length ? desc->length : desc->data[0]; + + if (len < bLength) { + return -1; + } + + memcpy(dest, desc->data, bLength); + return bLength; +} + +/* ------------------------------------------------------------------ */ + +static void usb_desc_ep_init(USBDevice *dev) +{ + const USBDescIface *iface; + int i, e, pid, ep; + + usb_ep_init(dev); + for (i = 0; i < dev->ninterfaces; i++) { + iface = dev->ifaces[i]; + if (iface == NULL) { + continue; + } + for (e = 0; e < iface->bNumEndpoints; e++) { + pid = (iface->eps[e].bEndpointAddress & USB_DIR_IN) ? + USB_TOKEN_IN : USB_TOKEN_OUT; + ep = iface->eps[e].bEndpointAddress & 0x0f; + usb_ep_set_type(dev, pid, ep, iface->eps[e].bmAttributes & 0x03); + usb_ep_set_ifnum(dev, pid, ep, iface->bInterfaceNumber); + usb_ep_set_max_packet_size(dev, pid, ep, + iface->eps[e].wMaxPacketSize); + } + } +} + +static const USBDescIface *usb_desc_find_interface(USBDevice *dev, + int nif, int alt) +{ + const USBDescIface *iface; + int g, i; + + if (!dev->config) { + return NULL; + } + for (g = 0; g < dev->config->nif_groups; g++) { + for (i = 0; i < dev->config->if_groups[g].nif; i++) { + iface = &dev->config->if_groups[g].ifs[i]; + if (iface->bInterfaceNumber == nif && + iface->bAlternateSetting == alt) { + return iface; + } + } + } + for (i = 0; i < dev->config->nif; i++) { + iface = &dev->config->ifs[i]; + if (iface->bInterfaceNumber == nif && + iface->bAlternateSetting == alt) { + return iface; + } + } + return NULL; +} + +static int usb_desc_set_interface(USBDevice *dev, int index, int value) +{ + const USBDescIface *iface; + int old; + + iface = usb_desc_find_interface(dev, index, value); + if (iface == NULL) { + return -1; + } + + old = dev->altsetting[index]; + dev->altsetting[index] = value; + dev->ifaces[index] = iface; + usb_desc_ep_init(dev); + + if (old != value) { + usb_device_set_interface(dev, index, old, value); + } + return 0; +} + +static int usb_desc_set_config(USBDevice *dev, int value) +{ + int i; + + if (value == 0) { + dev->configuration = 0; + dev->ninterfaces = 0; + dev->config = NULL; + } else { + for (i = 0; i < dev->device->bNumConfigurations; i++) { + if (dev->device->confs[i].bConfigurationValue == value) { + dev->configuration = value; + dev->ninterfaces = dev->device->confs[i].bNumInterfaces; + dev->config = dev->device->confs + i; + assert(dev->ninterfaces <= USB_MAX_INTERFACES); + } + } + if (i < dev->device->bNumConfigurations) { + return -1; + } + } + + for (i = 0; i < dev->ninterfaces; i++) { + usb_desc_set_interface(dev, i, 0); + } + for (; i < USB_MAX_INTERFACES; i++) { + dev->altsetting[i] = 0; + dev->ifaces[i] = NULL; + } + + return 0; +} + +static void usb_desc_setdefaults(USBDevice *dev) +{ + const USBDesc *desc = usb_device_get_usb_desc(dev); + + assert(desc != NULL); + switch (dev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + dev->device = desc->full; + break; + case USB_SPEED_HIGH: + dev->device = desc->high; + break; + } + usb_desc_set_config(dev, 0); +} + +void usb_desc_init(USBDevice *dev) +{ + const USBDesc *desc = usb_device_get_usb_desc(dev); + + assert(desc != NULL); + dev->speed = USB_SPEED_FULL; + dev->speedmask = 0; + if (desc->full) { + dev->speedmask |= USB_SPEED_MASK_FULL; + } + if (desc->high) { + dev->speedmask |= USB_SPEED_MASK_HIGH; + } + usb_desc_setdefaults(dev); +} + +void usb_desc_attach(USBDevice *dev) +{ + const USBDesc *desc = usb_device_get_usb_desc(dev); + + assert(desc != NULL); + if (desc->high && (dev->port->speedmask & USB_SPEED_MASK_HIGH)) { + dev->speed = USB_SPEED_HIGH; + } else if (desc->full && (dev->port->speedmask & USB_SPEED_MASK_FULL)) { + dev->speed = USB_SPEED_FULL; + } else { + fprintf(stderr, "usb: port/device speed mismatch for \"%s\"\n", + usb_device_get_product_desc(dev)); + return; + } + usb_desc_setdefaults(dev); +} + +void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str) +{ + USBDescString *s; + + QLIST_FOREACH(s, &dev->strings, next) { + if (s->index == index) { + break; + } + } + if (s == NULL) { + s = g_malloc0(sizeof(*s)); + s->index = index; + QLIST_INSERT_HEAD(&dev->strings, s, next); + } + g_free(s->str); + s->str = g_strdup(str); +} + +const char *usb_desc_get_string(USBDevice *dev, uint8_t index) +{ + USBDescString *s; + + QLIST_FOREACH(s, &dev->strings, next) { + if (s->index == index) { + return s->str; + } + } + return NULL; +} + +int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len) +{ + uint8_t bLength, pos, i; + const char *str; + + if (len < 4) { + return -1; + } + + if (index == 0) { + /* language ids */ + dest[0] = 4; + dest[1] = USB_DT_STRING; + dest[2] = 0x09; + dest[3] = 0x04; + return 4; + } + + str = usb_desc_get_string(dev, index); + if (str == NULL) { + str = usb_device_get_usb_desc(dev)->str[index]; + if (str == NULL) { + return 0; + } + } + + bLength = strlen(str) * 2 + 2; + dest[0] = bLength; + dest[1] = USB_DT_STRING; + i = 0; pos = 2; + while (pos+1 < bLength && pos+1 < len) { + dest[pos++] = str[i++]; + dest[pos++] = 0; + } + return pos; +} + +int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len) +{ + const USBDesc *desc = usb_device_get_usb_desc(dev); + const USBDescDevice *other_dev; + uint8_t buf[256]; + uint8_t type = value >> 8; + uint8_t index = value & 0xff; + int ret = -1; + + if (dev->speed == USB_SPEED_HIGH) { + other_dev = usb_device_get_usb_desc(dev)->full; + } else { + other_dev = usb_device_get_usb_desc(dev)->high; + } + + switch(type) { + case USB_DT_DEVICE: + ret = usb_desc_device(&desc->id, dev->device, buf, sizeof(buf)); + trace_usb_desc_device(dev->addr, len, ret); + break; + case USB_DT_CONFIG: + if (index < dev->device->bNumConfigurations) { + ret = usb_desc_config(dev->device->confs + index, buf, sizeof(buf)); + } + trace_usb_desc_config(dev->addr, index, len, ret); + break; + case USB_DT_STRING: + ret = usb_desc_string(dev, index, buf, sizeof(buf)); + trace_usb_desc_string(dev->addr, index, len, ret); + break; + + case USB_DT_DEVICE_QUALIFIER: + if (other_dev != NULL) { + ret = usb_desc_device_qualifier(other_dev, buf, sizeof(buf)); + } + trace_usb_desc_device_qualifier(dev->addr, len, ret); + break; + case USB_DT_OTHER_SPEED_CONFIG: + if (other_dev != NULL && index < other_dev->bNumConfigurations) { + ret = usb_desc_config(other_dev->confs + index, buf, sizeof(buf)); + buf[0x01] = USB_DT_OTHER_SPEED_CONFIG; + } + trace_usb_desc_other_speed_config(dev->addr, index, len, ret); + break; + + case USB_DT_DEBUG: + /* ignore silently */ + break; + + default: + fprintf(stderr, "%s: %d unknown type %d (len %zd)\n", __FUNCTION__, + dev->addr, type, len); + break; + } + + if (ret > 0) { + if (ret > len) { + ret = len; + } + memcpy(dest, buf, ret); + } + return ret; +} + +int usb_desc_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + const USBDesc *desc = usb_device_get_usb_desc(dev); + int ret = -1; + + assert(desc != NULL); + switch(request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + trace_usb_set_addr(dev->addr); + ret = 0; + break; + + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + ret = usb_desc_get_descriptor(dev, value, data, length); + break; + + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + /* + * 9.4.2: 0 should be returned if the device is unconfigured, otherwise + * the non zero value of bConfigurationValue. + */ + data[0] = dev->config ? dev->config->bConfigurationValue : 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + ret = usb_desc_set_config(dev, value); + trace_usb_set_config(dev->addr, value, ret); + break; + + case DeviceRequest | USB_REQ_GET_STATUS: { + const USBDescConfig *config = dev->config ? + dev->config : &dev->device->confs[0]; + + data[0] = 0; + /* + * Default state: Device behavior when this request is received while + * the device is in the Default state is not specified. + * We return the same value that a configured device would return if + * it used the first configuration. + */ + if (config->bmAttributes & 0x40) { + data[0] |= 1 << USB_DEVICE_SELF_POWERED; + } + if (dev->remote_wakeup) { + data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP; + } + data[1] = 0x00; + ret = 2; + break; + } + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + ret = 0; + } + trace_usb_clear_device_feature(dev->addr, value, ret); + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + ret = 0; + } + trace_usb_set_device_feature(dev->addr, value, ret); + break; + + case InterfaceRequest | USB_REQ_GET_INTERFACE: + if (index < 0 || index >= dev->ninterfaces) { + break; + } + data[0] = dev->altsetting[index]; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = usb_desc_set_interface(dev, index, value); + trace_usb_set_interface(dev->addr, index, value, ret); + break; + + } + return ret; +} diff --git a/hw/usb/desc.h b/hw/usb/desc.h new file mode 100644 index 0000000..d6e07ea --- /dev/null +++ b/hw/usb/desc.h @@ -0,0 +1,117 @@ +#ifndef QEMU_HW_USB_DESC_H +#define QEMU_HW_USB_DESC_H + +#include <inttypes.h> + +struct USBDescID { + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; +}; + +struct USBDescDevice { + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint8_t bNumConfigurations; + + const USBDescConfig *confs; +}; + +struct USBDescConfig { + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; + + /* grouped interfaces */ + uint8_t nif_groups; + const USBDescIfaceAssoc *if_groups; + + /* "normal" interfaces */ + uint8_t nif; + const USBDescIface *ifs; +}; + +/* conceptually an Interface Association Descriptor, and releated interfaces */ +struct USBDescIfaceAssoc { + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; + uint8_t bFunctionSubClass; + uint8_t bFunctionProtocol; + uint8_t iFunction; + + uint8_t nif; + const USBDescIface *ifs; +}; + +struct USBDescIface { + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + + uint8_t ndesc; + USBDescOther *descs; + USBDescEndpoint *eps; +}; + +struct USBDescEndpoint { + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + uint8_t bRefresh; + uint8_t bSynchAddress; + + uint8_t is_audio; /* has bRefresh + bSynchAddress */ + uint8_t *extra; +}; + +struct USBDescOther { + uint8_t length; + const uint8_t *data; +}; + +typedef const char *USBDescStrings[256]; + +struct USBDesc { + USBDescID id; + const USBDescDevice *full; + const USBDescDevice *high; + const char* const *str; +}; + +/* generate usb packages from structs */ +int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, + uint8_t *dest, size_t len); +int usb_desc_device_qualifier(const USBDescDevice *dev, + uint8_t *dest, size_t len); +int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len); +int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, + size_t len); +int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len); +int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len); +int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len); + +/* control message emulation helpers */ +void usb_desc_init(USBDevice *dev); +void usb_desc_attach(USBDevice *dev); +void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str); +const char *usb_desc_get_string(USBDevice *dev, uint8_t index); +int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len); +int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len); +int usb_desc_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data); + +#endif /* QEMU_HW_USB_DESC_H */ diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c new file mode 100644 index 0000000..426b95c --- /dev/null +++ b/hw/usb/dev-audio.c @@ -0,0 +1,714 @@ +/* + * QEMU USB audio device + * + * written by: + * H. Peter Anvin <hpa@linux.intel.com> + * Gerd Hoffmann <kraxel@redhat.com> + * + * lousely based on usb net device code which is: + * + * Copyright (c) 2006 Thomas Sailer + * Copyright (c) 2008 Andrzej Zaborowski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/hw.h" +#include "hw/audiodev.h" +#include "audio/audio.h" + +#define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ +#define USBAUDIO_PRODUCT_NUM 0x0002 + +#define DEV_CONFIG_VALUE 1 /* The one and only */ + +/* Descriptor subtypes for AC interfaces */ +#define DST_AC_HEADER 1 +#define DST_AC_INPUT_TERMINAL 2 +#define DST_AC_OUTPUT_TERMINAL 3 +#define DST_AC_FEATURE_UNIT 6 +/* Descriptor subtypes for AS interfaces */ +#define DST_AS_GENERAL 1 +#define DST_AS_FORMAT_TYPE 2 +/* Descriptor subtypes for endpoints */ +#define DST_EP_GENERAL 1 + +enum usb_audio_strings { + STRING_NULL, + STRING_MANUFACTURER, + STRING_PRODUCT, + STRING_SERIALNUMBER, + STRING_CONFIG, + STRING_USBAUDIO_CONTROL, + STRING_INPUT_TERMINAL, + STRING_FEATURE_UNIT, + STRING_OUTPUT_TERMINAL, + STRING_NULL_STREAM, + STRING_REAL_STREAM, +}; + +static const USBDescStrings usb_audio_stringtable = { + [STRING_MANUFACTURER] = "QEMU", + [STRING_PRODUCT] = "QEMU USB Audio", + [STRING_SERIALNUMBER] = "1", + [STRING_CONFIG] = "Audio Configuration", + [STRING_USBAUDIO_CONTROL] = "Audio Device", + [STRING_INPUT_TERMINAL] = "Audio Output Pipe", + [STRING_FEATURE_UNIT] = "Audio Output Volume Control", + [STRING_OUTPUT_TERMINAL] = "Audio Output Terminal", + [STRING_NULL_STREAM] = "Audio Output - Disabled", + [STRING_REAL_STREAM] = "Audio Output - 48 kHz Stereo", +}; + +#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff) +#define U24(x) U16(x), (((x) >> 16) & 0xff) +#define U32(x) U24(x), (((x) >> 24) & 0xff) + +/* + * A Basic Audio Device uses these specific values + */ +#define USBAUDIO_PACKET_SIZE 192 +#define USBAUDIO_SAMPLE_RATE 48000 +#define USBAUDIO_PACKET_INTERVAL 1 + +static const USBDescIface desc_iface[] = { + { + .bInterfaceNumber = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL, + .bInterfaceProtocol = 0x04, + .iInterface = STRING_USBAUDIO_CONTROL, + .ndesc = 4, + .descs = (USBDescOther[]) { + { + /* Headphone Class-Specific AC Interface Header Descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AC_HEADER, /* u8 bDescriptorSubtype */ + U16(0x0100), /* u16 bcdADC */ + U16(0x2b), /* u16 wTotalLength */ + 0x01, /* u8 bInCollection */ + 0x01, /* u8 baInterfaceNr */ + } + },{ + /* Generic Stereo Input Terminal ID1 Descriptor */ + .data = (uint8_t[]) { + 0x0c, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AC_INPUT_TERMINAL, /* u8 bDescriptorSubtype */ + 0x01, /* u8 bTerminalID */ + U16(0x0101), /* u16 wTerminalType */ + 0x00, /* u8 bAssocTerminal */ + 0x02, /* u16 bNrChannels */ + U16(0x0003), /* u16 wChannelConfig */ + 0x00, /* u8 iChannelNames */ + STRING_INPUT_TERMINAL, /* u8 iTerminal */ + } + },{ + /* Generic Stereo Feature Unit ID2 Descriptor */ + .data = (uint8_t[]) { + 0x0d, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AC_FEATURE_UNIT, /* u8 bDescriptorSubtype */ + 0x02, /* u8 bUnitID */ + 0x01, /* u8 bSourceID */ + 0x02, /* u8 bControlSize */ + U16(0x0001), /* u16 bmaControls(0) */ + U16(0x0002), /* u16 bmaControls(1) */ + U16(0x0002), /* u16 bmaControls(2) */ + STRING_FEATURE_UNIT, /* u8 iFeature */ + } + },{ + /* Headphone Ouptut Terminal ID3 Descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */ + 0x03, /* u8 bUnitID */ + U16(0x0301), /* u16 wTerminalType (SPK) */ + 0x00, /* u8 bAssocTerminal */ + 0x02, /* u8 bSourceID */ + STRING_OUTPUT_TERMINAL, /* u8 iTerminal */ + } + } + }, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, + .iInterface = STRING_NULL_STREAM, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, + .iInterface = STRING_REAL_STREAM, + .ndesc = 2, + .descs = (USBDescOther[]) { + { + /* Headphone Class-specific AS General Interface Descriptor */ + .data = (uint8_t[]) { + 0x07, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AS_GENERAL, /* u8 bDescriptorSubtype */ + 0x01, /* u8 bTerminalLink */ + 0x00, /* u8 bDelay */ + 0x01, 0x00, /* u16 wFormatTag */ + } + },{ + /* Headphone Type I Format Type Descriptor */ + .data = (uint8_t[]) { + 0x0b, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */ + 0x01, /* u8 bFormatType */ + 0x02, /* u8 bNrChannels */ + 0x02, /* u8 bSubFrameSize */ + 0x10, /* u8 bBitResolution */ + 0x01, /* u8 bSamFreqType */ + U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */ + } + } + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | 0x01, + .bmAttributes = 0x0d, + .wMaxPacketSize = USBAUDIO_PACKET_SIZE, + .bInterval = 1, + .is_audio = 1, + /* Stereo Headphone Class-specific + AS Audio Data Endpoint Descriptor */ + .extra = (uint8_t[]) { + 0x07, /* u8 bLength */ + USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */ + DST_EP_GENERAL, /* u8 bDescriptorSubtype */ + 0x00, /* u8 bmAttributes */ + 0x00, /* u8 bLockDelayUnits */ + U16(0x0000), /* u16 wLockDelay */ + }, + }, + } + } +}; + +static const USBDescDevice desc_device = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 2, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = STRING_CONFIG, + .bmAttributes = 0xc0, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface), + .ifs = desc_iface, + }, + }, +}; + +static const USBDesc desc_audio = { + .id = { + .idVendor = USBAUDIO_VENDOR_NUM, + .idProduct = USBAUDIO_PRODUCT_NUM, + .bcdDevice = 0, + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIALNUMBER, + }, + .full = &desc_device, + .str = usb_audio_stringtable, +}; + +/* + * A USB audio device supports an arbitrary number of alternate + * interface settings for each interface. Each corresponds to a block + * diagram of parameterized blocks. This can thus refer to things like + * number of channels, data rates, or in fact completely different + * block diagrams. Alternative setting 0 is always the null block diagram, + * which is used by a disabled device. + */ +enum usb_audio_altset { + ALTSET_OFF = 0x00, /* No endpoint */ + ALTSET_ON = 0x01, /* Single endpoint */ +}; + +/* + * Class-specific control requests + */ +#define CR_SET_CUR 0x01 +#define CR_GET_CUR 0x81 +#define CR_SET_MIN 0x02 +#define CR_GET_MIN 0x82 +#define CR_SET_MAX 0x03 +#define CR_GET_MAX 0x83 +#define CR_SET_RES 0x04 +#define CR_GET_RES 0x84 +#define CR_SET_MEM 0x05 +#define CR_GET_MEM 0x85 +#define CR_GET_STAT 0xff + +/* + * Feature Unit Control Selectors + */ +#define MUTE_CONTROL 0x01 +#define VOLUME_CONTROL 0x02 +#define BASS_CONTROL 0x03 +#define MID_CONTROL 0x04 +#define TREBLE_CONTROL 0x05 +#define GRAPHIC_EQUALIZER_CONTROL 0x06 +#define AUTOMATIC_GAIN_CONTROL 0x07 +#define DELAY_CONTROL 0x08 +#define BASS_BOOST_CONTROL 0x09 +#define LOUDNESS_CONTROL 0x0a + +/* + * buffering + */ + +struct streambuf { + uint8_t *data; + uint32_t size; + uint32_t prod; + uint32_t cons; +}; + +static void streambuf_init(struct streambuf *buf, uint32_t size) +{ + g_free(buf->data); + buf->size = size - (size % USBAUDIO_PACKET_SIZE); + buf->data = g_malloc(buf->size); + buf->prod = 0; + buf->cons = 0; +} + +static void streambuf_fini(struct streambuf *buf) +{ + g_free(buf->data); + buf->data = NULL; +} + +static int streambuf_put(struct streambuf *buf, USBPacket *p) +{ + uint32_t free = buf->size - (buf->prod - buf->cons); + + if (!free) { + return 0; + } + assert(free >= USBAUDIO_PACKET_SIZE); + usb_packet_copy(p, buf->data + (buf->prod % buf->size), + USBAUDIO_PACKET_SIZE); + buf->prod += USBAUDIO_PACKET_SIZE; + return USBAUDIO_PACKET_SIZE; +} + +static uint8_t *streambuf_get(struct streambuf *buf) +{ + uint32_t used = buf->prod - buf->cons; + uint8_t *data; + + if (!used) { + return NULL; + } + assert(used >= USBAUDIO_PACKET_SIZE); + data = buf->data + (buf->cons % buf->size); + buf->cons += USBAUDIO_PACKET_SIZE; + return data; +} + +typedef struct USBAudioState { + /* qemu interfaces */ + USBDevice dev; + QEMUSoundCard card; + + /* state */ + struct { + enum usb_audio_altset altset; + struct audsettings as; + SWVoiceOut *voice; + bool mute; + uint8_t vol[2]; + struct streambuf buf; + } out; + + /* properties */ + uint32_t debug; + uint32_t buffer; +} USBAudioState; + +static void output_callback(void *opaque, int avail) +{ + USBAudioState *s = opaque; + uint8_t *data; + + for (;;) { + if (avail < USBAUDIO_PACKET_SIZE) { + return; + } + data = streambuf_get(&s->out.buf); + if (NULL == data) { + return; + } + AUD_write(s->out.voice, data, USBAUDIO_PACKET_SIZE); + avail -= USBAUDIO_PACKET_SIZE; + } +} + +static int usb_audio_set_output_altset(USBAudioState *s, int altset) +{ + switch (altset) { + case ALTSET_OFF: + streambuf_init(&s->out.buf, s->buffer); + AUD_set_active_out(s->out.voice, false); + break; + case ALTSET_ON: + AUD_set_active_out(s->out.voice, true); + break; + default: + return -1; + } + + if (s->debug) { + fprintf(stderr, "usb-audio: set interface %d\n", altset); + } + s->out.altset = altset; + return 0; +} + +/* + * Note: we arbitrarily map the volume control range onto -inf..+8 dB + */ +#define ATTRIB_ID(cs, attrib, idif) \ + (((cs) << 24) | ((attrib) << 16) | (idif)) + +static int usb_audio_get_control(USBAudioState *s, uint8_t attrib, + uint16_t cscn, uint16_t idif, + int length, uint8_t *data) +{ + uint8_t cs = cscn >> 8; + uint8_t cn = cscn - 1; /* -1 for the non-present master control */ + uint32_t aid = ATTRIB_ID(cs, attrib, idif); + int ret = USB_RET_STALL; + + switch (aid) { + case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200): + data[0] = s->out.mute; + ret = 1; + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200): + if (cn < 2) { + uint16_t vol = (s->out.vol[cn] * 0x8800 + 127) / 255 + 0x8000; + data[0] = vol; + data[1] = vol >> 8; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200): + if (cn < 2) { + data[0] = 0x01; + data[1] = 0x80; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): + if (cn < 2) { + data[0] = 0x00; + data[1] = 0x08; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): + if (cn < 2) { + data[0] = 0x88; + data[1] = 0x00; + ret = 2; + } + break; + } + + return ret; +} +static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, + uint16_t cscn, uint16_t idif, + int length, uint8_t *data) +{ + uint8_t cs = cscn >> 8; + uint8_t cn = cscn - 1; /* -1 for the non-present master control */ + uint32_t aid = ATTRIB_ID(cs, attrib, idif); + int ret = USB_RET_STALL; + bool set_vol = false; + + switch (aid) { + case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200): + s->out.mute = data[0] & 1; + set_vol = true; + ret = 0; + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200): + if (cn < 2) { + uint16_t vol = data[0] + (data[1] << 8); + + if (s->debug) { + fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol); + } + + vol -= 0x8000; + vol = (vol * 255 + 0x4400) / 0x8800; + if (vol > 255) { + vol = 255; + } + + s->out.vol[cn] = vol; + set_vol = true; + ret = 0; + } + break; + } + + if (set_vol) { + if (s->debug) { + fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n", + s->out.mute, s->out.vol[0], s->out.vol[1]); + } + AUD_set_volume_out(s->out.voice, s->out.mute, + s->out.vol[0], s->out.vol[1]); + } + + return ret; +} + +static int usb_audio_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev); + int ret = 0; + + if (s->debug) { + fprintf(stderr, "usb-audio: control transaction: " + "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n", + request, value, index, length); + } + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + switch (request) { + case ClassInterfaceRequest | CR_GET_CUR: + case ClassInterfaceRequest | CR_GET_MIN: + case ClassInterfaceRequest | CR_GET_MAX: + case ClassInterfaceRequest | CR_GET_RES: + ret = usb_audio_get_control(s, request & 0xff, value, index, + length, data); + if (ret < 0) { + if (s->debug) { + fprintf(stderr, "usb-audio: fail: get control\n"); + } + goto fail; + } + break; + + case ClassInterfaceOutRequest | CR_SET_CUR: + case ClassInterfaceOutRequest | CR_SET_MIN: + case ClassInterfaceOutRequest | CR_SET_MAX: + case ClassInterfaceOutRequest | CR_SET_RES: + ret = usb_audio_set_control(s, request & 0xff, value, index, + length, data); + if (ret < 0) { + if (s->debug) { + fprintf(stderr, "usb-audio: fail: set control\n"); + } + goto fail; + } + break; + + default: +fail: + if (s->debug) { + fprintf(stderr, "usb-audio: failed control transaction: " + "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n", + request, value, index, length); + } + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_audio_set_interface(USBDevice *dev, int iface, + int old, int value) +{ + USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev); + + if (iface == 1) { + usb_audio_set_output_altset(s, value); + } +} + +static void usb_audio_handle_reset(USBDevice *dev) +{ + USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev); + + if (s->debug) { + fprintf(stderr, "usb-audio: reset\n"); + } + usb_audio_set_output_altset(s, ALTSET_OFF); +} + +static int usb_audio_handle_dataout(USBAudioState *s, USBPacket *p) +{ + int rc; + + if (s->out.altset == ALTSET_OFF) { + return USB_RET_STALL; + } + + rc = streambuf_put(&s->out.buf, p); + if (rc < p->iov.size && s->debug > 1) { + fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n", + p->iov.size - rc); + } + + return 0; +} + +static int usb_audio_handle_data(USBDevice *dev, USBPacket *p) +{ + USBAudioState *s = (USBAudioState *) dev; + int ret = 0; + + switch (p->pid) { + case USB_TOKEN_OUT: + switch (p->ep->nr) { + case 1: + ret = usb_audio_handle_dataout(s, p); + break; + default: + goto fail; + } + break; + + default: +fail: + ret = USB_RET_STALL; + break; + } + if (ret == USB_RET_STALL && s->debug) { + fprintf(stderr, "usb-audio: failed data transaction: " + "pid 0x%x ep 0x%x len 0x%zx\n", + p->pid, p->ep->nr, p->iov.size); + } + return ret; +} + +static void usb_audio_handle_destroy(USBDevice *dev) +{ + USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev); + + if (s->debug) { + fprintf(stderr, "usb-audio: destroy\n"); + } + + usb_audio_set_output_altset(s, ALTSET_OFF); + AUD_close_out(&s->card, s->out.voice); + AUD_remove_card(&s->card); + + streambuf_fini(&s->out.buf); +} + +static int usb_audio_initfn(USBDevice *dev) +{ + USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev); + + usb_desc_init(dev); + s->dev.opaque = s; + AUD_register_card("usb-audio", &s->card); + + s->out.altset = ALTSET_OFF; + s->out.mute = false; + s->out.vol[0] = 240; /* 0 dB */ + s->out.vol[1] = 240; /* 0 dB */ + s->out.as.freq = USBAUDIO_SAMPLE_RATE; + s->out.as.nchannels = 2; + s->out.as.fmt = AUD_FMT_S16; + s->out.as.endianness = 0; + streambuf_init(&s->out.buf, s->buffer); + + s->out.voice = AUD_open_out(&s->card, s->out.voice, "usb-audio", + s, output_callback, &s->out.as); + AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.vol[1]); + AUD_set_active_out(s->out.voice, 0); + return 0; +} + +static const VMStateDescription vmstate_usb_audio = { + .name = "usb-audio", + .unmigratable = 1, +}; + +static Property usb_audio_properties[] = { + DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0), + DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, + 8 * USBAUDIO_PACKET_SIZE), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_audio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *k = USB_DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_usb_audio; + dc->props = usb_audio_properties; + k->product_desc = "QEMU USB Audio Interface"; + k->usb_desc = &desc_audio; + k->init = usb_audio_initfn; + k->handle_reset = usb_audio_handle_reset; + k->handle_control = usb_audio_handle_control; + k->handle_data = usb_audio_handle_data; + k->handle_destroy = usb_audio_handle_destroy; + k->set_interface = usb_audio_set_interface; +} + +static TypeInfo usb_audio_info = { + .name = "usb-audio", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBAudioState), + .class_init = usb_audio_class_init, +}; + +static void usb_audio_register_types(void) +{ + type_register_static(&usb_audio_info); + usb_legacy_register("usb-audio", "audio", NULL); +} + +type_init(usb_audio_register_types) diff --git a/hw/usb/dev-bluetooth.c b/hw/usb/dev-bluetooth.c new file mode 100644 index 0000000..195370c --- /dev/null +++ b/hw/usb/dev-bluetooth.c @@ -0,0 +1,557 @@ +/* + * QEMU Bluetooth HCI USB Transport Layer v1.0 + * + * Copyright (C) 2007 OpenMoko, Inc. + * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu-common.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "net.h" +#include "hw/bt.h" + +struct USBBtState { + USBDevice dev; + struct HCIInfo *hci; + + int config; + +#define CFIFO_LEN_MASK 255 +#define DFIFO_LEN_MASK 4095 + struct usb_hci_in_fifo_s { + uint8_t data[(DFIFO_LEN_MASK + 1) * 2]; + struct { + uint8_t *data; + int len; + } fifo[CFIFO_LEN_MASK + 1]; + int dstart, dlen, dsize, start, len; + } evt, acl, sco; + + struct usb_hci_out_fifo_s { + uint8_t data[4096]; + int len; + } outcmd, outacl, outsco; +}; + +#define USB_EVT_EP 1 +#define USB_ACL_EP 2 +#define USB_SCO_EP 3 + +enum { + STR_MANUFACTURER = 1, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_SERIALNUMBER] = "1", +}; + +static const USBDescIface desc_iface_bluetooth[] = { + { + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = 0xe0, /* Wireless */ + .bInterfaceSubClass = 0x01, /* Radio Frequency */ + .bInterfaceProtocol = 0x01, /* Bluetooth */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | USB_EVT_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 0x10, + .bInterval = 0x02, + }, + { + .bEndpointAddress = USB_DIR_OUT | USB_ACL_EP, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + .bInterval = 0x0a, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_ACL_EP, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + .bInterval = 0x0a, + }, + }, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xe0, /* Wireless */ + .bInterfaceSubClass = 0x01, /* Radio Frequency */ + .bInterfaceProtocol = 0x01, /* Bluetooth */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0, + .bInterval = 0x01, + }, + }, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = 0xe0, /* Wireless */ + .bInterfaceSubClass = 0x01, /* Radio Frequency */ + .bInterfaceProtocol = 0x01, /* Bluetooth */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x09, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x09, + .bInterval = 0x01, + }, + }, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 2, + .bNumEndpoints = 2, + .bInterfaceClass = 0xe0, /* Wireless */ + .bInterfaceSubClass = 0x01, /* Radio Frequency */ + .bInterfaceProtocol = 0x01, /* Bluetooth */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x11, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x11, + .bInterval = 0x01, + }, + }, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 3, + .bNumEndpoints = 2, + .bInterfaceClass = 0xe0, /* Wireless */ + .bInterfaceSubClass = 0x01, /* Radio Frequency */ + .bInterfaceProtocol = 0x01, /* Bluetooth */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x19, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x19, + .bInterval = 0x01, + }, + }, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 4, + .bNumEndpoints = 2, + .bInterfaceClass = 0xe0, /* Wireless */ + .bInterfaceSubClass = 0x01, /* Radio Frequency */ + .bInterfaceProtocol = 0x01, /* Bluetooth */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x21, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x21, + .bInterval = 0x01, + }, + }, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = 5, + .bNumEndpoints = 2, + .bInterfaceClass = 0xe0, /* Wireless */ + .bInterfaceSubClass = 0x01, /* Radio Frequency */ + .bInterfaceProtocol = 0x01, /* Bluetooth */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x31, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = 0x31, + .bInterval = 0x01, + }, + }, + } +}; + +static const USBDescDevice desc_device_bluetooth = { + .bcdUSB = 0x0110, + .bDeviceClass = 0xe0, /* Wireless */ + .bDeviceSubClass = 0x01, /* Radio Frequency */ + .bDeviceProtocol = 0x01, /* Bluetooth */ + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 2, + .bConfigurationValue = 1, + .bmAttributes = 0xc0, + .bMaxPower = 0, + .nif = ARRAY_SIZE(desc_iface_bluetooth), + .ifs = desc_iface_bluetooth, + }, + }, +}; + +static const USBDesc desc_bluetooth = { + .id = { + .idVendor = 0x0a12, + .idProduct = 0x0001, + .bcdDevice = 0x1958, + .iManufacturer = STR_MANUFACTURER, + .iProduct = 0, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_bluetooth, + .str = desc_strings, +}; + +static void usb_bt_fifo_reset(struct usb_hci_in_fifo_s *fifo) +{ + fifo->dstart = 0; + fifo->dlen = 0; + fifo->dsize = DFIFO_LEN_MASK + 1; + fifo->start = 0; + fifo->len = 0; +} + +static void usb_bt_fifo_enqueue(struct usb_hci_in_fifo_s *fifo, + const uint8_t *data, int len) +{ + int off = fifo->dstart + fifo->dlen; + uint8_t *buf; + + fifo->dlen += len; + if (off <= DFIFO_LEN_MASK) { + if (off + len > DFIFO_LEN_MASK + 1 && + (fifo->dsize = off + len) > (DFIFO_LEN_MASK + 1) * 2) { + fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len); + exit(-1); + } + buf = fifo->data + off; + } else { + if (fifo->dlen > fifo->dsize) { + fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len); + exit(-1); + } + buf = fifo->data + off - fifo->dsize; + } + + off = (fifo->start + fifo->len ++) & CFIFO_LEN_MASK; + fifo->fifo[off].data = memcpy(buf, data, len); + fifo->fifo[off].len = len; +} + +static inline int usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo, + USBPacket *p) +{ + int len; + + if (likely(!fifo->len)) + return USB_RET_STALL; + + len = MIN(p->iov.size, fifo->fifo[fifo->start].len); + usb_packet_copy(p, fifo->fifo[fifo->start].data, len); + if (len == p->iov.size) { + fifo->fifo[fifo->start].len -= len; + fifo->fifo[fifo->start].data += len; + } else { + fifo->start ++; + fifo->start &= CFIFO_LEN_MASK; + fifo->len --; + } + + fifo->dstart += len; + fifo->dlen -= len; + if (fifo->dstart >= fifo->dsize) { + fifo->dstart = 0; + fifo->dsize = DFIFO_LEN_MASK + 1; + } + + return len; +} + +static inline void usb_bt_fifo_out_enqueue(struct USBBtState *s, + struct usb_hci_out_fifo_s *fifo, + void (*send)(struct HCIInfo *, const uint8_t *, int), + int (*complete)(const uint8_t *, int), + USBPacket *p) +{ + usb_packet_copy(p, fifo->data + fifo->len, p->iov.size); + fifo->len += p->iov.size; + if (complete(fifo->data, fifo->len)) { + send(s->hci, fifo->data, fifo->len); + fifo->len = 0; + } + + /* TODO: do we need to loop? */ +} + +static int usb_bt_hci_cmd_complete(const uint8_t *data, int len) +{ + len -= HCI_COMMAND_HDR_SIZE; + return len >= 0 && + len >= ((struct hci_command_hdr *) data)->plen; +} + +static int usb_bt_hci_acl_complete(const uint8_t *data, int len) +{ + len -= HCI_ACL_HDR_SIZE; + return len >= 0 && + len >= le16_to_cpu(((struct hci_acl_hdr *) data)->dlen); +} + +static int usb_bt_hci_sco_complete(const uint8_t *data, int len) +{ + len -= HCI_SCO_HDR_SIZE; + return len >= 0 && + len >= ((struct hci_sco_hdr *) data)->dlen; +} + +static void usb_bt_handle_reset(USBDevice *dev) +{ + struct USBBtState *s = (struct USBBtState *) dev->opaque; + + usb_bt_fifo_reset(&s->evt); + usb_bt_fifo_reset(&s->acl); + usb_bt_fifo_reset(&s->sco); + s->outcmd.len = 0; + s->outacl.len = 0; + s->outsco.len = 0; +} + +static int usb_bt_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + struct USBBtState *s = (struct USBBtState *) dev->opaque; + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + switch (request) { + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + s->config = 0; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + s->config = 1; + usb_bt_fifo_reset(&s->evt); + usb_bt_fifo_reset(&s->acl); + usb_bt_fifo_reset(&s->sco); + break; + } + return ret; + } + + ret = 0; + switch (request) { + case InterfaceRequest | USB_REQ_GET_STATUS: + case EndpointRequest | USB_REQ_GET_STATUS: + data[0] = 0x00; + data[1] = 0x00; + ret = 2; + break; + case InterfaceOutRequest | USB_REQ_CLEAR_FEATURE: + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + goto fail; + case InterfaceOutRequest | USB_REQ_SET_FEATURE: + case EndpointOutRequest | USB_REQ_SET_FEATURE: + goto fail; + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8): + if (s->config) + usb_bt_fifo_out_enqueue(s, &s->outcmd, s->hci->cmd_send, + usb_bt_hci_cmd_complete, p); + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_bt_handle_data(USBDevice *dev, USBPacket *p) +{ + struct USBBtState *s = (struct USBBtState *) dev->opaque; + int ret = 0; + + if (!s->config) + goto fail; + + switch (p->pid) { + case USB_TOKEN_IN: + switch (p->ep->nr) { + case USB_EVT_EP: + ret = usb_bt_fifo_dequeue(&s->evt, p); + break; + + case USB_ACL_EP: + ret = usb_bt_fifo_dequeue(&s->acl, p); + break; + + case USB_SCO_EP: + ret = usb_bt_fifo_dequeue(&s->sco, p); + break; + + default: + goto fail; + } + break; + + case USB_TOKEN_OUT: + switch (p->ep->nr) { + case USB_ACL_EP: + usb_bt_fifo_out_enqueue(s, &s->outacl, s->hci->acl_send, + usb_bt_hci_acl_complete, p); + break; + + case USB_SCO_EP: + usb_bt_fifo_out_enqueue(s, &s->outsco, s->hci->sco_send, + usb_bt_hci_sco_complete, p); + break; + + default: + goto fail; + } + break; + + default: + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_bt_out_hci_packet_event(void *opaque, + const uint8_t *data, int len) +{ + struct USBBtState *s = (struct USBBtState *) opaque; + + usb_bt_fifo_enqueue(&s->evt, data, len); +} + +static void usb_bt_out_hci_packet_acl(void *opaque, + const uint8_t *data, int len) +{ + struct USBBtState *s = (struct USBBtState *) opaque; + + usb_bt_fifo_enqueue(&s->acl, data, len); +} + +static void usb_bt_handle_destroy(USBDevice *dev) +{ + struct USBBtState *s = (struct USBBtState *) dev->opaque; + + s->hci->opaque = NULL; + s->hci->evt_recv = NULL; + s->hci->acl_recv = NULL; +} + +static int usb_bt_initfn(USBDevice *dev) +{ + usb_desc_init(dev); + return 0; +} + +USBDevice *usb_bt_init(USBBus *bus, HCIInfo *hci) +{ + USBDevice *dev; + struct USBBtState *s; + + if (!hci) + return NULL; + dev = usb_create_simple(bus, "usb-bt-dongle"); + if (!dev) { + return NULL; + } + s = DO_UPCAST(struct USBBtState, dev, dev); + s->dev.opaque = s; + + s->hci = hci; + s->hci->opaque = s; + s->hci->evt_recv = usb_bt_out_hci_packet_event; + s->hci->acl_recv = usb_bt_out_hci_packet_acl; + + usb_bt_handle_reset(&s->dev); + + return dev; +} + +static const VMStateDescription vmstate_usb_bt = { + .name = "usb-bt", + .unmigratable = 1, +}; + +static void usb_bt_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_bt_initfn; + uc->product_desc = "QEMU BT dongle"; + uc->usb_desc = &desc_bluetooth; + uc->handle_reset = usb_bt_handle_reset; + uc->handle_control = usb_bt_handle_control; + uc->handle_data = usb_bt_handle_data; + uc->handle_destroy = usb_bt_handle_destroy; + dc->vmsd = &vmstate_usb_bt; +} + +static TypeInfo bt_info = { + .name = "usb-bt-dongle", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(struct USBBtState), + .class_init = usb_bt_class_initfn, +}; + +static void usb_bt_register_types(void) +{ + type_register_static(&bt_info); +} + +type_init(usb_bt_register_types) diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c new file mode 100644 index 0000000..f29544d --- /dev/null +++ b/hw/usb/dev-hid.c @@ -0,0 +1,638 @@ +/* + * QEMU USB HID devices + * + * Copyright (c) 2005 Fabrice Bellard + * Copyright (c) 2007 OpenMoko, Inc. (andrew@openedhand.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "console.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "qemu-timer.h" +#include "hw/hid.h" + +/* HID interface requests */ +#define GET_REPORT 0xa101 +#define GET_IDLE 0xa102 +#define GET_PROTOCOL 0xa103 +#define SET_REPORT 0x2109 +#define SET_IDLE 0x210a +#define SET_PROTOCOL 0x210b + +/* HID descriptor types */ +#define USB_DT_HID 0x21 +#define USB_DT_REPORT 0x22 +#define USB_DT_PHY 0x23 + +typedef struct USBHIDState { + USBDevice dev; + USBEndpoint *intr; + HIDState hid; +} USBHIDState; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT_MOUSE, + STR_PRODUCT_TABLET, + STR_PRODUCT_KEYBOARD, + STR_SERIALNUMBER, + STR_CONFIG_MOUSE, + STR_CONFIG_TABLET, + STR_CONFIG_KEYBOARD, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_PRODUCT_MOUSE] = "QEMU USB Mouse", + [STR_PRODUCT_TABLET] = "QEMU USB Tablet", + [STR_PRODUCT_KEYBOARD] = "QEMU USB Keyboard", + [STR_SERIALNUMBER] = "42", /* == remote wakeup works */ + [STR_CONFIG_MOUSE] = "HID Mouse", + [STR_CONFIG_TABLET] = "HID Tablet", + [STR_CONFIG_KEYBOARD] = "HID Keyboard", +}; + +static const USBDescIface desc_iface_mouse = { + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0x01, /* boot */ + .bInterfaceProtocol = 0x02, + .ndesc = 1, + .descs = (USBDescOther[]) { + { + /* HID descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_HID, /* u8 bDescriptorType */ + 0x01, 0x00, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + USB_DT_REPORT, /* u8 type: Report */ + 52, 0, /* u16 len */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 4, + .bInterval = 0x0a, + }, + }, +}; + +static const USBDescIface desc_iface_tablet = { + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceProtocol = 0x02, + .ndesc = 1, + .descs = (USBDescOther[]) { + { + /* HID descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_HID, /* u8 bDescriptorType */ + 0x01, 0x00, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + USB_DT_REPORT, /* u8 type: Report */ + 74, 0, /* u16 len */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 8, + .bInterval = 0x0a, + }, + }, +}; + +static const USBDescIface desc_iface_keyboard = { + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0x01, /* boot */ + .bInterfaceProtocol = 0x01, /* keyboard */ + .ndesc = 1, + .descs = (USBDescOther[]) { + { + /* HID descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_HID, /* u8 bDescriptorType */ + 0x11, 0x01, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + USB_DT_REPORT, /* u8 type: Report */ + 0x3f, 0, /* u16 len */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 8, + .bInterval = 0x0a, + }, + }, +}; + +static const USBDescDevice desc_device_mouse = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_MOUSE, + .bmAttributes = 0xa0, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_mouse, + }, + }, +}; + +static const USBDescDevice desc_device_tablet = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_TABLET, + .bmAttributes = 0xa0, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_tablet, + }, + }, +}; + +static const USBDescDevice desc_device_keyboard = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_KEYBOARD, + .bmAttributes = 0xa0, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_keyboard, + }, + }, +}; + +static const USBDesc desc_mouse = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_MOUSE, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_mouse, + .str = desc_strings, +}; + +static const USBDesc desc_tablet = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_TABLET, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_tablet, + .str = desc_strings, +}; + +static const USBDesc desc_keyboard = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_KEYBOARD, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_keyboard, + .str = desc_strings, +}; + +static const uint8_t qemu_mouse_hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x02, /* Usage (Mouse) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x05, 0x09, /* Usage Page (Button) */ + 0x19, 0x01, /* Usage Minimum (1) */ + 0x29, 0x03, /* Usage Maximum (3) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x95, 0x03, /* Report Count (3) */ + 0x75, 0x01, /* Report Size (1) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x05, /* Report Size (5) */ + 0x81, 0x01, /* Input (Constant) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x30, /* Usage (X) */ + 0x09, 0x31, /* Usage (Y) */ + 0x09, 0x38, /* Usage (Wheel) */ + 0x15, 0x81, /* Logical Minimum (-0x7f) */ + 0x25, 0x7f, /* Logical Maximum (0x7f) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x03, /* Report Count (3) */ + 0x81, 0x06, /* Input (Data, Variable, Relative) */ + 0xc0, /* End Collection */ + 0xc0, /* End Collection */ +}; + +static const uint8_t qemu_tablet_hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x05, 0x09, /* Usage Page (Button) */ + 0x19, 0x01, /* Usage Minimum (1) */ + 0x29, 0x03, /* Usage Maximum (3) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x95, 0x03, /* Report Count (3) */ + 0x75, 0x01, /* Report Size (1) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x05, /* Report Size (5) */ + 0x81, 0x01, /* Input (Constant) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x30, /* Usage (X) */ + 0x09, 0x31, /* Usage (Y) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x7f, /* Logical Maximum (0x7fff) */ + 0x35, 0x00, /* Physical Minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical Maximum (0x7fff) */ + 0x75, 0x10, /* Report Size (16) */ + 0x95, 0x02, /* Report Count (2) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x38, /* Usage (Wheel) */ + 0x15, 0x81, /* Logical Minimum (-0x7f) */ + 0x25, 0x7f, /* Logical Maximum (0x7f) */ + 0x35, 0x00, /* Physical Minimum (same as logical) */ + 0x45, 0x00, /* Physical Maximum (same as logical) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x01, /* Report Count (1) */ + 0x81, 0x06, /* Input (Data, Variable, Relative) */ + 0xc0, /* End Collection */ + 0xc0, /* End Collection */ +}; + +static const uint8_t qemu_keyboard_hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x06, /* Usage (Keyboard) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x75, 0x01, /* Report Size (1) */ + 0x95, 0x08, /* Report Count (8) */ + 0x05, 0x07, /* Usage Page (Key Codes) */ + 0x19, 0xe0, /* Usage Minimum (224) */ + 0x29, 0xe7, /* Usage Maximum (231) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x08, /* Report Size (8) */ + 0x81, 0x01, /* Input (Constant) */ + 0x95, 0x05, /* Report Count (5) */ + 0x75, 0x01, /* Report Size (1) */ + 0x05, 0x08, /* Usage Page (LEDs) */ + 0x19, 0x01, /* Usage Minimum (1) */ + 0x29, 0x05, /* Usage Maximum (5) */ + 0x91, 0x02, /* Output (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x03, /* Report Size (3) */ + 0x91, 0x01, /* Output (Constant) */ + 0x95, 0x06, /* Report Count (6) */ + 0x75, 0x08, /* Report Size (8) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0xff, /* Logical Maximum (255) */ + 0x05, 0x07, /* Usage Page (Key Codes) */ + 0x19, 0x00, /* Usage Minimum (0) */ + 0x29, 0xff, /* Usage Maximum (255) */ + 0x81, 0x00, /* Input (Data, Array) */ + 0xc0, /* End Collection */ +}; + +static void usb_hid_changed(HIDState *hs) +{ + USBHIDState *us = container_of(hs, USBHIDState, hid); + + usb_wakeup(us->intr); +} + +static void usb_hid_handle_reset(USBDevice *dev) +{ + USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); + + hid_reset(&us->hid); +} + +static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); + HIDState *hs = &us->hid; + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch (request) { + /* hid specific requests */ + case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: + switch (value >> 8) { + case 0x22: + if (hs->kind == HID_MOUSE) { + memcpy(data, qemu_mouse_hid_report_descriptor, + sizeof(qemu_mouse_hid_report_descriptor)); + ret = sizeof(qemu_mouse_hid_report_descriptor); + } else if (hs->kind == HID_TABLET) { + memcpy(data, qemu_tablet_hid_report_descriptor, + sizeof(qemu_tablet_hid_report_descriptor)); + ret = sizeof(qemu_tablet_hid_report_descriptor); + } else if (hs->kind == HID_KEYBOARD) { + memcpy(data, qemu_keyboard_hid_report_descriptor, + sizeof(qemu_keyboard_hid_report_descriptor)); + ret = sizeof(qemu_keyboard_hid_report_descriptor); + } + break; + default: + goto fail; + } + break; + case GET_REPORT: + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + ret = hid_pointer_poll(hs, data, length); + } else if (hs->kind == HID_KEYBOARD) { + ret = hid_keyboard_poll(hs, data, length); + } + break; + case SET_REPORT: + if (hs->kind == HID_KEYBOARD) { + ret = hid_keyboard_write(hs, data, length); + } else { + goto fail; + } + break; + case GET_PROTOCOL: + if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { + goto fail; + } + ret = 1; + data[0] = hs->protocol; + break; + case SET_PROTOCOL: + if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { + goto fail; + } + ret = 0; + hs->protocol = value; + break; + case GET_IDLE: + ret = 1; + data[0] = hs->idle; + break; + case SET_IDLE: + hs->idle = (uint8_t) (value >> 8); + hid_set_next_idle(hs, qemu_get_clock_ns(vm_clock)); + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + hid_pointer_activate(hs); + } + ret = 0; + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); + HIDState *hs = &us->hid; + uint8_t buf[p->iov.size]; + int ret = 0; + + switch (p->pid) { + case USB_TOKEN_IN: + if (p->ep->nr == 1) { + int64_t curtime = qemu_get_clock_ns(vm_clock); + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + hid_pointer_activate(hs); + } + if (!hid_has_events(hs) && + (!hs->idle || hs->next_idle_clock - curtime > 0)) { + return USB_RET_NAK; + } + hid_set_next_idle(hs, curtime); + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + ret = hid_pointer_poll(hs, buf, p->iov.size); + } else if (hs->kind == HID_KEYBOARD) { + ret = hid_keyboard_poll(hs, buf, p->iov.size); + } + usb_packet_copy(p, buf, ret); + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_hid_handle_destroy(USBDevice *dev) +{ + USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); + + hid_free(&us->hid); +} + +static int usb_hid_initfn(USBDevice *dev, int kind) +{ + USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); + + usb_desc_init(dev); + us->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); + hid_init(&us->hid, kind, usb_hid_changed); + return 0; +} + +static int usb_tablet_initfn(USBDevice *dev) +{ + return usb_hid_initfn(dev, HID_TABLET); +} + +static int usb_mouse_initfn(USBDevice *dev) +{ + return usb_hid_initfn(dev, HID_MOUSE); +} + +static int usb_keyboard_initfn(USBDevice *dev) +{ + return usb_hid_initfn(dev, HID_KEYBOARD); +} + +static int usb_ptr_post_load(void *opaque, int version_id) +{ + USBHIDState *s = opaque; + + if (s->dev.remote_wakeup) { + hid_pointer_activate(&s->hid); + } + return 0; +} + +static const VMStateDescription vmstate_usb_ptr = { + .name = "usb-ptr", + .version_id = 1, + .minimum_version_id = 1, + .post_load = usb_ptr_post_load, + .fields = (VMStateField []) { + VMSTATE_USB_DEVICE(dev, USBHIDState), + VMSTATE_HID_POINTER_DEVICE(hid, USBHIDState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_usb_kbd = { + .name = "usb-kbd", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_USB_DEVICE(dev, USBHIDState), + VMSTATE_HID_KEYBOARD_DEVICE(hid, USBHIDState), + VMSTATE_END_OF_LIST() + } +}; + +static void usb_hid_class_initfn(ObjectClass *klass, void *data) +{ + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->handle_reset = usb_hid_handle_reset; + uc->handle_control = usb_hid_handle_control; + uc->handle_data = usb_hid_handle_data; + uc->handle_destroy = usb_hid_handle_destroy; +} + +static void usb_tablet_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + usb_hid_class_initfn(klass, data); + uc->init = usb_tablet_initfn; + uc->product_desc = "QEMU USB Tablet"; + uc->usb_desc = &desc_tablet; + dc->vmsd = &vmstate_usb_ptr; +} + +static TypeInfo usb_tablet_info = { + .name = "usb-tablet", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHIDState), + .class_init = usb_tablet_class_initfn, +}; + +static void usb_mouse_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + usb_hid_class_initfn(klass, data); + uc->init = usb_mouse_initfn; + uc->product_desc = "QEMU USB Mouse"; + uc->usb_desc = &desc_mouse; + dc->vmsd = &vmstate_usb_ptr; +} + +static TypeInfo usb_mouse_info = { + .name = "usb-mouse", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHIDState), + .class_init = usb_mouse_class_initfn, +}; + +static void usb_keyboard_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + usb_hid_class_initfn(klass, data); + uc->init = usb_keyboard_initfn; + uc->product_desc = "QEMU USB Keyboard"; + uc->usb_desc = &desc_keyboard; + dc->vmsd = &vmstate_usb_kbd; +} + +static TypeInfo usb_keyboard_info = { + .name = "usb-kbd", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHIDState), + .class_init = usb_keyboard_class_initfn, +}; + +static void usb_hid_register_types(void) +{ + type_register_static(&usb_tablet_info); + usb_legacy_register("usb-tablet", "tablet", NULL); + type_register_static(&usb_mouse_info); + usb_legacy_register("usb-mouse", "mouse", NULL); + type_register_static(&usb_keyboard_info); + usb_legacy_register("usb-kbd", "keyboard", NULL); +} + +type_init(usb_hid_register_types) diff --git a/hw/usb/dev-hub.c b/hw/usb/dev-hub.c new file mode 100644 index 0000000..eb4e711 --- /dev/null +++ b/hw/usb/dev-hub.c @@ -0,0 +1,549 @@ +/* + * QEMU USB HUB emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" + +//#define DEBUG + +#define NUM_PORTS 8 + +typedef struct USBHubPort { + USBPort port; + uint16_t wPortStatus; + uint16_t wPortChange; +} USBHubPort; + +typedef struct USBHubState { + USBDevice dev; + USBEndpoint *intr; + USBHubPort ports[NUM_PORTS]; +} USBHubState; + +#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS) +#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS) +#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE) +#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE) + +#define PORT_STAT_CONNECTION 0x0001 +#define PORT_STAT_ENABLE 0x0002 +#define PORT_STAT_SUSPEND 0x0004 +#define PORT_STAT_OVERCURRENT 0x0008 +#define PORT_STAT_RESET 0x0010 +#define PORT_STAT_POWER 0x0100 +#define PORT_STAT_LOW_SPEED 0x0200 +#define PORT_STAT_HIGH_SPEED 0x0400 +#define PORT_STAT_TEST 0x0800 +#define PORT_STAT_INDICATOR 0x1000 + +#define PORT_STAT_C_CONNECTION 0x0001 +#define PORT_STAT_C_ENABLE 0x0002 +#define PORT_STAT_C_SUSPEND 0x0004 +#define PORT_STAT_C_OVERCURRENT 0x0008 +#define PORT_STAT_C_RESET 0x0010 + +#define PORT_CONNECTION 0 +#define PORT_ENABLE 1 +#define PORT_SUSPEND 2 +#define PORT_OVERCURRENT 3 +#define PORT_RESET 4 +#define PORT_POWER 8 +#define PORT_LOWSPEED 9 +#define PORT_HIGHSPEED 10 +#define PORT_C_CONNECTION 16 +#define PORT_C_ENABLE 17 +#define PORT_C_SUSPEND 18 +#define PORT_C_OVERCURRENT 19 +#define PORT_C_RESET 20 +#define PORT_TEST 21 +#define PORT_INDICATOR 22 + +/* same as Linux kernel root hubs */ + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_PRODUCT] = "QEMU USB Hub", + [STR_SERIALNUMBER] = "314159", +}; + +static const USBDescIface desc_iface_hub = { + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HUB, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 1 + (NUM_PORTS + 7) / 8, + .bInterval = 0xff, + }, + } +}; + +static const USBDescDevice desc_device_hub = { + .bcdUSB = 0x0110, + .bDeviceClass = USB_CLASS_HUB, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = 0xe0, + .nif = 1, + .ifs = &desc_iface_hub, + }, + }, +}; + +static const USBDesc desc_hub = { + .id = { + .idVendor = 0x0409, + .idProduct = 0x55aa, + .bcdDevice = 0x0101, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_hub, + .str = desc_strings, +}; + +static const uint8_t qemu_hub_hub_descriptor[] = +{ + 0x00, /* u8 bLength; patched in later */ + 0x29, /* u8 bDescriptorType; Hub-descriptor */ + 0x00, /* u8 bNbrPorts; (patched later) */ + 0x0a, /* u16 wHubCharacteristics; */ + 0x00, /* (per-port OC, no power switching) */ + 0x01, /* u8 bPwrOn2pwrGood; 2ms */ + 0x00 /* u8 bHubContrCurrent; 0 mA */ + + /* DeviceRemovable and PortPwrCtrlMask patched in later */ +}; + +static void usb_hub_attach(USBPort *port1) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + port->wPortStatus |= PORT_STAT_CONNECTION; + port->wPortChange |= PORT_STAT_C_CONNECTION; + if (port->port.dev->speed == USB_SPEED_LOW) { + port->wPortStatus |= PORT_STAT_LOW_SPEED; + } else { + port->wPortStatus &= ~PORT_STAT_LOW_SPEED; + } + usb_wakeup(s->intr); +} + +static void usb_hub_detach(USBPort *port1) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + usb_wakeup(s->intr); + + /* Let upstream know the device on this port is gone */ + s->dev.port->ops->child_detach(s->dev.port, port1->dev); + + port->wPortStatus &= ~PORT_STAT_CONNECTION; + port->wPortChange |= PORT_STAT_C_CONNECTION; + if (port->wPortStatus & PORT_STAT_ENABLE) { + port->wPortStatus &= ~PORT_STAT_ENABLE; + port->wPortChange |= PORT_STAT_C_ENABLE; + } +} + +static void usb_hub_child_detach(USBPort *port1, USBDevice *child) +{ + USBHubState *s = port1->opaque; + + /* Pass along upstream */ + s->dev.port->ops->child_detach(s->dev.port, child); +} + +static void usb_hub_wakeup(USBPort *port1) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + if (port->wPortStatus & PORT_STAT_SUSPEND) { + port->wPortChange |= PORT_STAT_C_SUSPEND; + usb_wakeup(s->intr); + } +} + +static void usb_hub_complete(USBPort *port, USBPacket *packet) +{ + USBHubState *s = port->opaque; + + /* + * Just pass it along upstream for now. + * + * If we ever implement usb 2.0 split transactions this will + * become a little more complicated ... + * + * Can't use usb_packet_complete() here because packet->owner is + * cleared already, go call the ->complete() callback directly + * instead. + */ + s->dev.port->ops->complete(s->dev.port, packet); +} + +static USBDevice *usb_hub_find_device(USBDevice *dev, uint8_t addr) +{ + USBHubState *s = DO_UPCAST(USBHubState, dev, dev); + USBHubPort *port; + USBDevice *downstream; + int i; + + for (i = 0; i < NUM_PORTS; i++) { + port = &s->ports[i]; + if (!(port->wPortStatus & PORT_STAT_ENABLE)) { + continue; + } + downstream = usb_find_device(&port->port, addr); + if (downstream != NULL) { + return downstream; + } + } + return NULL; +} + +static void usb_hub_handle_reset(USBDevice *dev) +{ + USBHubState *s = DO_UPCAST(USBHubState, dev, dev); + USBHubPort *port; + int i; + + for (i = 0; i < NUM_PORTS; i++) { + port = s->ports + i; + port->wPortStatus = PORT_STAT_POWER; + port->wPortChange = 0; + if (port->port.dev && port->port.dev->attached) { + port->wPortStatus |= PORT_STAT_CONNECTION; + port->wPortChange |= PORT_STAT_C_CONNECTION; + if (port->port.dev->speed == USB_SPEED_LOW) { + port->wPortStatus |= PORT_STAT_LOW_SPEED; + } + } + } +} + +static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + switch(request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0 && index != 0x81) { /* clear ep halt */ + goto fail; + } + ret = 0; + break; + /* usb specific requests */ + case GetHubStatus: + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0; + ret = 4; + break; + case GetPortStatus: + { + unsigned int n = index - 1; + USBHubPort *port; + if (n >= NUM_PORTS) { + goto fail; + } + port = &s->ports[n]; + data[0] = port->wPortStatus; + data[1] = port->wPortStatus >> 8; + data[2] = port->wPortChange; + data[3] = port->wPortChange >> 8; + ret = 4; + } + break; + case SetHubFeature: + case ClearHubFeature: + if (value == 0 || value == 1) { + } else { + goto fail; + } + ret = 0; + break; + case SetPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + USBDevice *dev; + if (n >= NUM_PORTS) { + goto fail; + } + port = &s->ports[n]; + dev = port->port.dev; + switch(value) { + case PORT_SUSPEND: + port->wPortStatus |= PORT_STAT_SUSPEND; + break; + case PORT_RESET: + if (dev && dev->attached) { + usb_device_reset(dev); + port->wPortChange |= PORT_STAT_C_RESET; + /* set enable bit */ + port->wPortStatus |= PORT_STAT_ENABLE; + } + break; + case PORT_POWER: + break; + default: + goto fail; + } + ret = 0; + } + break; + case ClearPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + + if (n >= NUM_PORTS) { + goto fail; + } + port = &s->ports[n]; + switch(value) { + case PORT_ENABLE: + port->wPortStatus &= ~PORT_STAT_ENABLE; + break; + case PORT_C_ENABLE: + port->wPortChange &= ~PORT_STAT_C_ENABLE; + break; + case PORT_SUSPEND: + port->wPortStatus &= ~PORT_STAT_SUSPEND; + break; + case PORT_C_SUSPEND: + port->wPortChange &= ~PORT_STAT_C_SUSPEND; + break; + case PORT_C_CONNECTION: + port->wPortChange &= ~PORT_STAT_C_CONNECTION; + break; + case PORT_C_OVERCURRENT: + port->wPortChange &= ~PORT_STAT_C_OVERCURRENT; + break; + case PORT_C_RESET: + port->wPortChange &= ~PORT_STAT_C_RESET; + break; + default: + goto fail; + } + ret = 0; + } + break; + case GetHubDescriptor: + { + unsigned int n, limit, var_hub_size = 0; + memcpy(data, qemu_hub_hub_descriptor, + sizeof(qemu_hub_hub_descriptor)); + data[2] = NUM_PORTS; + + /* fill DeviceRemovable bits */ + limit = ((NUM_PORTS + 1 + 7) / 8) + 7; + for (n = 7; n < limit; n++) { + data[n] = 0x00; + var_hub_size++; + } + + /* fill PortPwrCtrlMask bits */ + limit = limit + ((NUM_PORTS + 7) / 8); + for (;n < limit; n++) { + data[n] = 0xff; + var_hub_size++; + } + + ret = sizeof(qemu_hub_hub_descriptor) + var_hub_size; + data[0] = ret; + break; + } + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hub_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + switch(p->pid) { + case USB_TOKEN_IN: + if (p->ep->nr == 1) { + USBHubPort *port; + unsigned int status; + uint8_t buf[4]; + int i, n; + n = (NUM_PORTS + 1 + 7) / 8; + if (p->iov.size == 1) { /* FreeBSD workaround */ + n = 1; + } else if (n > p->iov.size) { + return USB_RET_BABBLE; + } + status = 0; + for(i = 0; i < NUM_PORTS; i++) { + port = &s->ports[i]; + if (port->wPortChange) + status |= (1 << (i + 1)); + } + if (status != 0) { + for(i = 0; i < n; i++) { + buf[i] = status >> (8 * i); + } + usb_packet_copy(p, buf, n); + ret = n; + } else { + ret = USB_RET_NAK; /* usb11 11.13.1 */ + } + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_hub_handle_destroy(USBDevice *dev) +{ + USBHubState *s = (USBHubState *)dev; + int i; + + for (i = 0; i < NUM_PORTS; i++) { + usb_unregister_port(usb_bus_from_device(dev), + &s->ports[i].port); + } +} + +static USBPortOps usb_hub_port_ops = { + .attach = usb_hub_attach, + .detach = usb_hub_detach, + .child_detach = usb_hub_child_detach, + .wakeup = usb_hub_wakeup, + .complete = usb_hub_complete, +}; + +static int usb_hub_initfn(USBDevice *dev) +{ + USBHubState *s = DO_UPCAST(USBHubState, dev, dev); + USBHubPort *port; + int i; + + usb_desc_init(dev); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); + for (i = 0; i < NUM_PORTS; i++) { + port = &s->ports[i]; + usb_register_port(usb_bus_from_device(dev), + &port->port, s, i, &usb_hub_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); + usb_port_location(&port->port, dev->port, i+1); + } + usb_hub_handle_reset(dev); + return 0; +} + +static const VMStateDescription vmstate_usb_hub_port = { + .name = "usb-hub-port", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT16(wPortStatus, USBHubPort), + VMSTATE_UINT16(wPortChange, USBHubPort), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_usb_hub = { + .name = "usb-hub", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_USB_DEVICE(dev, USBHubState), + VMSTATE_STRUCT_ARRAY(ports, USBHubState, NUM_PORTS, 0, + vmstate_usb_hub_port, USBHubPort), + VMSTATE_END_OF_LIST() + } +}; + +static void usb_hub_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_hub_initfn; + uc->product_desc = "QEMU USB Hub"; + uc->usb_desc = &desc_hub; + uc->find_device = usb_hub_find_device; + uc->handle_reset = usb_hub_handle_reset; + uc->handle_control = usb_hub_handle_control; + uc->handle_data = usb_hub_handle_data; + uc->handle_destroy = usb_hub_handle_destroy; + dc->fw_name = "hub"; + dc->vmsd = &vmstate_usb_hub; +} + +static TypeInfo hub_info = { + .name = "usb-hub", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHubState), + .class_init = usb_hub_class_initfn, +}; + +static void usb_hub_register_types(void) +{ + type_register_static(&hub_info); +} + +type_init(usb_hub_register_types) diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c new file mode 100644 index 0000000..cff55f2 --- /dev/null +++ b/hw/usb/dev-network.c @@ -0,0 +1,1423 @@ +/* + * QEMU USB Net devices + * + * Copyright (c) 2006 Thomas Sailer + * Copyright (c) 2008 Andrzej Zaborowski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "net.h" +#include "qemu-queue.h" +#include "sysemu.h" +#include "iov.h" + +/*#define TRAFFIC_DEBUG*/ +/* Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only CDC Ethernet configurations. + */ +#define CDC_VENDOR_NUM 0x0525 /* NetChip */ +#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */ +/* For hardware that can talk RNDIS and either of the above protocols, + * use this ID ... the windows INF files will know it. + */ +#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */ +#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */ + +enum usbstring_idx { + STRING_MANUFACTURER = 1, + STRING_PRODUCT, + STRING_ETHADDR, + STRING_DATA, + STRING_CONTROL, + STRING_RNDIS_CONTROL, + STRING_CDC, + STRING_SUBSET, + STRING_RNDIS, + STRING_SERIALNUMBER, +}; + +#define DEV_CONFIG_VALUE 1 /* CDC or a subset */ +#define DEV_RNDIS_CONFIG_VALUE 2 /* RNDIS; optional */ + +#define USB_CDC_SUBCLASS_ACM 0x02 +#define USB_CDC_SUBCLASS_ETHERNET 0x06 + +#define USB_CDC_PROTO_NONE 0 +#define USB_CDC_ACM_PROTO_VENDOR 0xff + +#define USB_CDC_HEADER_TYPE 0x00 /* header_desc */ +#define USB_CDC_CALL_MANAGEMENT_TYPE 0x01 /* call_mgmt_descriptor */ +#define USB_CDC_ACM_TYPE 0x02 /* acm_descriptor */ +#define USB_CDC_UNION_TYPE 0x06 /* union_desc */ +#define USB_CDC_ETHERNET_TYPE 0x0f /* ether_desc */ + +#define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00 +#define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01 +#define USB_CDC_REQ_SET_LINE_CODING 0x20 +#define USB_CDC_REQ_GET_LINE_CODING 0x21 +#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22 +#define USB_CDC_REQ_SEND_BREAK 0x23 +#define USB_CDC_SET_ETHERNET_MULTICAST_FILTERS 0x40 +#define USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER 0x41 +#define USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER 0x42 +#define USB_CDC_SET_ETHERNET_PACKET_FILTER 0x43 +#define USB_CDC_GET_ETHERNET_STATISTIC 0x44 + +#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +#define ETH_FRAME_LEN 1514 /* Max. octets in frame sans FCS */ + +static const USBDescStrings usb_net_stringtable = { + [STRING_MANUFACTURER] = "QEMU", + [STRING_PRODUCT] = "RNDIS/QEMU USB Network Device", + [STRING_ETHADDR] = "400102030405", + [STRING_DATA] = "QEMU USB Net Data Interface", + [STRING_CONTROL] = "QEMU USB Net Control Interface", + [STRING_RNDIS_CONTROL] = "QEMU USB Net RNDIS Control Interface", + [STRING_CDC] = "QEMU USB Net CDC", + [STRING_SUBSET] = "QEMU USB Net Subset", + [STRING_RNDIS] = "QEMU USB Net RNDIS", + [STRING_SERIALNUMBER] = "1", +}; + +static const USBDescIface desc_iface_rndis[] = { + { + /* RNDIS Control Interface */ + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR, + .iInterface = STRING_RNDIS_CONTROL, + .ndesc = 4, + .descs = (USBDescOther[]) { + { + /* Header Descriptor */ + .data = (uint8_t[]) { + 0x05, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + USB_CDC_HEADER_TYPE, /* u8 bDescriptorSubType */ + 0x10, 0x01, /* le16 bcdCDC */ + }, + },{ + /* Call Management Descriptor */ + .data = (uint8_t[]) { + 0x05, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + USB_CDC_CALL_MANAGEMENT_TYPE, /* u8 bDescriptorSubType */ + 0x00, /* u8 bmCapabilities */ + 0x01, /* u8 bDataInterface */ + }, + },{ + /* ACM Descriptor */ + .data = (uint8_t[]) { + 0x04, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + USB_CDC_ACM_TYPE, /* u8 bDescriptorSubType */ + 0x00, /* u8 bmCapabilities */ + }, + },{ + /* Union Descriptor */ + .data = (uint8_t[]) { + 0x05, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + USB_CDC_UNION_TYPE, /* u8 bDescriptorSubType */ + 0x00, /* u8 bMasterInterface0 */ + 0x01, /* u8 bSlaveInterface0 */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = STATUS_BYTECOUNT, + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, + }, + } + },{ + /* RNDIS Data Interface */ + .bInterfaceNumber = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .iInterface = STRING_DATA, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + } + } + } +}; + +static const USBDescIface desc_iface_cdc[] = { + { + /* CDC Control Interface */ + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .iInterface = STRING_CONTROL, + .ndesc = 3, + .descs = (USBDescOther[]) { + { + /* Header Descriptor */ + .data = (uint8_t[]) { + 0x05, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + USB_CDC_HEADER_TYPE, /* u8 bDescriptorSubType */ + 0x10, 0x01, /* le16 bcdCDC */ + }, + },{ + /* Union Descriptor */ + .data = (uint8_t[]) { + 0x05, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + USB_CDC_UNION_TYPE, /* u8 bDescriptorSubType */ + 0x00, /* u8 bMasterInterface0 */ + 0x01, /* u8 bSlaveInterface0 */ + }, + },{ + /* Ethernet Descriptor */ + .data = (uint8_t[]) { + 0x0d, /* u8 bLength */ + USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ + USB_CDC_ETHERNET_TYPE, /* u8 bDescriptorSubType */ + STRING_ETHADDR, /* u8 iMACAddress */ + 0x00, 0x00, 0x00, 0x00, /* le32 bmEthernetStatistics */ + ETH_FRAME_LEN & 0xff, + ETH_FRAME_LEN >> 8, /* le16 wMaxSegmentSize */ + 0x00, 0x00, /* le16 wNumberMCFilters */ + 0x00, /* u8 bNumberPowerFilters */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = STATUS_BYTECOUNT, + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, + }, + } + },{ + /* CDC Data Interface (off) */ + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, + },{ + /* CDC Data Interface */ + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .iInterface = STRING_DATA, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 0x40, + } + } + } +}; + +static const USBDescDevice desc_device_net = { + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_COMM, + .bMaxPacketSize0 = 0x40, + .bNumConfigurations = 2, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 2, + .bConfigurationValue = DEV_RNDIS_CONFIG_VALUE, + .iConfiguration = STRING_RNDIS, + .bmAttributes = 0xc0, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface_rndis), + .ifs = desc_iface_rndis, + },{ + .bNumInterfaces = 2, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = STRING_CDC, + .bmAttributes = 0xc0, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface_cdc), + .ifs = desc_iface_cdc, + } + }, +}; + +static const USBDesc desc_net = { + .id = { + .idVendor = RNDIS_VENDOR_NUM, + .idProduct = RNDIS_PRODUCT_NUM, + .bcdDevice = 0, + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIALNUMBER, + }, + .full = &desc_device_net, + .str = usb_net_stringtable, +}; + +/* + * RNDIS Definitions - in theory not specific to USB. + */ +#define RNDIS_MAXIMUM_FRAME_SIZE 1518 +#define RNDIS_MAX_TOTAL_SIZE 1558 + +/* Remote NDIS Versions */ +#define RNDIS_MAJOR_VERSION 1 +#define RNDIS_MINOR_VERSION 0 + +/* Status Values */ +#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */ +#define RNDIS_STATUS_FAILURE 0xc0000001U /* Unspecified error */ +#define RNDIS_STATUS_INVALID_DATA 0xc0010015U /* Invalid data */ +#define RNDIS_STATUS_NOT_SUPPORTED 0xc00000bbU /* Unsupported request */ +#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000bU /* Device connected */ +#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000cU /* Device disconnected */ + +/* Message Set for Connectionless (802.3) Devices */ +enum { + RNDIS_PACKET_MSG = 1, + RNDIS_INITIALIZE_MSG = 2, /* Initialize device */ + RNDIS_HALT_MSG = 3, + RNDIS_QUERY_MSG = 4, + RNDIS_SET_MSG = 5, + RNDIS_RESET_MSG = 6, + RNDIS_INDICATE_STATUS_MSG = 7, + RNDIS_KEEPALIVE_MSG = 8, +}; + +/* Message completion */ +enum { + RNDIS_INITIALIZE_CMPLT = 0x80000002U, + RNDIS_QUERY_CMPLT = 0x80000004U, + RNDIS_SET_CMPLT = 0x80000005U, + RNDIS_RESET_CMPLT = 0x80000006U, + RNDIS_KEEPALIVE_CMPLT = 0x80000008U, +}; + +/* Device Flags */ +enum { + RNDIS_DF_CONNECTIONLESS = 1, + RNDIS_DF_CONNECTIONORIENTED = 2, +}; + +#define RNDIS_MEDIUM_802_3 0x00000000U + +/* from drivers/net/sk98lin/h/skgepnmi.h */ +#define OID_PNP_CAPABILITIES 0xfd010100 +#define OID_PNP_SET_POWER 0xfd010101 +#define OID_PNP_QUERY_POWER 0xfd010102 +#define OID_PNP_ADD_WAKE_UP_PATTERN 0xfd010103 +#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xfd010104 +#define OID_PNP_ENABLE_WAKE_UP 0xfd010106 + +typedef uint32_t le32; + +typedef struct rndis_init_msg_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; + le32 MajorVersion; + le32 MinorVersion; + le32 MaxTransferSize; +} rndis_init_msg_type; + +typedef struct rndis_init_cmplt_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; + le32 Status; + le32 MajorVersion; + le32 MinorVersion; + le32 DeviceFlags; + le32 Medium; + le32 MaxPacketsPerTransfer; + le32 MaxTransferSize; + le32 PacketAlignmentFactor; + le32 AFListOffset; + le32 AFListSize; +} rndis_init_cmplt_type; + +typedef struct rndis_halt_msg_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; +} rndis_halt_msg_type; + +typedef struct rndis_query_msg_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; + le32 OID; + le32 InformationBufferLength; + le32 InformationBufferOffset; + le32 DeviceVcHandle; +} rndis_query_msg_type; + +typedef struct rndis_query_cmplt_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; + le32 Status; + le32 InformationBufferLength; + le32 InformationBufferOffset; +} rndis_query_cmplt_type; + +typedef struct rndis_set_msg_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; + le32 OID; + le32 InformationBufferLength; + le32 InformationBufferOffset; + le32 DeviceVcHandle; +} rndis_set_msg_type; + +typedef struct rndis_set_cmplt_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; + le32 Status; +} rndis_set_cmplt_type; + +typedef struct rndis_reset_msg_type { + le32 MessageType; + le32 MessageLength; + le32 Reserved; +} rndis_reset_msg_type; + +typedef struct rndis_reset_cmplt_type { + le32 MessageType; + le32 MessageLength; + le32 Status; + le32 AddressingReset; +} rndis_reset_cmplt_type; + +typedef struct rndis_indicate_status_msg_type { + le32 MessageType; + le32 MessageLength; + le32 Status; + le32 StatusBufferLength; + le32 StatusBufferOffset; +} rndis_indicate_status_msg_type; + +typedef struct rndis_keepalive_msg_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; +} rndis_keepalive_msg_type; + +typedef struct rndis_keepalive_cmplt_type { + le32 MessageType; + le32 MessageLength; + le32 RequestID; + le32 Status; +} rndis_keepalive_cmplt_type; + +struct rndis_packet_msg_type { + le32 MessageType; + le32 MessageLength; + le32 DataOffset; + le32 DataLength; + le32 OOBDataOffset; + le32 OOBDataLength; + le32 NumOOBDataElements; + le32 PerPacketInfoOffset; + le32 PerPacketInfoLength; + le32 VcHandle; + le32 Reserved; +}; + +struct rndis_config_parameter { + le32 ParameterNameOffset; + le32 ParameterNameLength; + le32 ParameterType; + le32 ParameterValueOffset; + le32 ParameterValueLength; +}; + +/* implementation specific */ +enum rndis_state +{ + RNDIS_UNINITIALIZED, + RNDIS_INITIALIZED, + RNDIS_DATA_INITIALIZED, +}; + +/* from ndis.h */ +enum ndis_oid { + /* Required Object IDs (OIDs) */ + OID_GEN_SUPPORTED_LIST = 0x00010101, + OID_GEN_HARDWARE_STATUS = 0x00010102, + OID_GEN_MEDIA_SUPPORTED = 0x00010103, + OID_GEN_MEDIA_IN_USE = 0x00010104, + OID_GEN_MAXIMUM_LOOKAHEAD = 0x00010105, + OID_GEN_MAXIMUM_FRAME_SIZE = 0x00010106, + OID_GEN_LINK_SPEED = 0x00010107, + OID_GEN_TRANSMIT_BUFFER_SPACE = 0x00010108, + OID_GEN_RECEIVE_BUFFER_SPACE = 0x00010109, + OID_GEN_TRANSMIT_BLOCK_SIZE = 0x0001010a, + OID_GEN_RECEIVE_BLOCK_SIZE = 0x0001010b, + OID_GEN_VENDOR_ID = 0x0001010c, + OID_GEN_VENDOR_DESCRIPTION = 0x0001010d, + OID_GEN_CURRENT_PACKET_FILTER = 0x0001010e, + OID_GEN_CURRENT_LOOKAHEAD = 0x0001010f, + OID_GEN_DRIVER_VERSION = 0x00010110, + OID_GEN_MAXIMUM_TOTAL_SIZE = 0x00010111, + OID_GEN_PROTOCOL_OPTIONS = 0x00010112, + OID_GEN_MAC_OPTIONS = 0x00010113, + OID_GEN_MEDIA_CONNECT_STATUS = 0x00010114, + OID_GEN_MAXIMUM_SEND_PACKETS = 0x00010115, + OID_GEN_VENDOR_DRIVER_VERSION = 0x00010116, + OID_GEN_SUPPORTED_GUIDS = 0x00010117, + OID_GEN_NETWORK_LAYER_ADDRESSES = 0x00010118, + OID_GEN_TRANSPORT_HEADER_OFFSET = 0x00010119, + OID_GEN_MACHINE_NAME = 0x0001021a, + OID_GEN_RNDIS_CONFIG_PARAMETER = 0x0001021b, + OID_GEN_VLAN_ID = 0x0001021c, + + /* Optional OIDs */ + OID_GEN_MEDIA_CAPABILITIES = 0x00010201, + OID_GEN_PHYSICAL_MEDIUM = 0x00010202, + + /* Required statistics OIDs */ + OID_GEN_XMIT_OK = 0x00020101, + OID_GEN_RCV_OK = 0x00020102, + OID_GEN_XMIT_ERROR = 0x00020103, + OID_GEN_RCV_ERROR = 0x00020104, + OID_GEN_RCV_NO_BUFFER = 0x00020105, + + /* Optional statistics OIDs */ + OID_GEN_DIRECTED_BYTES_XMIT = 0x00020201, + OID_GEN_DIRECTED_FRAMES_XMIT = 0x00020202, + OID_GEN_MULTICAST_BYTES_XMIT = 0x00020203, + OID_GEN_MULTICAST_FRAMES_XMIT = 0x00020204, + OID_GEN_BROADCAST_BYTES_XMIT = 0x00020205, + OID_GEN_BROADCAST_FRAMES_XMIT = 0x00020206, + OID_GEN_DIRECTED_BYTES_RCV = 0x00020207, + OID_GEN_DIRECTED_FRAMES_RCV = 0x00020208, + OID_GEN_MULTICAST_BYTES_RCV = 0x00020209, + OID_GEN_MULTICAST_FRAMES_RCV = 0x0002020a, + OID_GEN_BROADCAST_BYTES_RCV = 0x0002020b, + OID_GEN_BROADCAST_FRAMES_RCV = 0x0002020c, + OID_GEN_RCV_CRC_ERROR = 0x0002020d, + OID_GEN_TRANSMIT_QUEUE_LENGTH = 0x0002020e, + OID_GEN_GET_TIME_CAPS = 0x0002020f, + OID_GEN_GET_NETCARD_TIME = 0x00020210, + OID_GEN_NETCARD_LOAD = 0x00020211, + OID_GEN_DEVICE_PROFILE = 0x00020212, + OID_GEN_INIT_TIME_MS = 0x00020213, + OID_GEN_RESET_COUNTS = 0x00020214, + OID_GEN_MEDIA_SENSE_COUNTS = 0x00020215, + OID_GEN_FRIENDLY_NAME = 0x00020216, + OID_GEN_MINIPORT_INFO = 0x00020217, + OID_GEN_RESET_VERIFY_PARAMETERS = 0x00020218, + + /* IEEE 802.3 (Ethernet) OIDs */ + OID_802_3_PERMANENT_ADDRESS = 0x01010101, + OID_802_3_CURRENT_ADDRESS = 0x01010102, + OID_802_3_MULTICAST_LIST = 0x01010103, + OID_802_3_MAXIMUM_LIST_SIZE = 0x01010104, + OID_802_3_MAC_OPTIONS = 0x01010105, + OID_802_3_RCV_ERROR_ALIGNMENT = 0x01020101, + OID_802_3_XMIT_ONE_COLLISION = 0x01020102, + OID_802_3_XMIT_MORE_COLLISIONS = 0x01020103, + OID_802_3_XMIT_DEFERRED = 0x01020201, + OID_802_3_XMIT_MAX_COLLISIONS = 0x01020202, + OID_802_3_RCV_OVERRUN = 0x01020203, + OID_802_3_XMIT_UNDERRUN = 0x01020204, + OID_802_3_XMIT_HEARTBEAT_FAILURE = 0x01020205, + OID_802_3_XMIT_TIMES_CRS_LOST = 0x01020206, + OID_802_3_XMIT_LATE_COLLISIONS = 0x01020207, +}; + +static const uint32_t oid_supported_list[] = +{ + /* the general stuff */ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_PHYSICAL_MEDIUM, + + /* the statistical stuff */ + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + + /* IEEE 802.3 */ + /* the general stuff */ + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAC_OPTIONS, + OID_802_3_MAXIMUM_LIST_SIZE, + + /* the statistical stuff */ + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, +}; + +#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA (1 << 0) +#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED (1 << 1) +#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND (1 << 2) +#define NDIS_MAC_OPTION_NO_LOOPBACK (1 << 3) +#define NDIS_MAC_OPTION_FULL_DUPLEX (1 << 4) +#define NDIS_MAC_OPTION_EOTX_INDICATION (1 << 5) +#define NDIS_MAC_OPTION_8021P_PRIORITY (1 << 6) + +struct rndis_response { + QTAILQ_ENTRY(rndis_response) entries; + uint32_t length; + uint8_t buf[0]; +}; + +typedef struct USBNetState { + USBDevice dev; + + enum rndis_state rndis_state; + uint32_t medium; + uint32_t speed; + uint32_t media_state; + uint16_t filter; + uint32_t vendorid; + + unsigned int out_ptr; + uint8_t out_buf[2048]; + + USBPacket *inpkt; + unsigned int in_ptr, in_len; + uint8_t in_buf[2048]; + + char usbstring_mac[13]; + NICState *nic; + NICConf conf; + QTAILQ_HEAD(rndis_resp_head, rndis_response) rndis_resp; +} USBNetState; + +static int is_rndis(USBNetState *s) +{ + return s->dev.config->bConfigurationValue == DEV_RNDIS_CONFIG_VALUE; +} + +static int ndis_query(USBNetState *s, uint32_t oid, + uint8_t *inbuf, unsigned int inlen, uint8_t *outbuf, + size_t outlen) +{ + unsigned int i; + + switch (oid) { + /* general oids (table 4-1) */ + /* mandatory */ + case OID_GEN_SUPPORTED_LIST: + for (i = 0; i < ARRAY_SIZE(oid_supported_list); i++) + ((le32 *) outbuf)[i] = cpu_to_le32(oid_supported_list[i]); + return sizeof(oid_supported_list); + + /* mandatory */ + case OID_GEN_HARDWARE_STATUS: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MEDIA_SUPPORTED: + *((le32 *) outbuf) = cpu_to_le32(s->medium); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MEDIA_IN_USE: + *((le32 *) outbuf) = cpu_to_le32(s->medium); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MAXIMUM_FRAME_SIZE: + *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_LINK_SPEED: + *((le32 *) outbuf) = cpu_to_le32(s->speed); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_TRANSMIT_BLOCK_SIZE: + *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RECEIVE_BLOCK_SIZE: + *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_VENDOR_ID: + *((le32 *) outbuf) = cpu_to_le32(s->vendorid); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_VENDOR_DESCRIPTION: + pstrcpy((char *)outbuf, outlen, "QEMU USB RNDIS Net"); + return strlen((char *)outbuf) + 1; + + case OID_GEN_VENDOR_DRIVER_VERSION: + *((le32 *) outbuf) = cpu_to_le32(1); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_CURRENT_PACKET_FILTER: + *((le32 *) outbuf) = cpu_to_le32(s->filter); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MAXIMUM_TOTAL_SIZE: + *((le32 *) outbuf) = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MEDIA_CONNECT_STATUS: + *((le32 *) outbuf) = cpu_to_le32(s->media_state); + return sizeof(le32); + + case OID_GEN_PHYSICAL_MEDIUM: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + case OID_GEN_MAC_OPTIONS: + *((le32 *) outbuf) = cpu_to_le32( + NDIS_MAC_OPTION_RECEIVE_SERIALIZED | + NDIS_MAC_OPTION_FULL_DUPLEX); + return sizeof(le32); + + /* statistics OIDs (table 4-2) */ + /* mandatory */ + case OID_GEN_XMIT_OK: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RCV_OK: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_XMIT_ERROR: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RCV_ERROR: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RCV_NO_BUFFER: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* ieee802.3 OIDs (table 4-3) */ + /* mandatory */ + case OID_802_3_PERMANENT_ADDRESS: + memcpy(outbuf, s->conf.macaddr.a, 6); + return 6; + + /* mandatory */ + case OID_802_3_CURRENT_ADDRESS: + memcpy(outbuf, s->conf.macaddr.a, 6); + return 6; + + /* mandatory */ + case OID_802_3_MULTICAST_LIST: + *((le32 *) outbuf) = cpu_to_le32(0xe0000000); + return sizeof(le32); + + /* mandatory */ + case OID_802_3_MAXIMUM_LIST_SIZE: + *((le32 *) outbuf) = cpu_to_le32(1); + return sizeof(le32); + + case OID_802_3_MAC_OPTIONS: + return 0; + + /* ieee802.3 statistics OIDs (table 4-4) */ + /* mandatory */ + case OID_802_3_RCV_ERROR_ALIGNMENT: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* mandatory */ + case OID_802_3_XMIT_ONE_COLLISION: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + /* mandatory */ + case OID_802_3_XMIT_MORE_COLLISIONS: + *((le32 *) outbuf) = cpu_to_le32(0); + return sizeof(le32); + + default: + fprintf(stderr, "usbnet: unknown OID 0x%08x\n", oid); + return 0; + } + return -1; +} + +static int ndis_set(USBNetState *s, uint32_t oid, + uint8_t *inbuf, unsigned int inlen) +{ + switch (oid) { + case OID_GEN_CURRENT_PACKET_FILTER: + s->filter = le32_to_cpup((le32 *) inbuf); + if (s->filter) { + s->rndis_state = RNDIS_DATA_INITIALIZED; + } else { + s->rndis_state = RNDIS_INITIALIZED; + } + return 0; + + case OID_802_3_MULTICAST_LIST: + return 0; + } + return -1; +} + +static int rndis_get_response(USBNetState *s, uint8_t *buf) +{ + int ret = 0; + struct rndis_response *r = s->rndis_resp.tqh_first; + + if (!r) + return ret; + + QTAILQ_REMOVE(&s->rndis_resp, r, entries); + ret = r->length; + memcpy(buf, r->buf, r->length); + g_free(r); + + return ret; +} + +static void *rndis_queue_response(USBNetState *s, unsigned int length) +{ + struct rndis_response *r = + g_malloc0(sizeof(struct rndis_response) + length); + + QTAILQ_INSERT_TAIL(&s->rndis_resp, r, entries); + r->length = length; + + return &r->buf[0]; +} + +static void rndis_clear_responsequeue(USBNetState *s) +{ + struct rndis_response *r; + + while ((r = s->rndis_resp.tqh_first)) { + QTAILQ_REMOVE(&s->rndis_resp, r, entries); + g_free(r); + } +} + +static int rndis_init_response(USBNetState *s, rndis_init_msg_type *buf) +{ + rndis_init_cmplt_type *resp = + rndis_queue_response(s, sizeof(rndis_init_cmplt_type)); + + if (!resp) + return USB_RET_STALL; + + resp->MessageType = cpu_to_le32(RNDIS_INITIALIZE_CMPLT); + resp->MessageLength = cpu_to_le32(sizeof(rndis_init_cmplt_type)); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + resp->MajorVersion = cpu_to_le32(RNDIS_MAJOR_VERSION); + resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION); + resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS); + resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3); + resp->MaxPacketsPerTransfer = cpu_to_le32(1); + resp->MaxTransferSize = cpu_to_le32(ETH_FRAME_LEN + + sizeof(struct rndis_packet_msg_type) + 22); + resp->PacketAlignmentFactor = cpu_to_le32(0); + resp->AFListOffset = cpu_to_le32(0); + resp->AFListSize = cpu_to_le32(0); + return 0; +} + +static int rndis_query_response(USBNetState *s, + rndis_query_msg_type *buf, unsigned int length) +{ + rndis_query_cmplt_type *resp; + /* oid_supported_list is the largest data reply */ + uint8_t infobuf[sizeof(oid_supported_list)]; + uint32_t bufoffs, buflen; + int infobuflen; + unsigned int resplen; + + bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8; + buflen = le32_to_cpu(buf->InformationBufferLength); + if (bufoffs + buflen > length) + return USB_RET_STALL; + + infobuflen = ndis_query(s, le32_to_cpu(buf->OID), + bufoffs + (uint8_t *) buf, buflen, infobuf, + sizeof(infobuf)); + resplen = sizeof(rndis_query_cmplt_type) + + ((infobuflen < 0) ? 0 : infobuflen); + resp = rndis_queue_response(s, resplen); + if (!resp) + return USB_RET_STALL; + + resp->MessageType = cpu_to_le32(RNDIS_QUERY_CMPLT); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->MessageLength = cpu_to_le32(resplen); + + if (infobuflen < 0) { + /* OID not supported */ + resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); + resp->InformationBufferLength = cpu_to_le32(0); + resp->InformationBufferOffset = cpu_to_le32(0); + return 0; + } + + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + resp->InformationBufferOffset = + cpu_to_le32(infobuflen ? sizeof(rndis_query_cmplt_type) - 8 : 0); + resp->InformationBufferLength = cpu_to_le32(infobuflen); + memcpy(resp + 1, infobuf, infobuflen); + + return 0; +} + +static int rndis_set_response(USBNetState *s, + rndis_set_msg_type *buf, unsigned int length) +{ + rndis_set_cmplt_type *resp = + rndis_queue_response(s, sizeof(rndis_set_cmplt_type)); + uint32_t bufoffs, buflen; + int ret; + + if (!resp) + return USB_RET_STALL; + + bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8; + buflen = le32_to_cpu(buf->InformationBufferLength); + if (bufoffs + buflen > length) + return USB_RET_STALL; + + ret = ndis_set(s, le32_to_cpu(buf->OID), + bufoffs + (uint8_t *) buf, buflen); + resp->MessageType = cpu_to_le32(RNDIS_SET_CMPLT); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->MessageLength = cpu_to_le32(sizeof(rndis_set_cmplt_type)); + if (ret < 0) { + /* OID not supported */ + resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); + return 0; + } + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + return 0; +} + +static int rndis_reset_response(USBNetState *s, rndis_reset_msg_type *buf) +{ + rndis_reset_cmplt_type *resp = + rndis_queue_response(s, sizeof(rndis_reset_cmplt_type)); + + if (!resp) + return USB_RET_STALL; + + resp->MessageType = cpu_to_le32(RNDIS_RESET_CMPLT); + resp->MessageLength = cpu_to_le32(sizeof(rndis_reset_cmplt_type)); + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + resp->AddressingReset = cpu_to_le32(1); /* reset information */ + + return 0; +} + +static int rndis_keepalive_response(USBNetState *s, + rndis_keepalive_msg_type *buf) +{ + rndis_keepalive_cmplt_type *resp = + rndis_queue_response(s, sizeof(rndis_keepalive_cmplt_type)); + + if (!resp) + return USB_RET_STALL; + + resp->MessageType = cpu_to_le32(RNDIS_KEEPALIVE_CMPLT); + resp->MessageLength = cpu_to_le32(sizeof(rndis_keepalive_cmplt_type)); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + return 0; +} + +static int rndis_parse(USBNetState *s, uint8_t *data, int length) +{ + uint32_t msg_type; + le32 *tmp = (le32 *) data; + + msg_type = le32_to_cpup(tmp); + + switch (msg_type) { + case RNDIS_INITIALIZE_MSG: + s->rndis_state = RNDIS_INITIALIZED; + return rndis_init_response(s, (rndis_init_msg_type *) data); + + case RNDIS_HALT_MSG: + s->rndis_state = RNDIS_UNINITIALIZED; + return 0; + + case RNDIS_QUERY_MSG: + return rndis_query_response(s, (rndis_query_msg_type *) data, length); + + case RNDIS_SET_MSG: + return rndis_set_response(s, (rndis_set_msg_type *) data, length); + + case RNDIS_RESET_MSG: + rndis_clear_responsequeue(s); + s->out_ptr = s->in_ptr = s->in_len = 0; + return rndis_reset_response(s, (rndis_reset_msg_type *) data); + + case RNDIS_KEEPALIVE_MSG: + /* For USB: host does this every 5 seconds */ + return rndis_keepalive_response(s, (rndis_keepalive_msg_type *) data); + } + + return USB_RET_STALL; +} + +static void usb_net_handle_reset(USBDevice *dev) +{ +} + +static int usb_net_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBNetState *s = (USBNetState *) dev; + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch(request) { + case ClassInterfaceOutRequest | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (!is_rndis(s) || value || index != 0) { + goto fail; + } +#ifdef TRAFFIC_DEBUG + { + unsigned int i; + fprintf(stderr, "SEND_ENCAPSULATED_COMMAND:"); + for (i = 0; i < length; i++) { + if (!(i & 15)) + fprintf(stderr, "\n%04x:", i); + fprintf(stderr, " %02x", data[i]); + } + fprintf(stderr, "\n\n"); + } +#endif + ret = rndis_parse(s, data, length); + break; + + case ClassInterfaceRequest | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (!is_rndis(s) || value || index != 0) { + goto fail; + } + ret = rndis_get_response(s, data); + if (!ret) { + data[0] = 0; + ret = 1; + } +#ifdef TRAFFIC_DEBUG + { + unsigned int i; + fprintf(stderr, "GET_ENCAPSULATED_RESPONSE:"); + for (i = 0; i < ret; i++) { + if (!(i & 15)) + fprintf(stderr, "\n%04x:", i); + fprintf(stderr, " %02x", data[i]); + } + fprintf(stderr, "\n\n"); + } +#endif + break; + + default: + fail: + fprintf(stderr, "usbnet: failed control transaction: " + "request 0x%x value 0x%x index 0x%x length 0x%x\n", + request, value, index, length); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_net_handle_statusin(USBNetState *s, USBPacket *p) +{ + le32 buf[2]; + int ret = 8; + + if (p->iov.size < 8) { + return USB_RET_STALL; + } + + buf[0] = cpu_to_le32(1); + buf[1] = cpu_to_le32(0); + usb_packet_copy(p, buf, 8); + if (!s->rndis_resp.tqh_first) + ret = USB_RET_NAK; + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "usbnet: interrupt poll len %zu return %d", + p->iov.size, ret); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", ret); +#endif + + return ret; +} + +static int usb_net_handle_datain(USBNetState *s, USBPacket *p) +{ + int ret = USB_RET_NAK; + + if (s->in_ptr > s->in_len) { + s->in_ptr = s->in_len = 0; + ret = USB_RET_NAK; + return ret; + } + if (!s->in_len) { + ret = USB_RET_NAK; + return ret; + } + ret = s->in_len - s->in_ptr; + if (ret > p->iov.size) { + ret = p->iov.size; + } + usb_packet_copy(p, &s->in_buf[s->in_ptr], ret); + s->in_ptr += ret; + if (s->in_ptr >= s->in_len && + (is_rndis(s) || (s->in_len & (64 - 1)) || !ret)) { + /* no short packet necessary */ + s->in_ptr = s->in_len = 0; + } + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "usbnet: data in len %zu return %d", p->iov.size, ret); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", ret); +#endif + + return ret; +} + +static int usb_net_handle_dataout(USBNetState *s, USBPacket *p) +{ + int ret = p->iov.size; + int sz = sizeof(s->out_buf) - s->out_ptr; + struct rndis_packet_msg_type *msg = + (struct rndis_packet_msg_type *) s->out_buf; + uint32_t len; + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "usbnet: data out len %zu\n", p->iov.size); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->iov.size); +#endif + + if (sz > ret) + sz = ret; + usb_packet_copy(p, &s->out_buf[s->out_ptr], sz); + s->out_ptr += sz; + + if (!is_rndis(s)) { + if (ret < 64) { + qemu_send_packet(&s->nic->nc, s->out_buf, s->out_ptr); + s->out_ptr = 0; + } + return ret; + } + len = le32_to_cpu(msg->MessageLength); + if (s->out_ptr < 8 || s->out_ptr < len) + return ret; + if (le32_to_cpu(msg->MessageType) == RNDIS_PACKET_MSG) { + uint32_t offs = 8 + le32_to_cpu(msg->DataOffset); + uint32_t size = le32_to_cpu(msg->DataLength); + if (offs + size <= len) + qemu_send_packet(&s->nic->nc, s->out_buf + offs, size); + } + s->out_ptr -= len; + memmove(s->out_buf, &s->out_buf[len], s->out_ptr); + + return ret; +} + +static int usb_net_handle_data(USBDevice *dev, USBPacket *p) +{ + USBNetState *s = (USBNetState *) dev; + int ret = 0; + + switch(p->pid) { + case USB_TOKEN_IN: + switch (p->ep->nr) { + case 1: + ret = usb_net_handle_statusin(s, p); + break; + + case 2: + ret = usb_net_handle_datain(s, p); + break; + + default: + goto fail; + } + break; + + case USB_TOKEN_OUT: + switch (p->ep->nr) { + case 2: + ret = usb_net_handle_dataout(s, p); + break; + + default: + goto fail; + } + break; + + default: + fail: + ret = USB_RET_STALL; + break; + } + if (ret == USB_RET_STALL) + fprintf(stderr, "usbnet: failed data transaction: " + "pid 0x%x ep 0x%x len 0x%zx\n", + p->pid, p->ep->nr, p->iov.size); + return ret; +} + +static ssize_t usbnet_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + struct rndis_packet_msg_type *msg; + + if (is_rndis(s)) { + msg = (struct rndis_packet_msg_type *) s->in_buf; + if (s->rndis_state != RNDIS_DATA_INITIALIZED) { + return -1; + } + if (size + sizeof(struct rndis_packet_msg_type) > sizeof(s->in_buf)) + return -1; + + memset(msg, 0, sizeof(struct rndis_packet_msg_type)); + msg->MessageType = cpu_to_le32(RNDIS_PACKET_MSG); + msg->MessageLength = cpu_to_le32(size + sizeof(struct rndis_packet_msg_type)); + msg->DataOffset = cpu_to_le32(sizeof(struct rndis_packet_msg_type) - 8); + msg->DataLength = cpu_to_le32(size); + /* msg->OOBDataOffset; + * msg->OOBDataLength; + * msg->NumOOBDataElements; + * msg->PerPacketInfoOffset; + * msg->PerPacketInfoLength; + * msg->VcHandle; + * msg->Reserved; + */ + memcpy(msg + 1, buf, size); + s->in_len = size + sizeof(struct rndis_packet_msg_type); + } else { + if (size > sizeof(s->in_buf)) + return -1; + memcpy(s->in_buf, buf, size); + s->in_len = size; + } + s->in_ptr = 0; + return size; +} + +static int usbnet_can_receive(VLANClientState *nc) +{ + USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + if (is_rndis(s) && s->rndis_state != RNDIS_DATA_INITIALIZED) { + return 1; + } + + return !s->in_len; +} + +static void usbnet_cleanup(VLANClientState *nc) +{ + USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static void usb_net_handle_destroy(USBDevice *dev) +{ + USBNetState *s = (USBNetState *) dev; + + /* TODO: remove the nd_table[] entry */ + rndis_clear_responsequeue(s); + qemu_del_vlan_client(&s->nic->nc); +} + +static NetClientInfo net_usbnet_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = usbnet_can_receive, + .receive = usbnet_receive, + .cleanup = usbnet_cleanup, +}; + +static int usb_net_initfn(USBDevice *dev) +{ + USBNetState *s = DO_UPCAST(USBNetState, dev, dev); + + usb_desc_init(dev); + + s->rndis_state = RNDIS_UNINITIALIZED; + QTAILQ_INIT(&s->rndis_resp); + + s->medium = 0; /* NDIS_MEDIUM_802_3 */ + s->speed = 1000000; /* 100MBps, in 100Bps units */ + s->media_state = 0; /* NDIS_MEDIA_STATE_CONNECTED */; + s->filter = 0; + s->vendorid = 0x1234; + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_usbnet_info, &s->conf, + object_get_typename(OBJECT(s)), s->dev.qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + snprintf(s->usbstring_mac, sizeof(s->usbstring_mac), + "%02x%02x%02x%02x%02x%02x", + 0x40, + s->conf.macaddr.a[1], + s->conf.macaddr.a[2], + s->conf.macaddr.a[3], + s->conf.macaddr.a[4], + s->conf.macaddr.a[5]); + usb_desc_set_string(dev, STRING_ETHADDR, s->usbstring_mac); + + add_boot_device_path(s->conf.bootindex, &dev->qdev, "/ethernet@0"); + return 0; +} + +static USBDevice *usb_net_init(USBBus *bus, const char *cmdline) +{ + USBDevice *dev; + QemuOpts *opts; + int idx; + + opts = qemu_opts_parse(qemu_find_opts("net"), cmdline, 0); + if (!opts) { + return NULL; + } + qemu_opt_set(opts, "type", "nic"); + qemu_opt_set(opts, "model", "usb"); + + idx = net_client_init(NULL, opts, 0); + if (idx == -1) { + return NULL; + } + + dev = usb_create(bus, "usb-net"); + if (!dev) { + return NULL; + } + qdev_set_nic_properties(&dev->qdev, &nd_table[idx]); + qdev_init_nofail(&dev->qdev); + return dev; +} + +static const VMStateDescription vmstate_usb_net = { + .name = "usb-net", + .unmigratable = 1, +}; + +static Property net_properties[] = { + DEFINE_NIC_PROPERTIES(USBNetState, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_net_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_net_initfn; + uc->product_desc = "QEMU USB Network Interface"; + uc->usb_desc = &desc_net; + uc->handle_reset = usb_net_handle_reset; + uc->handle_control = usb_net_handle_control; + uc->handle_data = usb_net_handle_data; + uc->handle_destroy = usb_net_handle_destroy; + dc->fw_name = "network"; + dc->vmsd = &vmstate_usb_net; + dc->props = net_properties; +} + +static TypeInfo net_info = { + .name = "usb-net", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBNetState), + .class_init = usb_net_class_initfn, +}; + +static void usb_net_register_types(void) +{ + type_register_static(&net_info); + usb_legacy_register("usb-net", "net", usb_net_init); +} + +type_init(usb_net_register_types) diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c new file mode 100644 index 0000000..8dcac8b --- /dev/null +++ b/hw/usb/dev-serial.c @@ -0,0 +1,637 @@ +/* + * FTDI FT232BM Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault + * + * This code is licensed under the LGPL. + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "qemu-char.h" + +//#define DEBUG_Serial + +#ifdef DEBUG_Serial +#define DPRINTF(fmt, ...) \ +do { printf("usb-serial: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +#define RECV_BUF 384 + +/* Commands */ +#define FTDI_RESET 0 +#define FTDI_SET_MDM_CTRL 1 +#define FTDI_SET_FLOW_CTRL 2 +#define FTDI_SET_BAUD 3 +#define FTDI_SET_DATA 4 +#define FTDI_GET_MDM_ST 5 +#define FTDI_SET_EVENT_CHR 6 +#define FTDI_SET_ERROR_CHR 7 +#define FTDI_SET_LATENCY 9 +#define FTDI_GET_LATENCY 10 + +#define DeviceOutVendor ((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8) +#define DeviceInVendor ((USB_DIR_IN |USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8) + +/* RESET */ + +#define FTDI_RESET_SIO 0 +#define FTDI_RESET_RX 1 +#define FTDI_RESET_TX 2 + +/* SET_MDM_CTRL */ + +#define FTDI_DTR 1 +#define FTDI_SET_DTR (FTDI_DTR << 8) +#define FTDI_RTS 2 +#define FTDI_SET_RTS (FTDI_RTS << 8) + +/* SET_FLOW_CTRL */ + +#define FTDI_RTS_CTS_HS 1 +#define FTDI_DTR_DSR_HS 2 +#define FTDI_XON_XOFF_HS 4 + +/* SET_DATA */ + +#define FTDI_PARITY (0x7 << 8) +#define FTDI_ODD (0x1 << 8) +#define FTDI_EVEN (0x2 << 8) +#define FTDI_MARK (0x3 << 8) +#define FTDI_SPACE (0x4 << 8) + +#define FTDI_STOP (0x3 << 11) +#define FTDI_STOP1 (0x0 << 11) +#define FTDI_STOP15 (0x1 << 11) +#define FTDI_STOP2 (0x2 << 11) + +/* GET_MDM_ST */ +/* TODO: should be sent every 40ms */ +#define FTDI_CTS (1<<4) // CTS line status +#define FTDI_DSR (1<<5) // DSR line status +#define FTDI_RI (1<<6) // RI line status +#define FTDI_RLSD (1<<7) // Receive Line Signal Detect + +/* Status */ + +#define FTDI_DR (1<<0) // Data Ready +#define FTDI_OE (1<<1) // Overrun Err +#define FTDI_PE (1<<2) // Parity Err +#define FTDI_FE (1<<3) // Framing Err +#define FTDI_BI (1<<4) // Break Interrupt +#define FTDI_THRE (1<<5) // Transmitter Holding Register +#define FTDI_TEMT (1<<6) // Transmitter Empty +#define FTDI_FIFO (1<<7) // Error in FIFO + +typedef struct { + USBDevice dev; + uint8_t recv_buf[RECV_BUF]; + uint16_t recv_ptr; + uint16_t recv_used; + uint8_t event_chr; + uint8_t error_chr; + uint8_t event_trigger; + QEMUSerialSetParams params; + int latency; /* ms */ + CharDriverState *cs; +} USBSerialState; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT_SERIAL, + STR_PRODUCT_BRAILLE, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_PRODUCT_SERIAL] = "QEMU USB SERIAL", + [STR_PRODUCT_BRAILLE] = "QEMU USB BRAILLE", + [STR_SERIALNUMBER] = "1", +}; + +static const USBDescIface desc_iface0 = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + }, + } +}; + +static const USBDescDevice desc_device = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = 0x80, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface0, + }, + }, +}; + +static const USBDesc desc_serial = { + .id = { + .idVendor = 0x0403, + .idProduct = 0x6001, + .bcdDevice = 0x0400, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_SERIAL, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device, + .str = desc_strings, +}; + +static const USBDesc desc_braille = { + .id = { + .idVendor = 0x0403, + .idProduct = 0xfe72, + .bcdDevice = 0x0400, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_BRAILLE, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device, + .str = desc_strings, +}; + +static void usb_serial_reset(USBSerialState *s) +{ + /* TODO: Set flow control to none */ + s->event_chr = 0x0d; + s->event_trigger = 0; + s->recv_ptr = 0; + s->recv_used = 0; + /* TODO: purge in char driver */ +} + +static void usb_serial_handle_reset(USBDevice *dev) +{ + USBSerialState *s = (USBSerialState *)dev; + + DPRINTF("Reset\n"); + + usb_serial_reset(s); + /* TODO: Reset char device, send BREAK? */ +} + +static uint8_t usb_get_modem_lines(USBSerialState *s) +{ + int flags; + uint8_t ret; + + if (qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP) + return FTDI_CTS|FTDI_DSR|FTDI_RLSD; + + ret = 0; + if (flags & CHR_TIOCM_CTS) + ret |= FTDI_CTS; + if (flags & CHR_TIOCM_DSR) + ret |= FTDI_DSR; + if (flags & CHR_TIOCM_RI) + ret |= FTDI_RI; + if (flags & CHR_TIOCM_CAR) + ret |= FTDI_RLSD; + + return ret; +} + +static int usb_serial_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBSerialState *s = (USBSerialState *)dev; + int ret; + + DPRINTF("got control %x, value %x\n",request, value); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch (request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + break; + + /* Class specific requests. */ + case DeviceOutVendor | FTDI_RESET: + switch (value) { + case FTDI_RESET_SIO: + usb_serial_reset(s); + break; + case FTDI_RESET_RX: + s->recv_ptr = 0; + s->recv_used = 0; + /* TODO: purge from char device */ + break; + case FTDI_RESET_TX: + /* TODO: purge from char device */ + break; + } + break; + case DeviceOutVendor | FTDI_SET_MDM_CTRL: + { + static int flags; + qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_GET_TIOCM, &flags); + if (value & FTDI_SET_RTS) { + if (value & FTDI_RTS) + flags |= CHR_TIOCM_RTS; + else + flags &= ~CHR_TIOCM_RTS; + } + if (value & FTDI_SET_DTR) { + if (value & FTDI_DTR) + flags |= CHR_TIOCM_DTR; + else + flags &= ~CHR_TIOCM_DTR; + } + qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_SET_TIOCM, &flags); + break; + } + case DeviceOutVendor | FTDI_SET_FLOW_CTRL: + /* TODO: ioctl */ + break; + case DeviceOutVendor | FTDI_SET_BAUD: { + static const int subdivisors8[8] = { 0, 4, 2, 1, 3, 5, 6, 7 }; + int subdivisor8 = subdivisors8[((value & 0xc000) >> 14) + | ((index & 1) << 2)]; + int divisor = value & 0x3fff; + + /* chip special cases */ + if (divisor == 1 && subdivisor8 == 0) + subdivisor8 = 4; + if (divisor == 0 && subdivisor8 == 0) + divisor = 1; + + s->params.speed = (48000000 / 2) / (8 * divisor + subdivisor8); + qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + break; + } + case DeviceOutVendor | FTDI_SET_DATA: + switch (value & FTDI_PARITY) { + case 0: + s->params.parity = 'N'; + break; + case FTDI_ODD: + s->params.parity = 'O'; + break; + case FTDI_EVEN: + s->params.parity = 'E'; + break; + default: + DPRINTF("unsupported parity %d\n", value & FTDI_PARITY); + goto fail; + } + switch (value & FTDI_STOP) { + case FTDI_STOP1: + s->params.stop_bits = 1; + break; + case FTDI_STOP2: + s->params.stop_bits = 2; + break; + default: + DPRINTF("unsupported stop bits %d\n", value & FTDI_STOP); + goto fail; + } + qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + /* TODO: TX ON/OFF */ + break; + case DeviceInVendor | FTDI_GET_MDM_ST: + data[0] = usb_get_modem_lines(s) | 1; + data[1] = 0; + ret = 2; + break; + case DeviceOutVendor | FTDI_SET_EVENT_CHR: + /* TODO: handle it */ + s->event_chr = value; + break; + case DeviceOutVendor | FTDI_SET_ERROR_CHR: + /* TODO: handle it */ + s->error_chr = value; + break; + case DeviceOutVendor | FTDI_SET_LATENCY: + s->latency = value; + break; + case DeviceInVendor | FTDI_GET_LATENCY: + data[0] = s->latency; + ret = 1; + break; + default: + fail: + DPRINTF("got unsupported/bogus control %x, value %x\n", request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) +{ + USBSerialState *s = (USBSerialState *)dev; + int i, ret = 0; + uint8_t devep = p->ep->nr; + struct iovec *iov; + uint8_t header[2]; + int first_len, len; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) + goto fail; + for (i = 0; i < p->iov.niov; i++) { + iov = p->iov.iov + i; + qemu_chr_fe_write(s->cs, iov->iov_base, iov->iov_len); + } + break; + + case USB_TOKEN_IN: + if (devep != 1) + goto fail; + first_len = RECV_BUF - s->recv_ptr; + len = p->iov.size; + if (len <= 2) { + ret = USB_RET_NAK; + break; + } + header[0] = usb_get_modem_lines(s) | 1; + /* We do not have the uart details */ + /* handle serial break */ + if (s->event_trigger && s->event_trigger & FTDI_BI) { + s->event_trigger &= ~FTDI_BI; + header[1] = FTDI_BI; + usb_packet_copy(p, header, 2); + ret = 2; + break; + } else { + header[1] = 0; + } + len -= 2; + if (len > s->recv_used) + len = s->recv_used; + if (!len) { + ret = USB_RET_NAK; + break; + } + if (first_len > len) + first_len = len; + usb_packet_copy(p, header, 2); + usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len); + if (len > first_len) + usb_packet_copy(p, s->recv_buf, len - first_len); + s->recv_used -= len; + s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; + ret = len + 2; + break; + + default: + DPRINTF("Bad token\n"); + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_serial_handle_destroy(USBDevice *dev) +{ + USBSerialState *s = (USBSerialState *)dev; + + qemu_chr_delete(s->cs); +} + +static int usb_serial_can_read(void *opaque) +{ + USBSerialState *s = opaque; + return RECV_BUF - s->recv_used; +} + +static void usb_serial_read(void *opaque, const uint8_t *buf, int size) +{ + USBSerialState *s = opaque; + int first_size, start; + + /* room in the buffer? */ + if (size > (RECV_BUF - s->recv_used)) + size = RECV_BUF - s->recv_used; + + start = s->recv_ptr + s->recv_used; + if (start < RECV_BUF) { + /* copy data to end of buffer */ + first_size = RECV_BUF - start; + if (first_size > size) + first_size = size; + + memcpy(s->recv_buf + start, buf, first_size); + + /* wrap around to front if needed */ + if (size > first_size) + memcpy(s->recv_buf, buf + first_size, size - first_size); + } else { + start -= RECV_BUF; + memcpy(s->recv_buf + start, buf, size); + } + s->recv_used += size; +} + +static void usb_serial_event(void *opaque, int event) +{ + USBSerialState *s = opaque; + + switch (event) { + case CHR_EVENT_BREAK: + s->event_trigger |= FTDI_BI; + break; + case CHR_EVENT_FOCUS: + break; + case CHR_EVENT_OPENED: + usb_serial_reset(s); + /* TODO: Reset USB port */ + break; + } +} + +static int usb_serial_initfn(USBDevice *dev) +{ + USBSerialState *s = DO_UPCAST(USBSerialState, dev, dev); + + usb_desc_init(dev); + + if (!s->cs) { + error_report("Property chardev is required"); + return -1; + } + + qemu_chr_add_handlers(s->cs, usb_serial_can_read, usb_serial_read, + usb_serial_event, s); + usb_serial_handle_reset(dev); + return 0; +} + +static USBDevice *usb_serial_init(USBBus *bus, const char *filename) +{ + USBDevice *dev; + CharDriverState *cdrv; + uint32_t vendorid = 0, productid = 0; + char label[32]; + static int index; + + while (*filename && *filename != ':') { + const char *p; + char *e; + if (strstart(filename, "vendorid=", &p)) { + vendorid = strtol(p, &e, 16); + if (e == p || (*e && *e != ',' && *e != ':')) { + error_report("bogus vendor ID %s", p); + return NULL; + } + filename = e; + } else if (strstart(filename, "productid=", &p)) { + productid = strtol(p, &e, 16); + if (e == p || (*e && *e != ',' && *e != ':')) { + error_report("bogus product ID %s", p); + return NULL; + } + filename = e; + } else { + error_report("unrecognized serial USB option %s", filename); + return NULL; + } + while(*filename == ',') + filename++; + } + if (!*filename) { + error_report("character device specification needed"); + return NULL; + } + filename++; + + snprintf(label, sizeof(label), "usbserial%d", index++); + cdrv = qemu_chr_new(label, filename, NULL); + if (!cdrv) + return NULL; + + dev = usb_create(bus, "usb-serial"); + if (!dev) { + return NULL; + } + qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); + if (vendorid) + qdev_prop_set_uint16(&dev->qdev, "vendorid", vendorid); + if (productid) + qdev_prop_set_uint16(&dev->qdev, "productid", productid); + qdev_init_nofail(&dev->qdev); + + return dev; +} + +static USBDevice *usb_braille_init(USBBus *bus, const char *unused) +{ + USBDevice *dev; + CharDriverState *cdrv; + + cdrv = qemu_chr_new("braille", "braille", NULL); + if (!cdrv) + return NULL; + + dev = usb_create(bus, "usb-braille"); + qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); + qdev_init_nofail(&dev->qdev); + + return dev; +} + +static const VMStateDescription vmstate_usb_serial = { + .name = "usb-serial", + .unmigratable = 1, +}; + +static Property serial_properties[] = { + DEFINE_PROP_CHR("chardev", USBSerialState, cs), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_serial_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_serial_initfn; + uc->product_desc = "QEMU USB Serial"; + uc->usb_desc = &desc_serial; + uc->handle_reset = usb_serial_handle_reset; + uc->handle_control = usb_serial_handle_control; + uc->handle_data = usb_serial_handle_data; + uc->handle_destroy = usb_serial_handle_destroy; + dc->vmsd = &vmstate_usb_serial; + dc->props = serial_properties; +} + +static TypeInfo serial_info = { + .name = "usb-serial", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBSerialState), + .class_init = usb_serial_class_initfn, +}; + +static Property braille_properties[] = { + DEFINE_PROP_CHR("chardev", USBSerialState, cs), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_braille_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_serial_initfn; + uc->product_desc = "QEMU USB Braille"; + uc->usb_desc = &desc_braille; + uc->handle_reset = usb_serial_handle_reset; + uc->handle_control = usb_serial_handle_control; + uc->handle_data = usb_serial_handle_data; + uc->handle_destroy = usb_serial_handle_destroy; + dc->vmsd = &vmstate_usb_serial; + dc->props = braille_properties; +} + +static TypeInfo braille_info = { + .name = "usb-braille", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBSerialState), + .class_init = usb_braille_class_initfn, +}; + +static void usb_serial_register_types(void) +{ + type_register_static(&serial_info); + usb_legacy_register("usb-serial", "serial", usb_serial_init); + type_register_static(&braille_info); + usb_legacy_register("usb-braille", "braille", usb_braille_init); +} + +type_init(usb_serial_register_types) diff --git a/hw/usb/dev-smartcard-reader.c b/hw/usb/dev-smartcard-reader.c new file mode 100644 index 0000000..8e66675 --- /dev/null +++ b/hw/usb/dev-smartcard-reader.c @@ -0,0 +1,1365 @@ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * CCID Device emulation + * + * Written by Alon Levy, with contributions from Robert Relyea. + * + * Based on usb-serial.c, see its copyright and attributions below. + * + * This work is licensed under the terms of the GNU GPL, version 2.1 or later. + * See the COPYING file in the top-level directory. + * ------- (original copyright & attribution for usb-serial.c below) -------- + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault, + */ + +/* + * References: + * + * CCID Specification Revision 1.1 April 22nd 2005 + * "Universal Serial Bus, Device Class: Smart Card" + * Specification for Integrated Circuit(s) Cards Interface Devices + * + * Endianness note: from the spec (1.3) + * "Fields that are larger than a byte are stored in little endian" + * + * KNOWN BUGS + * 1. remove/insert can sometimes result in removed state instead of inserted. + * This is a result of the following: + * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This can happen + * when a short packet is sent, as seen in uhci-usb.c, resulting from a urb + * from the guest requesting SPD and us returning a smaller packet. + * Not sure which messages trigger this. + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "monitor.h" + +#include "hw/ccid.h" + +#define DPRINTF(s, lvl, fmt, ...) \ +do { \ + if (lvl <= s->debug) { \ + printf("usb-ccid: " fmt , ## __VA_ARGS__); \ + } \ +} while (0) + +#define D_WARN 1 +#define D_INFO 2 +#define D_MORE_INFO 3 +#define D_VERBOSE 4 + +#define CCID_DEV_NAME "usb-ccid" + +/* + * The two options for variable sized buffers: + * make them constant size, for large enough constant, + * or handle the migration complexity - VMState doesn't handle this case. + * sizes are expected never to be exceeded, unless guest misbehaves. + */ +#define BULK_OUT_DATA_SIZE 65536 +#define PENDING_ANSWERS_NUM 128 + +#define BULK_IN_BUF_SIZE 384 +#define BULK_IN_PENDING_NUM 8 + +#define InterfaceOutClass \ + ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)<<8) + +#define InterfaceInClass \ + ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)<<8) + +#define CCID_MAX_PACKET_SIZE 64 + +#define CCID_CONTROL_ABORT 0x1 +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2 +#define CCID_CONTROL_GET_DATA_RATES 0x3 + +#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID" +#define CCID_VENDOR_DESCRIPTION "QEMU " QEMU_VERSION +#define CCID_INTERFACE_NAME "CCID Interface" +#define CCID_SERIAL_NUMBER_STRING "1" +/* + * Using Gemplus Vendor and Product id + * Effect on various drivers: + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. + * linux has a number of class drivers, but openct filters based on + * vendor/product (/etc/openct.conf under fedora), hence Gemplus. + */ +#define CCID_VENDOR_ID 0x08e6 +#define CCID_PRODUCT_ID 0x4433 +#define CCID_DEVICE_VERSION 0x0000 + +/* + * BULK_OUT messages from PC to Reader + * Defined in CCID Rev 1.1 6.1 (page 26) + */ +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62 +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63 +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65 +#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c +#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e +#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a +#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72 +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73 + +/* + * BULK_IN messages from Reader to PC + * Defined in CCID Rev 1.1 6.2 (page 48) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80 +#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83 +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84 + +/* + * INTERRUPT_IN messages from Reader to PC + * Defined in CCID Rev 1.1 6.3 (page 56) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50 +#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51 + +/* + * Endpoints for CCID - addresses are up to us to decide. + * To support slot insertion and removal we must have an interrupt in ep + * in addition we need a bulk in and bulk out ep + * 5.2, page 20 + */ +#define CCID_INT_IN_EP 1 +#define CCID_BULK_IN_EP 2 +#define CCID_BULK_OUT_EP 3 + +/* bmSlotICCState masks */ +#define SLOT_0_STATE_MASK 1 +#define SLOT_0_CHANGED_MASK 2 + +/* Status codes that go in bStatus (see 6.2.6) */ +enum { + ICC_STATUS_PRESENT_ACTIVE = 0, + ICC_STATUS_PRESENT_INACTIVE, + ICC_STATUS_NOT_PRESENT +}; + +enum { + COMMAND_STATUS_NO_ERROR = 0, + COMMAND_STATUS_FAILED, + COMMAND_STATUS_TIME_EXTENSION_REQUIRED +}; + +/* Error codes that go in bError (see 6.2.6) */ +enum { + ERROR_CMD_NOT_SUPPORTED = 0, + ERROR_CMD_ABORTED = -1, + ERROR_ICC_MUTE = -2, + ERROR_XFR_PARITY_ERROR = -3, + ERROR_XFR_OVERRUN = -4, + ERROR_HW_ERROR = -5, +}; + +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ +enum { + CLOCK_STATUS_RUNNING = 0, + /* + * 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, + * 3 - unknown state. rest are RFU + */ +}; + +typedef struct QEMU_PACKED CCID_Header { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; +} CCID_Header; + +typedef struct QEMU_PACKED CCID_BULK_IN { + CCID_Header hdr; + uint8_t bStatus; /* Only used in BULK_IN */ + uint8_t bError; /* Only used in BULK_IN */ +} CCID_BULK_IN; + +typedef struct QEMU_PACKED CCID_SlotStatus { + CCID_BULK_IN b; + uint8_t bClockStatus; +} CCID_SlotStatus; + +typedef struct QEMU_PACKED CCID_Parameter { + CCID_BULK_IN b; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[0]; +} CCID_Parameter; + +typedef struct QEMU_PACKED CCID_DataBlock { + CCID_BULK_IN b; + uint8_t bChainParameter; + uint8_t abData[0]; +} CCID_DataBlock; + +/* 6.1.4 PC_to_RDR_XfrBlock */ +typedef struct QEMU_PACKED CCID_XferBlock { + CCID_Header hdr; + uint8_t bBWI; /* Block Waiting Timeout */ + uint16_t wLevelParameter; /* XXX currently unused */ + uint8_t abData[0]; +} CCID_XferBlock; + +typedef struct QEMU_PACKED CCID_IccPowerOn { + CCID_Header hdr; + uint8_t bPowerSelect; + uint16_t abRFU; +} CCID_IccPowerOn; + +typedef struct QEMU_PACKED CCID_IccPowerOff { + CCID_Header hdr; + uint16_t abRFU; +} CCID_IccPowerOff; + +typedef struct QEMU_PACKED CCID_SetParameters { + CCID_Header hdr; + uint8_t bProtocolNum; + uint16_t abRFU; + uint8_t abProtocolDataStructure[0]; +} CCID_SetParameters; + +typedef struct CCID_Notify_Slot_Change { + uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */ + uint8_t bmSlotICCState; +} CCID_Notify_Slot_Change; + +/* used for DataBlock response to XferBlock */ +typedef struct Answer { + uint8_t slot; + uint8_t seq; +} Answer; + +/* pending BULK_IN messages */ +typedef struct BulkIn { + uint8_t data[BULK_IN_BUF_SIZE]; + uint32_t len; + uint32_t pos; +} BulkIn; + +enum { + MIGRATION_NONE, + MIGRATION_MIGRATED, +}; + +typedef struct CCIDBus { + BusState qbus; +} CCIDBus; + +#define MAX_PROTOCOL_SIZE 7 + +/* + * powered - defaults to true, changed by PowerOn/PowerOff messages + */ +typedef struct USBCCIDState { + USBDevice dev; + USBEndpoint *intr; + CCIDBus bus; + CCIDCardState *card; + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ + uint32_t bulk_in_pending_start; + uint32_t bulk_in_pending_end; /* first free */ + uint32_t bulk_in_pending_num; + BulkIn *current_bulk_in; + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; + uint32_t bulk_out_pos; + uint64_t last_answer_error; + Answer pending_answers[PENDING_ANSWERS_NUM]; + uint32_t pending_answers_start; + uint32_t pending_answers_end; + uint32_t pending_answers_num; + uint8_t bError; + uint8_t bmCommandStatus; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; + uint32_t ulProtocolDataStructureSize; + uint32_t state_vmstate; + uint32_t migration_target_ip; + uint16_t migration_target_port; + uint8_t migration_state; + uint8_t bmSlotICCState; + uint8_t powered; + uint8_t notify_slot_change; + uint8_t debug; +} USBCCIDState; + +/* + * CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, + * "USB Device Framework", section 9.6.1, in the Universal Serial Bus + * Specification. + * + * This device implemented based on the spec and with an Athena Smart Card + * Reader as reference: + * 0dc3:1004 Athena Smartcard Solutions, Inc. + */ + +static const uint8_t qemu_ccid_descriptor[] = { + /* Smart Card Device Class Descriptor */ + 0x36, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; Functional */ + 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ + 0x00, /* + * u8 bMaxSlotIndex; The index of the highest available + * slot on this device. All slots are consecutive starting + * at 00h. + */ + 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ + + 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ + 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ + /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */ + 0xa0, 0x0f, 0x00, 0x00, + /* u32 dwMaximumClock; */ + 0x00, 0x00, 0x01, 0x00, + 0x00, /* u8 bNumClockSupported; * + * 0 means just the default and max. */ + /* u32 dwDataRate ;bps. 9600 == 00002580h */ + 0x80, 0x25, 0x00, 0x00, + /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */ + 0x00, 0xC2, 0x01, 0x00, + 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between + * default and max */ + /* u32 dwMaxIFSD; * + * maximum IFSD supported by CCID for protocol * + * T=1 (Maximum seen from various cards) */ + 0xfe, 0x00, 0x00, 0x00, + /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwMechanical; 0 - no special characteristics. */ + 0x00, 0x00, 0x00, 0x00, + /* + * u32 dwFeatures; + * 0 - No special characteristics + * + 2 Automatic parameter configuration based on ATR data + * + 4 Automatic activation of ICC on inserting + * + 8 Automatic ICC voltage selection + * + 10 Automatic ICC clock frequency change + * + 20 Automatic baud rate change + * + 40 Automatic parameters negotiation made by the CCID + * + 80 automatic PPS made by the CCID + * 100 CCID can set ICC in clock stop mode + * 200 NAD value other then 00 accepted (T=1 protocol) + * + 400 Automatic IFSD exchange as first exchange (T=1) + * One of the following only: + * + 10000 TPDU level exchanges with CCID + * 20000 Short APDU level exchange with CCID + * 40000 Short and Extended APDU level exchange with CCID + * + * + 100000 USB Wake up signaling supported on card + * insertion and removal. Must set bit 5 in bmAttributes + * in Configuration descriptor if 100000 is set. + */ + 0xfe, 0x04, 0x11, 0x00, + /* + * u32 dwMaxCCIDMessageLength; For extended APDU in + * [261 + 10 , 65544 + 10]. Otherwise the minimum is + * wMaxPacketSize of the Bulk-OUT endpoint + */ + 0x12, 0x00, 0x01, 0x00, + 0xFF, /* + * u8 bClassGetResponse; Significant only for CCID that + * offers an APDU level for exchanges. Indicates the + * default class value used by the CCID when it sends a + * Get Response command to perform the transportation of + * an APDU by T=0 protocol + * FFh indicates that the CCID echos the class of the APDU. + */ + 0xFF, /* + * u8 bClassEnvelope; EAPDU only. Envelope command for + * T=0 + */ + 0x00, 0x00, /* + * u16 wLcdLayout; XXYY Number of lines (XX) and chars per + * line for LCD display used for PIN entry. 0000 - no LCD + */ + 0x01, /* + * u8 bPINSupport; 01h PIN Verification, + * 02h PIN Modification + */ + 0x01, /* u8 bMaxCCIDBusySlots; */ +}; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_INTERFACE, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_PRODUCT] = "QEMU USB CCID", + [STR_SERIALNUMBER] = "1", + [STR_INTERFACE] = "CCID Interface", +}; + +static const USBDescIface desc_iface0 = { + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = 0x0b, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .iInterface = STR_INTERFACE, + .ndesc = 1, + .descs = (USBDescOther[]) { + { + /* smartcard descriptor */ + .data = qemu_ccid_descriptor, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | CCID_INT_IN_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .bInterval = 255, + .wMaxPacketSize = 64, + },{ + .bEndpointAddress = USB_DIR_IN | CCID_BULK_IN_EP, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + },{ + .bEndpointAddress = USB_DIR_OUT | CCID_BULK_OUT_EP, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + }, + } +}; + +static const USBDescDevice desc_device = { + .bcdUSB = 0x0110, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = 0xe0, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface0, + }, + }, +}; + +static const USBDesc desc_ccid = { + .id = { + .idVendor = CCID_VENDOR_ID, + .idProduct = CCID_PRODUCT_ID, + .bcdDevice = CCID_DEVICE_VERSION, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device, + .str = desc_strings, +}; + +static const uint8_t *ccid_card_get_atr(CCIDCardState *card, uint32_t *len) +{ + CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + if (cc->get_atr) { + return cc->get_atr(card, len); + } + return NULL; +} + +static void ccid_card_apdu_from_guest(CCIDCardState *card, + const uint8_t *apdu, + uint32_t len) +{ + CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + if (cc->apdu_from_guest) { + cc->apdu_from_guest(card, apdu, len); + } +} + +static int ccid_card_exitfn(CCIDCardState *card) +{ + CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + if (cc->exitfn) { + return cc->exitfn(card); + } + return 0; +} + +static int ccid_card_initfn(CCIDCardState *card) +{ + CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + if (cc->initfn) { + return cc->initfn(card); + } + return 0; +} + +static bool ccid_has_pending_answers(USBCCIDState *s) +{ + return s->pending_answers_num > 0; +} + +static void ccid_clear_pending_answers(USBCCIDState *s) +{ + s->pending_answers_num = 0; + s->pending_answers_start = 0; + s->pending_answers_end = 0; +} + +static void ccid_print_pending_answers(USBCCIDState *s) +{ + Answer *answer; + int i, count; + + DPRINTF(s, D_VERBOSE, "usb-ccid: pending answers:"); + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, D_VERBOSE, " empty\n"); + return; + } + for (i = s->pending_answers_start, count = s->pending_answers_num ; + count > 0; count--, i++) { + answer = &s->pending_answers[i % PENDING_ANSWERS_NUM]; + if (count == 1) { + DPRINTF(s, D_VERBOSE, "%d:%d\n", answer->slot, answer->seq); + } else { + DPRINTF(s, D_VERBOSE, "%d:%d,", answer->slot, answer->seq); + } + } +} + +static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) +{ + Answer *answer; + + assert(s->pending_answers_num < PENDING_ANSWERS_NUM); + s->pending_answers_num++; + answer = + &s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; + answer->slot = hdr->bSlot; + answer->seq = hdr->bSeq; + ccid_print_pending_answers(s); +} + +static void ccid_remove_pending_answer(USBCCIDState *s, + uint8_t *slot, uint8_t *seq) +{ + Answer *answer; + + assert(s->pending_answers_num > 0); + s->pending_answers_num--; + answer = + &s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; + *slot = answer->slot; + *seq = answer->seq; + ccid_print_pending_answers(s); +} + +static void ccid_bulk_in_clear(USBCCIDState *s) +{ + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->bulk_in_pending_num = 0; +} + +static void ccid_bulk_in_release(USBCCIDState *s) +{ + assert(s->current_bulk_in != NULL); + s->current_bulk_in->pos = 0; + s->current_bulk_in = NULL; +} + +static void ccid_bulk_in_get(USBCCIDState *s) +{ + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { + return; + } + assert(s->bulk_in_pending_num > 0); + s->bulk_in_pending_num--; + s->current_bulk_in = + &s->bulk_in_pending[(s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; +} + +static void *ccid_reserve_recv_buf(USBCCIDState *s, uint16_t len) +{ + BulkIn *bulk_in; + + DPRINTF(s, D_VERBOSE, "%s: QUEUE: reserve %d bytes\n", __func__, len); + + /* look for an existing element */ + if (len > BULK_IN_BUF_SIZE) { + DPRINTF(s, D_WARN, "usb-ccid.c: %s: len larger then max (%d>%d). " + "discarding message.\n", + __func__, len, BULK_IN_BUF_SIZE); + return NULL; + } + if (s->bulk_in_pending_num >= BULK_IN_PENDING_NUM) { + DPRINTF(s, D_WARN, "usb-ccid.c: %s: No free bulk_in buffers. " + "discarding message.\n", __func__); + return NULL; + } + bulk_in = + &s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; + s->bulk_in_pending_num++; + bulk_in->len = len; + return bulk_in->data; +} + +static void ccid_reset(USBCCIDState *s) +{ + ccid_bulk_in_clear(s); + ccid_clear_pending_answers(s); +} + +static void ccid_detach(USBCCIDState *s) +{ + ccid_reset(s); +} + +static void ccid_handle_reset(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + DPRINTF(s, 1, "Reset\n"); + + ccid_reset(s); +} + +static int ccid_handle_control(USBDevice *dev, USBPacket *p, int request, + int value, int index, int length, uint8_t *data) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + + DPRINTF(s, 1, "got control %x, value %x\n", request, value); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + switch (request) { + /* Class specific requests. */ + case InterfaceOutClass | CCID_CONTROL_ABORT: + DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: + DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + default: + DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", + request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static bool ccid_card_inserted(USBCCIDState *s) +{ + return s->bmSlotICCState & SLOT_0_STATE_MASK; +} + +static uint8_t ccid_card_status(USBCCIDState *s) +{ + return ccid_card_inserted(s) + ? (s->powered ? + ICC_STATUS_PRESENT_ACTIVE + : ICC_STATUS_PRESENT_INACTIVE + ) + : ICC_STATUS_NOT_PRESENT; +} + +static uint8_t ccid_calc_status(USBCCIDState *s) +{ + /* + * page 55, 6.2.6, calculation of bStatus from bmICCStatus and + * bmCommandStatus + */ + uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus << 6); + DPRINTF(s, D_VERBOSE, "status = %d\n", ret); + return ret; +} + +static void ccid_reset_error_status(USBCCIDState *s) +{ + s->bError = ERROR_CMD_NOT_SUPPORTED; + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; +} + +static void ccid_write_slot_status(USBCCIDState *s, CCID_Header *recv) +{ + CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus)); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bClockStatus = CLOCK_STATUS_RUNNING; + ccid_reset_error_status(s); +} + +static void ccid_write_parameters(USBCCIDState *s, CCID_Header *recv) +{ + CCID_Parameter *h; + uint32_t len = s->ulProtocolDataStructureSize; + + h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bProtocolNum = s->bProtocolNum; + memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block(USBCCIDState *s, uint8_t slot, uint8_t seq, + const uint8_t *data, uint32_t len) +{ + CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len); + + if (p == NULL) { + return; + } + p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock; + p->b.hdr.dwLength = cpu_to_le32(len); + p->b.hdr.bSlot = slot; + p->b.hdr.bSeq = seq; + p->b.bStatus = ccid_calc_status(s); + p->b.bError = s->bError; + if (p->b.bError) { + DPRINTF(s, D_VERBOSE, "error %d", p->b.bError); + } + memcpy(p->abData, data, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block_answer(USBCCIDState *s, + const uint8_t *data, uint32_t len) +{ + uint8_t seq; + uint8_t slot; + + if (!ccid_has_pending_answers(s)) { + abort(); + } + ccid_remove_pending_answer(s, &slot, &seq); + ccid_write_data_block(s, slot, seq, data, len); +} + +static void ccid_write_data_block_atr(USBCCIDState *s, CCID_Header *recv) +{ + const uint8_t *atr = NULL; + uint32_t len = 0; + + if (s->card) { + atr = ccid_card_get_atr(s->card, &len); + } + ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len); +} + +static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv) +{ + CCID_SetParameters *ph = (CCID_SetParameters *) recv; + uint32_t len = 0; + if ((ph->bProtocolNum & 3) == 0) { + len = 5; + } + if ((ph->bProtocolNum & 3) == 1) { + len = 7; + } + if (len == 0) { + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = 7; /* Protocol invalid or not supported */ + return; + } + s->bProtocolNum = ph->bProtocolNum; + memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); + s->ulProtocolDataStructureSize = len; + DPRINTF(s, 1, "%s: using len %d\n", __func__, len); +} + +/* + * must be 5 bytes for T=0, 7 bytes for T=1 + * See page 52 + */ +static const uint8_t abDefaultProtocolDataStructure[7] = { + 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; + +static void ccid_reset_parameters(USBCCIDState *s) +{ + uint32_t len = sizeof(abDefaultProtocolDataStructure); + + s->bProtocolNum = 1; /* T=1 */ + s->ulProtocolDataStructureSize = len; + memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); +} + +static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) +{ + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = error; +} + +/* NOTE: only a single slot is supported (SLOT_0) */ +static void ccid_on_slot_change(USBCCIDState *s, bool full) +{ + /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 */ + uint8_t current = s->bmSlotICCState; + if (full) { + s->bmSlotICCState |= SLOT_0_STATE_MASK; + } else { + s->bmSlotICCState &= ~SLOT_0_STATE_MASK; + } + if (current != s->bmSlotICCState) { + s->bmSlotICCState |= SLOT_0_CHANGED_MASK; + } + s->notify_slot_change = true; + usb_wakeup(s->intr); +} + +static void ccid_write_data_block_error( + USBCCIDState *s, uint8_t slot, uint8_t seq) +{ + ccid_write_data_block(s, slot, seq, NULL, 0); +} + +static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) +{ + uint32_t len; + + if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) { + DPRINTF(s, 1, + "usb-ccid: not sending apdu to client, no card connected\n"); + ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq); + return; + } + len = le32_to_cpu(recv->hdr.dwLength); + DPRINTF(s, 1, "%s: seq %d, len %d\n", __func__, + recv->hdr.bSeq, len); + ccid_add_pending_answer(s, (CCID_Header *)recv); + if (s->card) { + ccid_card_apdu_from_guest(s->card, recv->abData, len); + } else { + DPRINTF(s, D_WARN, "warning: discarded apdu\n"); + } +} + +/* + * Handle a single USB_TOKEN_OUT, return value returned to guest. + * Return value: + * 0 - all ok + * USB_RET_STALL - failed to handle packet + */ +static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) +{ + CCID_Header *ccid_header; + + if (p->iov.size + s->bulk_out_pos > BULK_OUT_DATA_SIZE) { + return USB_RET_STALL; + } + ccid_header = (CCID_Header *)s->bulk_out_data; + usb_packet_copy(p, s->bulk_out_data + s->bulk_out_pos, p->iov.size); + s->bulk_out_pos += p->iov.size; + if (p->iov.size == CCID_MAX_PACKET_SIZE) { + DPRINTF(s, D_VERBOSE, + "usb-ccid: bulk_in: expecting more packets (%zd/%d)\n", + p->iov.size, ccid_header->dwLength); + return 0; + } + if (s->bulk_out_pos < 10) { + DPRINTF(s, 1, + "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", + __func__); + } else { + DPRINTF(s, D_MORE_INFO, "%s %x\n", __func__, ccid_header->bMessageType); + switch (ccid_header->bMessageType) { + case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: + DPRINTF(s, 1, "PowerOn: %d\n", + ((CCID_IccPowerOn *)(ccid_header))->bPowerSelect); + s->powered = true; + if (!ccid_card_inserted(s)) { + ccid_report_error_failed(s, ERROR_ICC_MUTE); + } + /* atr is written regardless of error. */ + ccid_write_data_block_atr(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: + DPRINTF(s, 1, "PowerOff\n"); + ccid_reset_error_status(s); + s->powered = false; + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: + ccid_on_apdu_from_guest(s, (CCID_XferBlock *)s->bulk_out_data); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: + ccid_reset_error_status(s); + ccid_set_parameters(s, ccid_header); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: + ccid_reset_error_status(s); + ccid_reset_parameters(s); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: + ccid_reset_error_status(s); + ccid_write_parameters(s, ccid_header); + break; + default: + DPRINTF(s, 1, + "handle_data: ERROR: unhandled message type %Xh\n", + ccid_header->bMessageType); + /* + * The caller is expecting the device to respond, tell it we + * don't support the operation. + */ + ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); + ccid_write_slot_status(s, ccid_header); + break; + } + } + s->bulk_out_pos = 0; + return 0; +} + +static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, USBPacket *p) +{ + int ret = 0; + + assert(p->iov.size > 0); + ccid_bulk_in_get(s); + if (s->current_bulk_in != NULL) { + ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, + p->iov.size); + usb_packet_copy(p, s->current_bulk_in->data + + s->current_bulk_in->pos, ret); + s->current_bulk_in->pos += ret; + if (s->current_bulk_in->pos == s->current_bulk_in->len) { + ccid_bulk_in_release(s); + } + } else { + /* return when device has no data - usb 2.0 spec Table 8-4 */ + ret = USB_RET_NAK; + } + if (ret > 0) { + DPRINTF(s, D_MORE_INFO, + "%s: %zd/%d req/act to guest (BULK_IN)\n", + __func__, p->iov.size, ret); + } + if (ret != USB_RET_NAK && ret < p->iov.size) { + DPRINTF(s, 1, + "%s: returning short (EREMOTEIO) %d < %zd\n", + __func__, ret, p->iov.size); + } + return ret; +} + +static int ccid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + uint8_t buf[2]; + + switch (p->pid) { + case USB_TOKEN_OUT: + ret = ccid_handle_bulk_out(s, p); + break; + + case USB_TOKEN_IN: + switch (p->ep->nr) { + case CCID_BULK_IN_EP: + if (!p->iov.size) { + ret = USB_RET_NAK; + } else { + ret = ccid_bulk_in_copy_to_guest(s, p); + } + break; + case CCID_INT_IN_EP: + if (s->notify_slot_change) { + /* page 56, RDR_to_PC_NotifySlotChange */ + buf[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; + buf[1] = s->bmSlotICCState; + usb_packet_copy(p, buf, 2); + ret = 2; + s->notify_slot_change = false; + s->bmSlotICCState &= ~SLOT_0_CHANGED_MASK; + DPRINTF(s, D_INFO, + "handle_data: int_in: notify_slot_change %X, " + "requested len %zd\n", + s->bmSlotICCState, p->iov.size); + } + break; + default: + DPRINTF(s, 1, "Bad endpoint\n"); + ret = USB_RET_STALL; + break; + } + break; + default: + DPRINTF(s, 1, "Bad token\n"); + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void ccid_handle_destroy(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + ccid_bulk_in_clear(s); +} + +static void ccid_flush_pending_answers(USBCCIDState *s) +{ + while (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +static Answer *ccid_peek_next_answer(USBCCIDState *s) +{ + return s->pending_answers_num == 0 + ? NULL + : &s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM]; +} + +static struct BusInfo ccid_bus_info = { + .name = "ccid-bus", + .size = sizeof(CCIDBus), + .props = (Property[]) { + DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, + uint8_t *apdu, uint32_t len) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, + card->qdev.parent_bus->parent); + Answer *answer; + + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n"); + return; + } + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + answer = ccid_peek_next_answer(s); + if (answer == NULL) { + abort(); + } + DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n", + len, answer->seq, answer->slot); + ccid_write_data_block_answer(s, apdu, len); +} + +void ccid_card_card_removed(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + ccid_on_slot_change(s, false); + ccid_flush_pending_answers(s); + ccid_reset(s); +} + +int ccid_card_ccid_attach(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Attach\n"); + if (s->migration_state == MIGRATION_MIGRATED) { + s->migration_state = MIGRATION_NONE; + } + return 0; +} + +void ccid_card_ccid_detach(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Detach\n"); + if (ccid_card_inserted(s)) { + ccid_on_slot_change(s, false); + } + ccid_detach(s); +} + +void ccid_card_card_error(CCIDCardState *card, uint64_t error) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->last_answer_error = error; + DPRINTF(s, 1, "VSC_Error: %" PRIX64 "\n", s->last_answer_error); + /* TODO: these errors should be more verbose and propagated to the guest.*/ + /* + * We flush all pending answers on CardRemove message in ccid-card-passthru, + * so check that first to not trigger abort + */ + if (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +void ccid_card_card_inserted(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + ccid_flush_pending_answers(s); + ccid_on_slot_change(s, true); +} + +static int ccid_card_exit(DeviceState *qdev) +{ + int ret = 0; + CCIDCardState *card = CCID_CARD(qdev); + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + if (ccid_card_inserted(s)) { + ccid_card_card_removed(card); + } + ret = ccid_card_exitfn(card); + s->card = NULL; + return ret; +} + +static int ccid_card_init(DeviceState *qdev) +{ + CCIDCardState *card = CCID_CARD(qdev); + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + int ret = 0; + + if (card->slot != 0) { + error_report("Warning: usb-ccid supports one slot, can't add %d", + card->slot); + return -1; + } + if (s->card != NULL) { + error_report("Warning: usb-ccid card already full, not adding"); + return -1; + } + ret = ccid_card_initfn(card); + if (ret == 0) { + s->card = card; + } + return ret; +} + +static int ccid_initfn(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + usb_desc_init(dev); + qbus_create_inplace(&s->bus.qbus, &ccid_bus_info, &dev->qdev, NULL); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, CCID_INT_IN_EP); + s->bus.qbus.allow_hotplug = 1; + s->card = NULL; + s->migration_state = MIGRATION_NONE; + s->migration_target_ip = 0; + s->migration_target_port = 0; + s->dev.speed = USB_SPEED_FULL; + s->dev.speedmask = USB_SPEED_MASK_FULL; + s->notify_slot_change = false; + s->powered = true; + s->pending_answers_num = 0; + s->last_answer_error = 0; + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->current_bulk_in = NULL; + ccid_reset_error_status(s); + s->bulk_out_pos = 0; + ccid_reset_parameters(s); + ccid_reset(s); + return 0; +} + +static int ccid_post_load(void *opaque, int version_id) +{ + USBCCIDState *s = opaque; + + /* + * This must be done after usb_device_attach, which sets state to ATTACHED, + * while it must be DEFAULT in order to accept packets (like it is after + * reset, but reset will reset our addr and call our reset handler which + * may change state, and we don't want to do that when migrating). + */ + s->dev.state = s->state_vmstate; + return 0; +} + +static void ccid_pre_save(void *opaque) +{ + USBCCIDState *s = opaque; + + s->state_vmstate = s->dev.state; + if (s->dev.attached) { + /* + * Migrating an open device, ignore reconnection CHR_EVENT to avoid an + * erroneous detach. + */ + s->migration_state = MIGRATION_MIGRATED; + } +} + +static VMStateDescription bulk_in_vmstate = { + .name = "CCID BulkIn state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BUFFER(data, BulkIn), + VMSTATE_UINT32(len, BulkIn), + VMSTATE_UINT32(pos, BulkIn), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription answer_vmstate = { + .name = "CCID Answer state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(slot, Answer), + VMSTATE_UINT8(seq, Answer), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription usb_device_vmstate = { + .name = "usb_device", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(addr, USBDevice), + VMSTATE_BUFFER(setup_buf, USBDevice), + VMSTATE_BUFFER(data_buf, USBDevice), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription ccid_vmstate = { + .name = CCID_DEV_NAME, + .version_id = 1, + .minimum_version_id = 1, + .post_load = ccid_post_load, + .pre_save = ccid_pre_save, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice), + VMSTATE_UINT8(debug, USBCCIDState), + VMSTATE_BUFFER(bulk_out_data, USBCCIDState), + VMSTATE_UINT32(bulk_out_pos, USBCCIDState), + VMSTATE_UINT8(bmSlotICCState, USBCCIDState), + VMSTATE_UINT8(powered, USBCCIDState), + VMSTATE_UINT8(notify_slot_change, USBCCIDState), + VMSTATE_UINT64(last_answer_error, USBCCIDState), + VMSTATE_UINT8(bError, USBCCIDState), + VMSTATE_UINT8(bmCommandStatus, USBCCIDState), + VMSTATE_UINT8(bProtocolNum, USBCCIDState), + VMSTATE_BUFFER(abProtocolDataStructure, USBCCIDState), + VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState), + VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState, + BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn), + VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState), + VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState), + VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState, + PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer), + VMSTATE_UINT32(pending_answers_num, USBCCIDState), + VMSTATE_UINT8(migration_state, USBCCIDState), + VMSTATE_UINT32(state_vmstate, USBCCIDState), + VMSTATE_END_OF_LIST() + } +}; + +static Property ccid_properties[] = { + DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ccid_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = ccid_initfn; + uc->product_desc = "QEMU USB CCID"; + uc->usb_desc = &desc_ccid; + uc->handle_reset = ccid_handle_reset; + uc->handle_control = ccid_handle_control; + uc->handle_data = ccid_handle_data; + uc->handle_destroy = ccid_handle_destroy; + dc->desc = "CCID Rev 1.1 smartcard reader"; + dc->vmsd = &ccid_vmstate; + dc->props = ccid_properties; +} + +static TypeInfo ccid_info = { + .name = CCID_DEV_NAME, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBCCIDState), + .class_init = ccid_class_initfn, +}; + +static void ccid_card_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->bus_info = &ccid_bus_info; + k->init = ccid_card_init; + k->exit = ccid_card_exit; +} + +static TypeInfo ccid_card_type_info = { + .name = TYPE_CCID_CARD, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CCIDCardState), + .abstract = true, + .class_size = sizeof(CCIDCardClass), + .class_init = ccid_card_class_init, +}; + +static void ccid_register_types(void) +{ + type_register_static(&ccid_card_type_info); + type_register_static(&ccid_info); + usb_legacy_register(CCID_DEV_NAME, "ccid", NULL); +} + +type_init(ccid_register_types) diff --git a/hw/usb/dev-storage.c b/hw/usb/dev-storage.c new file mode 100644 index 0000000..6ffaf70 --- /dev/null +++ b/hw/usb/dev-storage.c @@ -0,0 +1,677 @@ +/* + * USB Mass Storage Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the LGPL. + */ + +#include "qemu-common.h" +#include "qemu-option.h" +#include "qemu-config.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/scsi.h" +#include "console.h" +#include "monitor.h" +#include "sysemu.h" +#include "blockdev.h" + +//#define DEBUG_MSD + +#ifdef DEBUG_MSD +#define DPRINTF(fmt, ...) \ +do { printf("usb-msd: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +/* USB requests. */ +#define MassStorageReset 0xff +#define GetMaxLun 0xfe + +enum USBMSDMode { + USB_MSDM_CBW, /* Command Block. */ + USB_MSDM_DATAOUT, /* Transfer data to device. */ + USB_MSDM_DATAIN, /* Transfer data from device. */ + USB_MSDM_CSW /* Command Status. */ +}; + +struct usb_msd_csw { + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t status; +}; + +typedef struct { + USBDevice dev; + enum USBMSDMode mode; + uint32_t scsi_len; + uint8_t *scsi_buf; + uint32_t data_len; + uint32_t residue; + struct usb_msd_csw csw; + SCSIRequest *req; + SCSIBus bus; + BlockConf conf; + char *serial; + SCSIDevice *scsi_dev; + uint32_t removable; + /* For async completion. */ + USBPacket *packet; +} MSDState; + +struct usb_msd_cbw { + uint32_t sig; + uint32_t tag; + uint32_t data_len; + uint8_t flags; + uint8_t lun; + uint8_t cmd_len; + uint8_t cmd[16]; +}; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_CONFIG_FULL, + STR_CONFIG_HIGH, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_PRODUCT] = "QEMU USB HARDDRIVE", + [STR_SERIALNUMBER] = "1", + [STR_CONFIG_FULL] = "Full speed config (usb 1.1)", + [STR_CONFIG_HIGH] = "High speed config (usb 2.0)", +}; + +static const USBDescIface desc_iface_full = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = 0x06, /* SCSI */ + .bInterfaceProtocol = 0x50, /* Bulk */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + }, + } +}; + +static const USBDescDevice desc_device_full = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_FULL, + .bmAttributes = 0xc0, + .nif = 1, + .ifs = &desc_iface_full, + }, + }, +}; + +static const USBDescIface desc_iface_high = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = 0x06, /* SCSI */ + .bInterfaceProtocol = 0x50, /* Bulk */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + }, + } +}; + +static const USBDescDevice desc_device_high = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_HIGH, + .bmAttributes = 0xc0, + .nif = 1, + .ifs = &desc_iface_high, + }, + }, +}; + +static const USBDesc desc = { + .id = { + .idVendor = 0x46f4, /* CRC16() of "QEMU" */ + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_full, + .high = &desc_device_high, + .str = desc_strings, +}; + +static void usb_msd_copy_data(MSDState *s, USBPacket *p) +{ + uint32_t len; + len = p->iov.size - p->result; + if (len > s->scsi_len) + len = s->scsi_len; + usb_packet_copy(p, s->scsi_buf, len); + s->scsi_len -= len; + s->scsi_buf += len; + s->data_len -= len; + if (s->scsi_len == 0 || s->data_len == 0) { + scsi_req_continue(s->req); + } +} + +static void usb_msd_send_status(MSDState *s, USBPacket *p) +{ + int len; + + DPRINTF("Command status %d tag 0x%x, len %zd\n", + s->csw.status, s->csw.tag, p->iov.size); + + assert(s->csw.sig == 0x53425355); + len = MIN(sizeof(s->csw), p->iov.size); + usb_packet_copy(p, &s->csw, len); + memset(&s->csw, 0, sizeof(s->csw)); +} + +static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + USBPacket *p = s->packet; + + assert((s->mode == USB_MSDM_DATAOUT) == (req->cmd.mode == SCSI_XFER_TO_DEV)); + s->scsi_len = len; + s->scsi_buf = scsi_req_get_buf(req); + if (p) { + usb_msd_copy_data(s, p); + p = s->packet; + if (p && p->result == p->iov.size) { + /* Set s->packet to NULL before calling usb_packet_complete + because another request may be issued before + usb_packet_complete returns. */ + DPRINTF("Packet complete %p\n", p); + s->packet = NULL; + usb_packet_complete(&s->dev, p); + } + } +} + +static void usb_msd_command_complete(SCSIRequest *req, uint32_t status, size_t resid) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + USBPacket *p = s->packet; + + DPRINTF("Command complete %d tag 0x%x\n", status, req->tag); + s->residue = s->data_len; + + s->csw.sig = cpu_to_le32(0x53425355); + s->csw.tag = cpu_to_le32(req->tag); + s->csw.residue = s->residue; + s->csw.status = status != 0; + + if (s->packet) { + if (s->data_len == 0 && s->mode == USB_MSDM_DATAOUT) { + /* A deferred packet with no write data remaining must be + the status read packet. */ + usb_msd_send_status(s, p); + s->mode = USB_MSDM_CBW; + } else { + if (s->data_len) { + int len = (p->iov.size - p->result); + usb_packet_skip(p, len); + s->data_len -= len; + } + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + } + s->packet = NULL; + usb_packet_complete(&s->dev, p); + } else if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + scsi_req_unref(req); + s->req = NULL; +} + +static void usb_msd_request_cancelled(SCSIRequest *req) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + + if (req == s->req) { + scsi_req_unref(s->req); + s->req = NULL; + s->packet = NULL; + s->scsi_len = 0; + } +} + +static void usb_msd_handle_reset(USBDevice *dev) +{ + MSDState *s = (MSDState *)dev; + + DPRINTF("Reset\n"); + if (s->req) { + scsi_req_cancel(s->req); + } + assert(s->req == NULL); + + if (s->packet) { + USBPacket *p = s->packet; + s->packet = NULL; + p->result = USB_RET_STALL; + usb_packet_complete(dev, p); + } + + s->mode = USB_MSDM_CBW; +} + +static int usb_msd_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + MSDState *s = (MSDState *)dev; + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch (request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + break; + /* Class specific requests. */ + case ClassInterfaceOutRequest | MassStorageReset: + /* Reset state ready for the next CBW. */ + s->mode = USB_MSDM_CBW; + ret = 0; + break; + case ClassInterfaceRequest | GetMaxLun: + data[0] = 0; + ret = 1; + break; + default: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_msd_cancel_io(USBDevice *dev, USBPacket *p) +{ + MSDState *s = DO_UPCAST(MSDState, dev, dev); + + if (s->req) { + scsi_req_cancel(s->req); + } +} + +static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) +{ + MSDState *s = (MSDState *)dev; + uint32_t tag; + int ret = 0; + struct usb_msd_cbw cbw; + uint8_t devep = p->ep->nr; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) + goto fail; + + switch (s->mode) { + case USB_MSDM_CBW: + if (p->iov.size != 31) { + fprintf(stderr, "usb-msd: Bad CBW size"); + goto fail; + } + usb_packet_copy(p, &cbw, 31); + if (le32_to_cpu(cbw.sig) != 0x43425355) { + fprintf(stderr, "usb-msd: Bad signature %08x\n", + le32_to_cpu(cbw.sig)); + goto fail; + } + DPRINTF("Command on LUN %d\n", cbw.lun); + if (cbw.lun != 0) { + fprintf(stderr, "usb-msd: Bad LUN %d\n", cbw.lun); + goto fail; + } + tag = le32_to_cpu(cbw.tag); + s->data_len = le32_to_cpu(cbw.data_len); + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } else if (cbw.flags & 0x80) { + s->mode = USB_MSDM_DATAIN; + } else { + s->mode = USB_MSDM_DATAOUT; + } + DPRINTF("Command tag 0x%x flags %08x len %d data %d\n", + tag, cbw.flags, cbw.cmd_len, s->data_len); + s->residue = 0; + s->scsi_len = 0; + s->req = scsi_req_new(s->scsi_dev, tag, 0, cbw.cmd, NULL); + scsi_req_enqueue(s->req); + if (s->req && s->req->cmd.xfer != SCSI_XFER_NONE) { + scsi_req_continue(s->req); + } + ret = p->result; + break; + + case USB_MSDM_DATAOUT: + DPRINTF("Data out %zd/%d\n", p->iov.size, s->data_len); + if (p->iov.size > s->data_len) { + goto fail; + } + + if (s->scsi_len) { + usb_msd_copy_data(s, p); + } + if (s->residue) { + int len = p->iov.size - p->result; + if (len) { + usb_packet_skip(p, len); + s->data_len -= len; + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + } + } + if (p->result < p->iov.size) { + DPRINTF("Deferring packet %p\n", p); + s->packet = p; + ret = USB_RET_ASYNC; + } else { + ret = p->result; + } + break; + + default: + DPRINTF("Unexpected write (len %zd)\n", p->iov.size); + goto fail; + } + break; + + case USB_TOKEN_IN: + if (devep != 1) + goto fail; + + switch (s->mode) { + case USB_MSDM_DATAOUT: + if (s->data_len != 0 || p->iov.size < 13) { + goto fail; + } + /* Waiting for SCSI write to complete. */ + s->packet = p; + ret = USB_RET_ASYNC; + break; + + case USB_MSDM_CSW: + if (p->iov.size < 13) { + goto fail; + } + + if (s->req) { + /* still in flight */ + s->packet = p; + ret = USB_RET_ASYNC; + } else { + usb_msd_send_status(s, p); + s->mode = USB_MSDM_CBW; + ret = 13; + } + break; + + case USB_MSDM_DATAIN: + DPRINTF("Data in %zd/%d, scsi_len %d\n", + p->iov.size, s->data_len, s->scsi_len); + if (s->scsi_len) { + usb_msd_copy_data(s, p); + } + if (s->residue) { + int len = p->iov.size - p->result; + if (len) { + usb_packet_skip(p, len); + s->data_len -= len; + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + } + } + if (p->result < p->iov.size) { + DPRINTF("Deferring packet %p\n", p); + s->packet = p; + ret = USB_RET_ASYNC; + } else { + ret = p->result; + } + break; + + default: + DPRINTF("Unexpected read (len %zd)\n", p->iov.size); + goto fail; + } + break; + + default: + DPRINTF("Bad token\n"); + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_msd_password_cb(void *opaque, int err) +{ + MSDState *s = opaque; + + if (!err) + err = usb_device_attach(&s->dev); + + if (err) + qdev_unplug(&s->dev.qdev); +} + +static const struct SCSIBusInfo usb_msd_scsi_info = { + .tcq = false, + .max_target = 0, + .max_lun = 0, + + .transfer_data = usb_msd_transfer_data, + .complete = usb_msd_command_complete, + .cancel = usb_msd_request_cancelled +}; + +static int usb_msd_initfn(USBDevice *dev) +{ + MSDState *s = DO_UPCAST(MSDState, dev, dev); + BlockDriverState *bs = s->conf.bs; + DriveInfo *dinfo; + + if (!bs) { + error_report("drive property not set"); + return -1; + } + + /* + * Hack alert: this pretends to be a block device, but it's really + * a SCSI bus that can serve only a single device, which it + * creates automatically. But first it needs to detach from its + * blockdev, or else scsi_bus_legacy_add_drive() dies when it + * attaches again. + * + * The hack is probably a bad idea. + */ + bdrv_detach_dev(bs, &s->dev.qdev); + s->conf.bs = NULL; + + if (!s->serial) { + /* try to fall back to value set with legacy -drive serial=... */ + dinfo = drive_get_by_blockdev(bs); + if (*dinfo->serial) { + s->serial = strdup(dinfo->serial); + } + } + if (s->serial) { + usb_desc_set_string(dev, STR_SERIALNUMBER, s->serial); + } + + usb_desc_init(dev); + scsi_bus_new(&s->bus, &s->dev.qdev, &usb_msd_scsi_info); + s->scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0, !!s->removable, + s->conf.bootindex); + if (!s->scsi_dev) { + return -1; + } + s->bus.qbus.allow_hotplug = 0; + usb_msd_handle_reset(dev); + + if (bdrv_key_required(bs)) { + if (cur_mon) { + monitor_read_bdrv_key_start(cur_mon, bs, usb_msd_password_cb, s); + s->dev.auto_attach = 0; + } else { + autostart = 0; + } + } + + return 0; +} + +static USBDevice *usb_msd_init(USBBus *bus, const char *filename) +{ + static int nr=0; + char id[8]; + QemuOpts *opts; + DriveInfo *dinfo; + USBDevice *dev; + const char *p1; + char fmt[32]; + + /* parse -usbdevice disk: syntax into drive opts */ + snprintf(id, sizeof(id), "usb%d", nr++); + opts = qemu_opts_create(qemu_find_opts("drive"), id, 0); + + p1 = strchr(filename, ':'); + if (p1++) { + const char *p2; + + if (strstart(filename, "format=", &p2)) { + int len = MIN(p1 - p2, sizeof(fmt)); + pstrcpy(fmt, len, p2); + qemu_opt_set(opts, "format", fmt); + } else if (*filename != ':') { + printf("unrecognized USB mass-storage option %s\n", filename); + return NULL; + } + filename = p1; + } + if (!*filename) { + printf("block device specification needed\n"); + return NULL; + } + qemu_opt_set(opts, "file", filename); + qemu_opt_set(opts, "if", "none"); + + /* create host drive */ + dinfo = drive_init(opts, 0); + if (!dinfo) { + qemu_opts_del(opts); + return NULL; + } + + /* create guest device */ + dev = usb_create(bus, "usb-storage"); + if (!dev) { + return NULL; + } + if (qdev_prop_set_drive(&dev->qdev, "drive", dinfo->bdrv) < 0) { + qdev_free(&dev->qdev); + return NULL; + } + if (qdev_init(&dev->qdev) < 0) + return NULL; + + return dev; +} + +static const VMStateDescription vmstate_usb_msd = { + .name = "usb-storage", + .unmigratable = 1, /* FIXME: handle transactions which are in flight */ + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_USB_DEVICE(dev, MSDState), + VMSTATE_END_OF_LIST() + } +}; + +static Property msd_properties[] = { + DEFINE_BLOCK_PROPERTIES(MSDState, conf), + DEFINE_PROP_STRING("serial", MSDState, serial), + DEFINE_PROP_BIT("removable", MSDState, removable, 0, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_msd_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_msd_initfn; + uc->product_desc = "QEMU USB MSD"; + uc->usb_desc = &desc; + uc->cancel_packet = usb_msd_cancel_io; + uc->handle_attach = usb_desc_attach; + uc->handle_reset = usb_msd_handle_reset; + uc->handle_control = usb_msd_handle_control; + uc->handle_data = usb_msd_handle_data; + dc->fw_name = "storage"; + dc->vmsd = &vmstate_usb_msd; + dc->props = msd_properties; +} + +static TypeInfo msd_info = { + .name = "usb-storage", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(MSDState), + .class_init = usb_msd_class_initfn, +}; + +static void usb_msd_register_types(void) +{ + type_register_static(&msd_info); + usb_legacy_register("usb-storage", "disk", usb_msd_init); +} + +type_init(usb_msd_register_types) diff --git a/hw/usb/dev-wacom.c b/hw/usb/dev-wacom.c new file mode 100644 index 0000000..c1cfd74 --- /dev/null +++ b/hw/usb/dev-wacom.c @@ -0,0 +1,381 @@ +/* + * Wacom PenPartner USB tablet emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Author: Andrzej Zaborowski <balrog@zabor.org> + * + * Based on hw/usb-hid.c: + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "console.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" + +/* Interface requests */ +#define WACOM_GET_REPORT 0x2101 +#define WACOM_SET_REPORT 0x2109 + +/* HID interface requests */ +#define HID_GET_REPORT 0xa101 +#define HID_GET_IDLE 0xa102 +#define HID_GET_PROTOCOL 0xa103 +#define HID_SET_IDLE 0x210a +#define HID_SET_PROTOCOL 0x210b + +typedef struct USBWacomState { + USBDevice dev; + QEMUPutMouseEntry *eh_entry; + int dx, dy, dz, buttons_state; + int x, y; + int mouse_grabbed; + enum { + WACOM_MODE_HID = 1, + WACOM_MODE_WACOM = 2, + } mode; + uint8_t idle; + int changed; +} USBWacomState; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_PRODUCT] = "Wacom PenPartner", + [STR_SERIALNUMBER] = "1", +}; + +static const USBDescIface desc_iface_wacom = { + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0x01, /* boot */ + .bInterfaceProtocol = 0x02, + .ndesc = 1, + .descs = (USBDescOther[]) { + { + /* HID descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + 0x21, /* u8 bDescriptorType */ + 0x01, 0x10, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + 0x22, /* u8 type: Report */ + 0x6e, 0, /* u16 len */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 8, + .bInterval = 0x0a, + }, + }, +}; + +static const USBDescDevice desc_device_wacom = { + .bcdUSB = 0x0110, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = 0x80, + .bMaxPower = 40, + .nif = 1, + .ifs = &desc_iface_wacom, + }, + }, +}; + +static const USBDesc desc_wacom = { + .id = { + .idVendor = 0x056a, + .idProduct = 0x0000, + .bcdDevice = 0x4210, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_wacom, + .str = desc_strings, +}; + +static void usb_mouse_event(void *opaque, + int dx1, int dy1, int dz1, int buttons_state) +{ + USBWacomState *s = opaque; + + s->dx += dx1; + s->dy += dy1; + s->dz += dz1; + s->buttons_state = buttons_state; + s->changed = 1; +} + +static void usb_wacom_event(void *opaque, + int x, int y, int dz, int buttons_state) +{ + USBWacomState *s = opaque; + + /* scale to Penpartner resolution */ + s->x = (x * 5040 / 0x7FFF); + s->y = (y * 3780 / 0x7FFF); + s->dz += dz; + s->buttons_state = buttons_state; + s->changed = 1; +} + +static inline int int_clamp(int val, int vmin, int vmax) +{ + if (val < vmin) + return vmin; + else if (val > vmax) + return vmax; + else + return val; +} + +static int usb_mouse_poll(USBWacomState *s, uint8_t *buf, int len) +{ + int dx, dy, dz, b, l; + + if (!s->mouse_grabbed) { + s->eh_entry = qemu_add_mouse_event_handler(usb_mouse_event, s, 0, + "QEMU PenPartner tablet"); + qemu_activate_mouse_event_handler(s->eh_entry); + s->mouse_grabbed = 1; + } + + dx = int_clamp(s->dx, -128, 127); + dy = int_clamp(s->dy, -128, 127); + dz = int_clamp(s->dz, -128, 127); + + s->dx -= dx; + s->dy -= dy; + s->dz -= dz; + + b = 0; + if (s->buttons_state & MOUSE_EVENT_LBUTTON) + b |= 0x01; + if (s->buttons_state & MOUSE_EVENT_RBUTTON) + b |= 0x02; + if (s->buttons_state & MOUSE_EVENT_MBUTTON) + b |= 0x04; + + buf[0] = b; + buf[1] = dx; + buf[2] = dy; + l = 3; + if (len >= 4) { + buf[3] = dz; + l = 4; + } + return l; +} + +static int usb_wacom_poll(USBWacomState *s, uint8_t *buf, int len) +{ + int b; + + if (!s->mouse_grabbed) { + s->eh_entry = qemu_add_mouse_event_handler(usb_wacom_event, s, 1, + "QEMU PenPartner tablet"); + qemu_activate_mouse_event_handler(s->eh_entry); + s->mouse_grabbed = 1; + } + + b = 0; + if (s->buttons_state & MOUSE_EVENT_LBUTTON) + b |= 0x01; + if (s->buttons_state & MOUSE_EVENT_RBUTTON) + b |= 0x40; + if (s->buttons_state & MOUSE_EVENT_MBUTTON) + b |= 0x20; /* eraser */ + + if (len < 7) + return 0; + + buf[0] = s->mode; + buf[5] = 0x00 | (b & 0xf0); + buf[1] = s->x & 0xff; + buf[2] = s->x >> 8; + buf[3] = s->y & 0xff; + buf[4] = s->y >> 8; + if (b & 0x3f) { + buf[6] = 0; + } else { + buf[6] = (unsigned char) -127; + } + + return 7; +} + +static void usb_wacom_handle_reset(USBDevice *dev) +{ + USBWacomState *s = (USBWacomState *) dev; + + s->dx = 0; + s->dy = 0; + s->dz = 0; + s->x = 0; + s->y = 0; + s->buttons_state = 0; + s->mode = WACOM_MODE_HID; +} + +static int usb_wacom_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBWacomState *s = (USBWacomState *) dev; + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch (request) { + case WACOM_SET_REPORT: + if (s->mouse_grabbed) { + qemu_remove_mouse_event_handler(s->eh_entry); + s->mouse_grabbed = 0; + } + s->mode = data[0]; + ret = 0; + break; + case WACOM_GET_REPORT: + data[0] = 0; + data[1] = s->mode; + ret = 2; + break; + /* USB HID requests */ + case HID_GET_REPORT: + if (s->mode == WACOM_MODE_HID) + ret = usb_mouse_poll(s, data, length); + else if (s->mode == WACOM_MODE_WACOM) + ret = usb_wacom_poll(s, data, length); + break; + case HID_GET_IDLE: + ret = 1; + data[0] = s->idle; + break; + case HID_SET_IDLE: + s->idle = (uint8_t) (value >> 8); + ret = 0; + break; + default: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_wacom_handle_data(USBDevice *dev, USBPacket *p) +{ + USBWacomState *s = (USBWacomState *) dev; + uint8_t buf[p->iov.size]; + int ret = 0; + + switch (p->pid) { + case USB_TOKEN_IN: + if (p->ep->nr == 1) { + if (!(s->changed || s->idle)) + return USB_RET_NAK; + s->changed = 0; + if (s->mode == WACOM_MODE_HID) + ret = usb_mouse_poll(s, buf, p->iov.size); + else if (s->mode == WACOM_MODE_WACOM) + ret = usb_wacom_poll(s, buf, p->iov.size); + usb_packet_copy(p, buf, ret); + break; + } + /* Fall through. */ + case USB_TOKEN_OUT: + default: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_wacom_handle_destroy(USBDevice *dev) +{ + USBWacomState *s = (USBWacomState *) dev; + + if (s->mouse_grabbed) { + qemu_remove_mouse_event_handler(s->eh_entry); + s->mouse_grabbed = 0; + } +} + +static int usb_wacom_initfn(USBDevice *dev) +{ + USBWacomState *s = DO_UPCAST(USBWacomState, dev, dev); + usb_desc_init(dev); + s->changed = 1; + return 0; +} + +static const VMStateDescription vmstate_usb_wacom = { + .name = "usb-wacom", + .unmigratable = 1, +}; + +static void usb_wacom_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "QEMU PenPartner Tablet"; + uc->usb_desc = &desc_wacom; + uc->init = usb_wacom_initfn; + uc->handle_reset = usb_wacom_handle_reset; + uc->handle_control = usb_wacom_handle_control; + uc->handle_data = usb_wacom_handle_data; + uc->handle_destroy = usb_wacom_handle_destroy; + dc->desc = "QEMU PenPartner Tablet"; + dc->vmsd = &vmstate_usb_wacom; +} + +static TypeInfo wacom_info = { + .name = "usb-wacom-tablet", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBWacomState), + .class_init = usb_wacom_class_init, +}; + +static void usb_wacom_register_types(void) +{ + type_register_static(&wacom_info); + usb_legacy_register("usb-wacom-tablet", "wacom-tablet", NULL); +} + +type_init(usb_wacom_register_types) diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c new file mode 100644 index 0000000..58811d3 --- /dev/null +++ b/hw/usb/hcd-ehci.c @@ -0,0 +1,2345 @@ +/* + * QEMU USB EHCI Emulation + * + * Copyright(c) 2008 Emutex Ltd. (address@hidden) + * + * EHCI project was started by Mark Burkley, with contributions by + * Niels de Vos. David S. Ahern continued working on it. Kevin Wolf, + * Jan Kiszka and Vincent Palatin contributed bugfixes. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/hw.h" +#include "qemu-timer.h" +#include "hw/usb.h" +#include "hw/pci.h" +#include "monitor.h" +#include "trace.h" +#include "dma.h" + +#define EHCI_DEBUG 0 + +#if EHCI_DEBUG +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +/* internal processing - reset HC to try and recover */ +#define USB_RET_PROCERR (-99) + +#define MMIO_SIZE 0x1000 + +/* Capability Registers Base Address - section 2.2 */ +#define CAPREGBASE 0x0000 +#define CAPLENGTH CAPREGBASE + 0x0000 // 1-byte, 0x0001 reserved +#define HCIVERSION CAPREGBASE + 0x0002 // 2-bytes, i/f version # +#define HCSPARAMS CAPREGBASE + 0x0004 // 4-bytes, structural params +#define HCCPARAMS CAPREGBASE + 0x0008 // 4-bytes, capability params +#define EECP HCCPARAMS + 1 +#define HCSPPORTROUTE1 CAPREGBASE + 0x000c +#define HCSPPORTROUTE2 CAPREGBASE + 0x0010 + +#define OPREGBASE 0x0020 // Operational Registers Base Address + +#define USBCMD OPREGBASE + 0x0000 +#define USBCMD_RUNSTOP (1 << 0) // run / Stop +#define USBCMD_HCRESET (1 << 1) // HC Reset +#define USBCMD_FLS (3 << 2) // Frame List Size +#define USBCMD_FLS_SH 2 // Frame List Size Shift +#define USBCMD_PSE (1 << 4) // Periodic Schedule Enable +#define USBCMD_ASE (1 << 5) // Asynch Schedule Enable +#define USBCMD_IAAD (1 << 6) // Int Asynch Advance Doorbell +#define USBCMD_LHCR (1 << 7) // Light Host Controller Reset +#define USBCMD_ASPMC (3 << 8) // Async Sched Park Mode Count +#define USBCMD_ASPME (1 << 11) // Async Sched Park Mode Enable +#define USBCMD_ITC (0x7f << 16) // Int Threshold Control +#define USBCMD_ITC_SH 16 // Int Threshold Control Shift + +#define USBSTS OPREGBASE + 0x0004 +#define USBSTS_RO_MASK 0x0000003f +#define USBSTS_INT (1 << 0) // USB Interrupt +#define USBSTS_ERRINT (1 << 1) // Error Interrupt +#define USBSTS_PCD (1 << 2) // Port Change Detect +#define USBSTS_FLR (1 << 3) // Frame List Rollover +#define USBSTS_HSE (1 << 4) // Host System Error +#define USBSTS_IAA (1 << 5) // Interrupt on Async Advance +#define USBSTS_HALT (1 << 12) // HC Halted +#define USBSTS_REC (1 << 13) // Reclamation +#define USBSTS_PSS (1 << 14) // Periodic Schedule Status +#define USBSTS_ASS (1 << 15) // Asynchronous Schedule Status + +/* + * Interrupt enable bits correspond to the interrupt active bits in USBSTS + * so no need to redefine here. + */ +#define USBINTR OPREGBASE + 0x0008 +#define USBINTR_MASK 0x0000003f + +#define FRINDEX OPREGBASE + 0x000c +#define CTRLDSSEGMENT OPREGBASE + 0x0010 +#define PERIODICLISTBASE OPREGBASE + 0x0014 +#define ASYNCLISTADDR OPREGBASE + 0x0018 +#define ASYNCLISTADDR_MASK 0xffffffe0 + +#define CONFIGFLAG OPREGBASE + 0x0040 + +#define PORTSC (OPREGBASE + 0x0044) +#define PORTSC_BEGIN PORTSC +#define PORTSC_END (PORTSC + 4 * NB_PORTS) +/* + * Bits that are reserved or are read-only are masked out of values + * written to us by software + */ +#define PORTSC_RO_MASK 0x007001c0 +#define PORTSC_RWC_MASK 0x0000002a +#define PORTSC_WKOC_E (1 << 22) // Wake on Over Current Enable +#define PORTSC_WKDS_E (1 << 21) // Wake on Disconnect Enable +#define PORTSC_WKCN_E (1 << 20) // Wake on Connect Enable +#define PORTSC_PTC (15 << 16) // Port Test Control +#define PORTSC_PTC_SH 16 // Port Test Control shift +#define PORTSC_PIC (3 << 14) // Port Indicator Control +#define PORTSC_PIC_SH 14 // Port Indicator Control Shift +#define PORTSC_POWNER (1 << 13) // Port Owner +#define PORTSC_PPOWER (1 << 12) // Port Power +#define PORTSC_LINESTAT (3 << 10) // Port Line Status +#define PORTSC_LINESTAT_SH 10 // Port Line Status Shift +#define PORTSC_PRESET (1 << 8) // Port Reset +#define PORTSC_SUSPEND (1 << 7) // Port Suspend +#define PORTSC_FPRES (1 << 6) // Force Port Resume +#define PORTSC_OCC (1 << 5) // Over Current Change +#define PORTSC_OCA (1 << 4) // Over Current Active +#define PORTSC_PEDC (1 << 3) // Port Enable/Disable Change +#define PORTSC_PED (1 << 2) // Port Enable/Disable +#define PORTSC_CSC (1 << 1) // Connect Status Change +#define PORTSC_CONNECT (1 << 0) // Current Connect Status + +#define FRAME_TIMER_FREQ 1000 +#define FRAME_TIMER_NS (1000000000 / FRAME_TIMER_FREQ) + +#define NB_MAXINTRATE 8 // Max rate at which controller issues ints +#define NB_PORTS 6 // Number of downstream ports +#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction +#define MAX_ITERATIONS 20 // Max number of QH before we break the loop +#define MAX_QH 100 // Max allowable queue heads in a chain + +/* Internal periodic / asynchronous schedule state machine states + */ +typedef enum { + EST_INACTIVE = 1000, + EST_ACTIVE, + EST_EXECUTING, + EST_SLEEPING, + /* The following states are internal to the state machine function + */ + EST_WAITLISTHEAD, + EST_FETCHENTRY, + EST_FETCHQH, + EST_FETCHITD, + EST_FETCHSITD, + EST_ADVANCEQUEUE, + EST_FETCHQTD, + EST_EXECUTE, + EST_WRITEBACK, + EST_HORIZONTALQH +} EHCI_STATES; + +/* macros for accessing fields within next link pointer entry */ +#define NLPTR_GET(x) ((x) & 0xffffffe0) +#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3) +#define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid + +/* link pointer types */ +#define NLPTR_TYPE_ITD 0 // isoc xfer descriptor +#define NLPTR_TYPE_QH 1 // queue head +#define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor +#define NLPTR_TYPE_FSTN 3 // frame span traversal node + + +/* EHCI spec version 1.0 Section 3.3 + */ +typedef struct EHCIitd { + uint32_t next; + + uint32_t transact[8]; +#define ITD_XACT_ACTIVE (1 << 31) +#define ITD_XACT_DBERROR (1 << 30) +#define ITD_XACT_BABBLE (1 << 29) +#define ITD_XACT_XACTERR (1 << 28) +#define ITD_XACT_LENGTH_MASK 0x0fff0000 +#define ITD_XACT_LENGTH_SH 16 +#define ITD_XACT_IOC (1 << 15) +#define ITD_XACT_PGSEL_MASK 0x00007000 +#define ITD_XACT_PGSEL_SH 12 +#define ITD_XACT_OFFSET_MASK 0x00000fff + + uint32_t bufptr[7]; +#define ITD_BUFPTR_MASK 0xfffff000 +#define ITD_BUFPTR_SH 12 +#define ITD_BUFPTR_EP_MASK 0x00000f00 +#define ITD_BUFPTR_EP_SH 8 +#define ITD_BUFPTR_DEVADDR_MASK 0x0000007f +#define ITD_BUFPTR_DEVADDR_SH 0 +#define ITD_BUFPTR_DIRECTION (1 << 11) +#define ITD_BUFPTR_MAXPKT_MASK 0x000007ff +#define ITD_BUFPTR_MAXPKT_SH 0 +#define ITD_BUFPTR_MULT_MASK 0x00000003 +#define ITD_BUFPTR_MULT_SH 0 +} EHCIitd; + +/* EHCI spec version 1.0 Section 3.4 + */ +typedef struct EHCIsitd { + uint32_t next; // Standard next link pointer + uint32_t epchar; +#define SITD_EPCHAR_IO (1 << 31) +#define SITD_EPCHAR_PORTNUM_MASK 0x7f000000 +#define SITD_EPCHAR_PORTNUM_SH 24 +#define SITD_EPCHAR_HUBADD_MASK 0x007f0000 +#define SITD_EPCHAR_HUBADDR_SH 16 +#define SITD_EPCHAR_EPNUM_MASK 0x00000f00 +#define SITD_EPCHAR_EPNUM_SH 8 +#define SITD_EPCHAR_DEVADDR_MASK 0x0000007f + + uint32_t uframe; +#define SITD_UFRAME_CMASK_MASK 0x0000ff00 +#define SITD_UFRAME_CMASK_SH 8 +#define SITD_UFRAME_SMASK_MASK 0x000000ff + + uint32_t results; +#define SITD_RESULTS_IOC (1 << 31) +#define SITD_RESULTS_PGSEL (1 << 30) +#define SITD_RESULTS_TBYTES_MASK 0x03ff0000 +#define SITD_RESULTS_TYBYTES_SH 16 +#define SITD_RESULTS_CPROGMASK_MASK 0x0000ff00 +#define SITD_RESULTS_CPROGMASK_SH 8 +#define SITD_RESULTS_ACTIVE (1 << 7) +#define SITD_RESULTS_ERR (1 << 6) +#define SITD_RESULTS_DBERR (1 << 5) +#define SITD_RESULTS_BABBLE (1 << 4) +#define SITD_RESULTS_XACTERR (1 << 3) +#define SITD_RESULTS_MISSEDUF (1 << 2) +#define SITD_RESULTS_SPLITXSTATE (1 << 1) + + uint32_t bufptr[2]; +#define SITD_BUFPTR_MASK 0xfffff000 +#define SITD_BUFPTR_CURROFF_MASK 0x00000fff +#define SITD_BUFPTR_TPOS_MASK 0x00000018 +#define SITD_BUFPTR_TPOS_SH 3 +#define SITD_BUFPTR_TCNT_MASK 0x00000007 + + uint32_t backptr; // Standard next link pointer +} EHCIsitd; + +/* EHCI spec version 1.0 Section 3.5 + */ +typedef struct EHCIqtd { + uint32_t next; // Standard next link pointer + uint32_t altnext; // Standard next link pointer + uint32_t token; +#define QTD_TOKEN_DTOGGLE (1 << 31) +#define QTD_TOKEN_TBYTES_MASK 0x7fff0000 +#define QTD_TOKEN_TBYTES_SH 16 +#define QTD_TOKEN_IOC (1 << 15) +#define QTD_TOKEN_CPAGE_MASK 0x00007000 +#define QTD_TOKEN_CPAGE_SH 12 +#define QTD_TOKEN_CERR_MASK 0x00000c00 +#define QTD_TOKEN_CERR_SH 10 +#define QTD_TOKEN_PID_MASK 0x00000300 +#define QTD_TOKEN_PID_SH 8 +#define QTD_TOKEN_ACTIVE (1 << 7) +#define QTD_TOKEN_HALT (1 << 6) +#define QTD_TOKEN_DBERR (1 << 5) +#define QTD_TOKEN_BABBLE (1 << 4) +#define QTD_TOKEN_XACTERR (1 << 3) +#define QTD_TOKEN_MISSEDUF (1 << 2) +#define QTD_TOKEN_SPLITXSTATE (1 << 1) +#define QTD_TOKEN_PING (1 << 0) + + uint32_t bufptr[5]; // Standard buffer pointer +#define QTD_BUFPTR_MASK 0xfffff000 +#define QTD_BUFPTR_SH 12 +} EHCIqtd; + +/* EHCI spec version 1.0 Section 3.6 + */ +typedef struct EHCIqh { + uint32_t next; // Standard next link pointer + + /* endpoint characteristics */ + uint32_t epchar; +#define QH_EPCHAR_RL_MASK 0xf0000000 +#define QH_EPCHAR_RL_SH 28 +#define QH_EPCHAR_C (1 << 27) +#define QH_EPCHAR_MPLEN_MASK 0x07FF0000 +#define QH_EPCHAR_MPLEN_SH 16 +#define QH_EPCHAR_H (1 << 15) +#define QH_EPCHAR_DTC (1 << 14) +#define QH_EPCHAR_EPS_MASK 0x00003000 +#define QH_EPCHAR_EPS_SH 12 +#define EHCI_QH_EPS_FULL 0 +#define EHCI_QH_EPS_LOW 1 +#define EHCI_QH_EPS_HIGH 2 +#define EHCI_QH_EPS_RESERVED 3 + +#define QH_EPCHAR_EP_MASK 0x00000f00 +#define QH_EPCHAR_EP_SH 8 +#define QH_EPCHAR_I (1 << 7) +#define QH_EPCHAR_DEVADDR_MASK 0x0000007f +#define QH_EPCHAR_DEVADDR_SH 0 + + /* endpoint capabilities */ + uint32_t epcap; +#define QH_EPCAP_MULT_MASK 0xc0000000 +#define QH_EPCAP_MULT_SH 30 +#define QH_EPCAP_PORTNUM_MASK 0x3f800000 +#define QH_EPCAP_PORTNUM_SH 23 +#define QH_EPCAP_HUBADDR_MASK 0x007f0000 +#define QH_EPCAP_HUBADDR_SH 16 +#define QH_EPCAP_CMASK_MASK 0x0000ff00 +#define QH_EPCAP_CMASK_SH 8 +#define QH_EPCAP_SMASK_MASK 0x000000ff +#define QH_EPCAP_SMASK_SH 0 + + uint32_t current_qtd; // Standard next link pointer + uint32_t next_qtd; // Standard next link pointer + uint32_t altnext_qtd; +#define QH_ALTNEXT_NAKCNT_MASK 0x0000001e +#define QH_ALTNEXT_NAKCNT_SH 1 + + uint32_t token; // Same as QTD token + uint32_t bufptr[5]; // Standard buffer pointer +#define BUFPTR_CPROGMASK_MASK 0x000000ff +#define BUFPTR_FRAMETAG_MASK 0x0000001f +#define BUFPTR_SBYTES_MASK 0x00000fe0 +#define BUFPTR_SBYTES_SH 5 +} EHCIqh; + +/* EHCI spec version 1.0 Section 3.7 + */ +typedef struct EHCIfstn { + uint32_t next; // Standard next link pointer + uint32_t backptr; // Standard next link pointer +} EHCIfstn; + +typedef struct EHCIQueue EHCIQueue; +typedef struct EHCIState EHCIState; + +enum async_state { + EHCI_ASYNC_NONE = 0, + EHCI_ASYNC_INFLIGHT, + EHCI_ASYNC_FINISHED, +}; + +struct EHCIQueue { + EHCIState *ehci; + QTAILQ_ENTRY(EHCIQueue) next; + uint32_t seen; + uint64_t ts; + + /* cached data from guest - needs to be flushed + * when guest removes an entry (doorbell, handshake sequence) + */ + EHCIqh qh; // copy of current QH (being worked on) + uint32_t qhaddr; // address QH read from + EHCIqtd qtd; // copy of current QTD (being worked on) + uint32_t qtdaddr; // address QTD read from + + USBPacket packet; + QEMUSGList sgl; + int pid; + uint32_t tbytes; + enum async_state async; + int usb_status; +}; + +typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead; + +struct EHCIState { + PCIDevice dev; + USBBus bus; + qemu_irq irq; + MemoryRegion mem; + int companion_count; + + /* properties */ + uint32_t freq; + uint32_t maxframes; + + /* + * EHCI spec version 1.0 Section 2.3 + * Host Controller Operational Registers + */ + union { + uint8_t mmio[MMIO_SIZE]; + struct { + uint8_t cap[OPREGBASE]; + uint32_t usbcmd; + uint32_t usbsts; + uint32_t usbintr; + uint32_t frindex; + uint32_t ctrldssegment; + uint32_t periodiclistbase; + uint32_t asynclistaddr; + uint32_t notused[9]; + uint32_t configflag; + uint32_t portsc[NB_PORTS]; + }; + }; + + /* + * Internal states, shadow registers, etc + */ + uint32_t sofv; + QEMUTimer *frame_timer; + int attach_poll_counter; + int astate; // Current state in asynchronous schedule + int pstate; // Current state in periodic schedule + USBPort ports[NB_PORTS]; + USBPort *companion_ports[NB_PORTS]; + uint32_t usbsts_pending; + EHCIQueueHead aqueues; + EHCIQueueHead pqueues; + + uint32_t a_fetch_addr; // which address to look at next + uint32_t p_fetch_addr; // which address to look at next + + USBPacket ipacket; + QEMUSGList isgl; + int isoch_pause; + + uint64_t last_run_ns; +}; + +#define SET_LAST_RUN_CLOCK(s) \ + (s)->last_run_ns = qemu_get_clock_ns(vm_clock); + +/* nifty macros from Arnon's EHCI version */ +#define get_field(data, field) \ + (((data) & field##_MASK) >> field##_SH) + +#define set_field(data, newval, field) do { \ + uint32_t val = *data; \ + val &= ~ field##_MASK; \ + val |= ((newval) << field##_SH) & field##_MASK; \ + *data = val; \ + } while(0) + +static const char *ehci_state_names[] = { + [EST_INACTIVE] = "INACTIVE", + [EST_ACTIVE] = "ACTIVE", + [EST_EXECUTING] = "EXECUTING", + [EST_SLEEPING] = "SLEEPING", + [EST_WAITLISTHEAD] = "WAITLISTHEAD", + [EST_FETCHENTRY] = "FETCH ENTRY", + [EST_FETCHQH] = "FETCH QH", + [EST_FETCHITD] = "FETCH ITD", + [EST_ADVANCEQUEUE] = "ADVANCEQUEUE", + [EST_FETCHQTD] = "FETCH QTD", + [EST_EXECUTE] = "EXECUTE", + [EST_WRITEBACK] = "WRITEBACK", + [EST_HORIZONTALQH] = "HORIZONTALQH", +}; + +static const char *ehci_mmio_names[] = { + [CAPLENGTH] = "CAPLENGTH", + [HCIVERSION] = "HCIVERSION", + [HCSPARAMS] = "HCSPARAMS", + [HCCPARAMS] = "HCCPARAMS", + [USBCMD] = "USBCMD", + [USBSTS] = "USBSTS", + [USBINTR] = "USBINTR", + [FRINDEX] = "FRINDEX", + [PERIODICLISTBASE] = "P-LIST BASE", + [ASYNCLISTADDR] = "A-LIST ADDR", + [PORTSC_BEGIN] = "PORTSC #0", + [PORTSC_BEGIN + 4] = "PORTSC #1", + [PORTSC_BEGIN + 8] = "PORTSC #2", + [PORTSC_BEGIN + 12] = "PORTSC #3", + [PORTSC_BEGIN + 16] = "PORTSC #4", + [PORTSC_BEGIN + 20] = "PORTSC #5", + [CONFIGFLAG] = "CONFIGFLAG", +}; + +static const char *nr2str(const char **n, size_t len, uint32_t nr) +{ + if (nr < len && n[nr] != NULL) { + return n[nr]; + } else { + return "unknown"; + } +} + +static const char *state2str(uint32_t state) +{ + return nr2str(ehci_state_names, ARRAY_SIZE(ehci_state_names), state); +} + +static const char *addr2str(target_phys_addr_t addr) +{ + return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr); +} + +static void ehci_trace_usbsts(uint32_t mask, int state) +{ + /* interrupts */ + if (mask & USBSTS_INT) { + trace_usb_ehci_usbsts("INT", state); + } + if (mask & USBSTS_ERRINT) { + trace_usb_ehci_usbsts("ERRINT", state); + } + if (mask & USBSTS_PCD) { + trace_usb_ehci_usbsts("PCD", state); + } + if (mask & USBSTS_FLR) { + trace_usb_ehci_usbsts("FLR", state); + } + if (mask & USBSTS_HSE) { + trace_usb_ehci_usbsts("HSE", state); + } + if (mask & USBSTS_IAA) { + trace_usb_ehci_usbsts("IAA", state); + } + + /* status */ + if (mask & USBSTS_HALT) { + trace_usb_ehci_usbsts("HALT", state); + } + if (mask & USBSTS_REC) { + trace_usb_ehci_usbsts("REC", state); + } + if (mask & USBSTS_PSS) { + trace_usb_ehci_usbsts("PSS", state); + } + if (mask & USBSTS_ASS) { + trace_usb_ehci_usbsts("ASS", state); + } +} + +static inline void ehci_set_usbsts(EHCIState *s, int mask) +{ + if ((s->usbsts & mask) == mask) { + return; + } + ehci_trace_usbsts(mask, 1); + s->usbsts |= mask; +} + +static inline void ehci_clear_usbsts(EHCIState *s, int mask) +{ + if ((s->usbsts & mask) == 0) { + return; + } + ehci_trace_usbsts(mask, 0); + s->usbsts &= ~mask; +} + +static inline void ehci_set_interrupt(EHCIState *s, int intr) +{ + int level = 0; + + // TODO honour interrupt threshold requests + + ehci_set_usbsts(s, intr); + + if ((s->usbsts & USBINTR_MASK) & s->usbintr) { + level = 1; + } + + qemu_set_irq(s->irq, level); +} + +static inline void ehci_record_interrupt(EHCIState *s, int intr) +{ + s->usbsts_pending |= intr; +} + +static inline void ehci_commit_interrupt(EHCIState *s) +{ + if (!s->usbsts_pending) { + return; + } + ehci_set_interrupt(s, s->usbsts_pending); + s->usbsts_pending = 0; +} + +static void ehci_set_state(EHCIState *s, int async, int state) +{ + if (async) { + trace_usb_ehci_state("async", state2str(state)); + s->astate = state; + } else { + trace_usb_ehci_state("periodic", state2str(state)); + s->pstate = state; + } +} + +static int ehci_get_state(EHCIState *s, int async) +{ + return async ? s->astate : s->pstate; +} + +static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr) +{ + if (async) { + s->a_fetch_addr = addr; + } else { + s->p_fetch_addr = addr; + } +} + +static int ehci_get_fetch_addr(EHCIState *s, int async) +{ + return async ? s->a_fetch_addr : s->p_fetch_addr; +} + +static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh) +{ + /* need three here due to argument count limits */ + trace_usb_ehci_qh_ptrs(q, addr, qh->next, + qh->current_qtd, qh->next_qtd, qh->altnext_qtd); + trace_usb_ehci_qh_fields(addr, + get_field(qh->epchar, QH_EPCHAR_RL), + get_field(qh->epchar, QH_EPCHAR_MPLEN), + get_field(qh->epchar, QH_EPCHAR_EPS), + get_field(qh->epchar, QH_EPCHAR_EP), + get_field(qh->epchar, QH_EPCHAR_DEVADDR)); + trace_usb_ehci_qh_bits(addr, + (bool)(qh->epchar & QH_EPCHAR_C), + (bool)(qh->epchar & QH_EPCHAR_H), + (bool)(qh->epchar & QH_EPCHAR_DTC), + (bool)(qh->epchar & QH_EPCHAR_I)); +} + +static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd) +{ + /* need three here due to argument count limits */ + trace_usb_ehci_qtd_ptrs(q, addr, qtd->next, qtd->altnext); + trace_usb_ehci_qtd_fields(addr, + get_field(qtd->token, QTD_TOKEN_TBYTES), + get_field(qtd->token, QTD_TOKEN_CPAGE), + get_field(qtd->token, QTD_TOKEN_CERR), + get_field(qtd->token, QTD_TOKEN_PID)); + trace_usb_ehci_qtd_bits(addr, + (bool)(qtd->token & QTD_TOKEN_IOC), + (bool)(qtd->token & QTD_TOKEN_ACTIVE), + (bool)(qtd->token & QTD_TOKEN_HALT), + (bool)(qtd->token & QTD_TOKEN_BABBLE), + (bool)(qtd->token & QTD_TOKEN_XACTERR)); +} + +static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd) +{ + trace_usb_ehci_itd(addr, itd->next, + get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT), + get_field(itd->bufptr[2], ITD_BUFPTR_MULT), + get_field(itd->bufptr[0], ITD_BUFPTR_EP), + get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR)); +} + +static void ehci_trace_sitd(EHCIState *s, target_phys_addr_t addr, + EHCIsitd *sitd) +{ + trace_usb_ehci_sitd(addr, sitd->next, + (bool)(sitd->results & SITD_RESULTS_ACTIVE)); +} + +/* queue management */ + +static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, int async) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + EHCIQueue *q; + + q = g_malloc0(sizeof(*q)); + q->ehci = ehci; + QTAILQ_INSERT_HEAD(head, q, next); + trace_usb_ehci_queue_action(q, "alloc"); + return q; +} + +static void ehci_free_queue(EHCIQueue *q, int async) +{ + EHCIQueueHead *head = async ? &q->ehci->aqueues : &q->ehci->pqueues; + trace_usb_ehci_queue_action(q, "free"); + if (q->async == EHCI_ASYNC_INFLIGHT) { + usb_cancel_packet(&q->packet); + } + QTAILQ_REMOVE(head, q, next); + g_free(q); +} + +static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr, + int async) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + EHCIQueue *q; + + QTAILQ_FOREACH(q, head, next) { + if (addr == q->qhaddr) { + return q; + } + } + return NULL; +} + +static void ehci_queues_rip_unused(EHCIState *ehci, int async, int flush) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + EHCIQueue *q, *tmp; + + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { + if (q->seen) { + q->seen = 0; + q->ts = ehci->last_run_ns; + continue; + } + if (!flush && ehci->last_run_ns < q->ts + 250000000) { + /* allow 0.25 sec idle */ + continue; + } + ehci_free_queue(q, async); + } +} + +static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + EHCIQueue *q, *tmp; + + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { + if (!usb_packet_is_inflight(&q->packet) || + q->packet.ep->dev != dev) { + continue; + } + ehci_free_queue(q, async); + } +} + +static void ehci_queues_rip_all(EHCIState *ehci, int async) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + EHCIQueue *q, *tmp; + + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { + ehci_free_queue(q, async); + } +} + +/* Attach or detach a device on root hub */ + +static void ehci_attach(USBPort *port) +{ + EHCIState *s = port->opaque; + uint32_t *portsc = &s->portsc[port->index]; + + trace_usb_ehci_port_attach(port->index, port->dev->product_desc); + + if (*portsc & PORTSC_POWNER) { + USBPort *companion = s->companion_ports[port->index]; + companion->dev = port->dev; + companion->ops->attach(companion); + return; + } + + *portsc |= PORTSC_CONNECT; + *portsc |= PORTSC_CSC; + + ehci_set_interrupt(s, USBSTS_PCD); +} + +static void ehci_detach(USBPort *port) +{ + EHCIState *s = port->opaque; + uint32_t *portsc = &s->portsc[port->index]; + + trace_usb_ehci_port_detach(port->index); + + if (*portsc & PORTSC_POWNER) { + USBPort *companion = s->companion_ports[port->index]; + companion->ops->detach(companion); + companion->dev = NULL; + /* + * EHCI spec 4.2.2: "When a disconnect occurs... On the event, + * the port ownership is returned immediately to the EHCI controller." + */ + *portsc &= ~PORTSC_POWNER; + return; + } + + ehci_queues_rip_device(s, port->dev, 0); + ehci_queues_rip_device(s, port->dev, 1); + + *portsc &= ~(PORTSC_CONNECT|PORTSC_PED); + *portsc |= PORTSC_CSC; + + ehci_set_interrupt(s, USBSTS_PCD); +} + +static void ehci_child_detach(USBPort *port, USBDevice *child) +{ + EHCIState *s = port->opaque; + uint32_t portsc = s->portsc[port->index]; + + if (portsc & PORTSC_POWNER) { + USBPort *companion = s->companion_ports[port->index]; + companion->ops->child_detach(companion, child); + companion->dev = NULL; + return; + } + + ehci_queues_rip_device(s, child, 0); + ehci_queues_rip_device(s, child, 1); +} + +static void ehci_wakeup(USBPort *port) +{ + EHCIState *s = port->opaque; + uint32_t portsc = s->portsc[port->index]; + + if (portsc & PORTSC_POWNER) { + USBPort *companion = s->companion_ports[port->index]; + if (companion->ops->wakeup) { + companion->ops->wakeup(companion); + } + } +} + +static int ehci_register_companion(USBBus *bus, USBPort *ports[], + uint32_t portcount, uint32_t firstport) +{ + EHCIState *s = container_of(bus, EHCIState, bus); + uint32_t i; + + if (firstport + portcount > NB_PORTS) { + qerror_report(QERR_INVALID_PARAMETER_VALUE, "firstport", + "firstport on masterbus"); + error_printf_unless_qmp( + "firstport value of %u makes companion take ports %u - %u, which " + "is outside of the valid range of 0 - %u\n", firstport, firstport, + firstport + portcount - 1, NB_PORTS - 1); + return -1; + } + + for (i = 0; i < portcount; i++) { + if (s->companion_ports[firstport + i]) { + qerror_report(QERR_INVALID_PARAMETER_VALUE, "masterbus", + "an USB masterbus"); + error_printf_unless_qmp( + "port %u on masterbus %s already has a companion assigned\n", + firstport + i, bus->qbus.name); + return -1; + } + } + + for (i = 0; i < portcount; i++) { + s->companion_ports[firstport + i] = ports[i]; + s->ports[firstport + i].speedmask |= + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL; + /* Ensure devs attached before the initial reset go to the companion */ + s->portsc[firstport + i] = PORTSC_POWNER; + } + + s->companion_count++; + s->mmio[0x05] = (s->companion_count << 4) | portcount; + + return 0; +} + +static USBDevice *ehci_find_device(EHCIState *ehci, uint8_t addr) +{ + USBDevice *dev; + USBPort *port; + int i; + + for (i = 0; i < NB_PORTS; i++) { + port = &ehci->ports[i]; + if (!(ehci->portsc[i] & PORTSC_PED)) { + DPRINTF("Port %d not enabled\n", i); + continue; + } + dev = usb_find_device(port, addr); + if (dev != NULL) { + return dev; + } + } + return NULL; +} + +/* 4.1 host controller initialization */ +static void ehci_reset(void *opaque) +{ + EHCIState *s = opaque; + int i; + USBDevice *devs[NB_PORTS]; + + trace_usb_ehci_reset(); + + /* + * Do the detach before touching portsc, so that it correctly gets send to + * us or to our companion based on PORTSC_POWNER before the reset. + */ + for(i = 0; i < NB_PORTS; i++) { + devs[i] = s->ports[i].dev; + if (devs[i] && devs[i]->attached) { + usb_detach(&s->ports[i]); + } + } + + memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE); + + s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH; + s->usbsts = USBSTS_HALT; + + s->astate = EST_INACTIVE; + s->pstate = EST_INACTIVE; + s->isoch_pause = -1; + s->attach_poll_counter = 0; + + for(i = 0; i < NB_PORTS; i++) { + if (s->companion_ports[i]) { + s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER; + } else { + s->portsc[i] = PORTSC_PPOWER; + } + if (devs[i] && devs[i]->attached) { + usb_attach(&s->ports[i]); + usb_device_reset(devs[i]); + } + } + ehci_queues_rip_all(s, 0); + ehci_queues_rip_all(s, 1); + qemu_del_timer(s->frame_timer); +} + +static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr) +{ + EHCIState *s = ptr; + uint32_t val; + + val = s->mmio[addr]; + + return val; +} + +static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr) +{ + EHCIState *s = ptr; + uint32_t val; + + val = s->mmio[addr] | (s->mmio[addr+1] << 8); + + return val; +} + +static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr) +{ + EHCIState *s = ptr; + uint32_t val; + + val = s->mmio[addr] | (s->mmio[addr+1] << 8) | + (s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24); + + trace_usb_ehci_mmio_readl(addr, addr2str(addr), val); + return val; +} + +static void ehci_mem_writeb(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + fprintf(stderr, "EHCI doesn't handle byte writes to MMIO\n"); + exit(1); +} + +static void ehci_mem_writew(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + fprintf(stderr, "EHCI doesn't handle 16-bit writes to MMIO\n"); + exit(1); +} + +static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner) +{ + USBDevice *dev = s->ports[port].dev; + uint32_t *portsc = &s->portsc[port]; + uint32_t orig; + + if (s->companion_ports[port] == NULL) + return; + + owner = owner & PORTSC_POWNER; + orig = *portsc & PORTSC_POWNER; + + if (!(owner ^ orig)) { + return; + } + + if (dev && dev->attached) { + usb_detach(&s->ports[port]); + } + + *portsc &= ~PORTSC_POWNER; + *portsc |= owner; + + if (dev && dev->attached) { + usb_attach(&s->ports[port]); + } +} + +static void handle_port_status_write(EHCIState *s, int port, uint32_t val) +{ + uint32_t *portsc = &s->portsc[port]; + USBDevice *dev = s->ports[port].dev; + + /* Clear rwc bits */ + *portsc &= ~(val & PORTSC_RWC_MASK); + /* The guest may clear, but not set the PED bit */ + *portsc &= val | ~PORTSC_PED; + /* POWNER is masked out by RO_MASK as it is RO when we've no companion */ + handle_port_owner_write(s, port, val); + /* And finally apply RO_MASK */ + val &= PORTSC_RO_MASK; + + if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) { + trace_usb_ehci_port_reset(port, 1); + } + + if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) { + trace_usb_ehci_port_reset(port, 0); + if (dev && dev->attached) { + usb_port_reset(&s->ports[port]); + *portsc &= ~PORTSC_CSC; + } + + /* + * Table 2.16 Set the enable bit(and enable bit change) to indicate + * to SW that this port has a high speed device attached + */ + if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) { + val |= PORTSC_PED; + } + } + + *portsc &= ~PORTSC_RO_MASK; + *portsc |= val; +} + +static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + EHCIState *s = ptr; + uint32_t *mmio = (uint32_t *)(&s->mmio[addr]); + uint32_t old = *mmio; + int i; + + trace_usb_ehci_mmio_writel(addr, addr2str(addr), val); + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ehci: Mis-aligned write to addr 0x" + TARGET_FMT_plx "\n", addr); + return; + } + + if (addr >= PORTSC && addr < PORTSC + 4 * NB_PORTS) { + handle_port_status_write(s, (addr-PORTSC)/4, val); + trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old); + return; + } + + if (addr < OPREGBASE) { + fprintf(stderr, "usb-ehci: write attempt to read-only register" + TARGET_FMT_plx "\n", addr); + return; + } + + + /* Do any register specific pre-write processing here. */ + switch(addr) { + case USBCMD: + if ((val & USBCMD_RUNSTOP) && !(s->usbcmd & USBCMD_RUNSTOP)) { + qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock)); + SET_LAST_RUN_CLOCK(s); + ehci_clear_usbsts(s, USBSTS_HALT); + } + + if (!(val & USBCMD_RUNSTOP) && (s->usbcmd & USBCMD_RUNSTOP)) { + qemu_del_timer(s->frame_timer); + ehci_queues_rip_all(s, 0); + ehci_queues_rip_all(s, 1); + ehci_set_usbsts(s, USBSTS_HALT); + } + + if (val & USBCMD_HCRESET) { + ehci_reset(s); + val = s->usbcmd; + } + + /* not supporting dynamic frame list size at the moment */ + if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) { + fprintf(stderr, "attempt to set frame list size -- value %d\n", + val & USBCMD_FLS); + val &= ~USBCMD_FLS; + } + break; + + case USBSTS: + val &= USBSTS_RO_MASK; // bits 6 thru 31 are RO + ehci_clear_usbsts(s, val); // bits 0 thru 5 are R/WC + val = s->usbsts; + ehci_set_interrupt(s, 0); + break; + + case USBINTR: + val &= USBINTR_MASK; + break; + + case FRINDEX: + s->sofv = val >> 3; + break; + + case CONFIGFLAG: + val &= 0x1; + if (val) { + for(i = 0; i < NB_PORTS; i++) + handle_port_owner_write(s, i, 0); + } + break; + + case PERIODICLISTBASE: + if ((s->usbcmd & USBCMD_PSE) && (s->usbcmd & USBCMD_RUNSTOP)) { + fprintf(stderr, + "ehci: PERIODIC list base register set while periodic schedule\n" + " is enabled and HC is enabled\n"); + } + break; + + case ASYNCLISTADDR: + if ((s->usbcmd & USBCMD_ASE) && (s->usbcmd & USBCMD_RUNSTOP)) { + fprintf(stderr, + "ehci: ASYNC list address register set while async schedule\n" + " is enabled and HC is enabled\n"); + } + break; + } + + *mmio = val; + trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old); +} + + +// TODO : Put in common header file, duplication from usb-ohci.c + +/* Get an array of dwords from main memory */ +static inline int get_dwords(EHCIState *ehci, uint32_t addr, + uint32_t *buf, int num) +{ + int i; + + for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + pci_dma_read(&ehci->dev, addr, buf, sizeof(*buf)); + *buf = le32_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of dwords in to main memory */ +static inline int put_dwords(EHCIState *ehci, uint32_t addr, + uint32_t *buf, int num) +{ + int i; + + for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + uint32_t tmp = cpu_to_le32(*buf); + pci_dma_write(&ehci->dev, addr, &tmp, sizeof(tmp)); + } + + return 1; +} + +// 4.10.2 + +static int ehci_qh_do_overlay(EHCIQueue *q) +{ + int i; + int dtoggle; + int ping; + int eps; + int reload; + + // remember values in fields to preserve in qh after overlay + + dtoggle = q->qh.token & QTD_TOKEN_DTOGGLE; + ping = q->qh.token & QTD_TOKEN_PING; + + q->qh.current_qtd = q->qtdaddr; + q->qh.next_qtd = q->qtd.next; + q->qh.altnext_qtd = q->qtd.altnext; + q->qh.token = q->qtd.token; + + + eps = get_field(q->qh.epchar, QH_EPCHAR_EPS); + if (eps == EHCI_QH_EPS_HIGH) { + q->qh.token &= ~QTD_TOKEN_PING; + q->qh.token |= ping; + } + + reload = get_field(q->qh.epchar, QH_EPCHAR_RL); + set_field(&q->qh.altnext_qtd, reload, QH_ALTNEXT_NAKCNT); + + for (i = 0; i < 5; i++) { + q->qh.bufptr[i] = q->qtd.bufptr[i]; + } + + if (!(q->qh.epchar & QH_EPCHAR_DTC)) { + // preserve QH DT bit + q->qh.token &= ~QTD_TOKEN_DTOGGLE; + q->qh.token |= dtoggle; + } + + q->qh.bufptr[1] &= ~BUFPTR_CPROGMASK_MASK; + q->qh.bufptr[2] &= ~BUFPTR_FRAMETAG_MASK; + + put_dwords(q->ehci, NLPTR_GET(q->qhaddr), (uint32_t *) &q->qh, + sizeof(EHCIqh) >> 2); + + return 0; +} + +static int ehci_init_transfer(EHCIQueue *q) +{ + uint32_t cpage, offset, bytes, plen; + dma_addr_t page; + + cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE); + bytes = get_field(q->qh.token, QTD_TOKEN_TBYTES); + offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK; + pci_dma_sglist_init(&q->sgl, &q->ehci->dev, 5); + + while (bytes > 0) { + if (cpage > 4) { + fprintf(stderr, "cpage out of range (%d)\n", cpage); + return USB_RET_PROCERR; + } + + page = q->qh.bufptr[cpage] & QTD_BUFPTR_MASK; + page += offset; + plen = bytes; + if (plen > 4096 - offset) { + plen = 4096 - offset; + offset = 0; + cpage++; + } + + qemu_sglist_add(&q->sgl, page, plen); + bytes -= plen; + } + return 0; +} + +static void ehci_finish_transfer(EHCIQueue *q, int status) +{ + uint32_t cpage, offset; + + qemu_sglist_destroy(&q->sgl); + + if (status > 0) { + /* update cpage & offset */ + cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE); + offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK; + + offset += status; + cpage += offset >> QTD_BUFPTR_SH; + offset &= ~QTD_BUFPTR_MASK; + + set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE); + q->qh.bufptr[0] &= QTD_BUFPTR_MASK; + q->qh.bufptr[0] |= offset; + } +} + +static void ehci_async_complete_packet(USBPort *port, USBPacket *packet) +{ + EHCIQueue *q; + EHCIState *s = port->opaque; + uint32_t portsc = s->portsc[port->index]; + + if (portsc & PORTSC_POWNER) { + USBPort *companion = s->companion_ports[port->index]; + companion->ops->complete(companion, packet); + return; + } + + q = container_of(packet, EHCIQueue, packet); + trace_usb_ehci_queue_action(q, "wakeup"); + assert(q->async == EHCI_ASYNC_INFLIGHT); + q->async = EHCI_ASYNC_FINISHED; + q->usb_status = packet->result; +} + +static void ehci_execute_complete(EHCIQueue *q) +{ + assert(q->async != EHCI_ASYNC_INFLIGHT); + q->async = EHCI_ASYNC_NONE; + + DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n", + q->qhaddr, q->qh.next, q->qtdaddr, q->usb_status); + + if (q->usb_status < 0) { + switch(q->usb_status) { + case USB_RET_IOERROR: + case USB_RET_NODEV: + q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR); + set_field(&q->qh.token, 0, QTD_TOKEN_CERR); + ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + break; + case USB_RET_STALL: + q->qh.token |= QTD_TOKEN_HALT; + ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + break; + case USB_RET_NAK: + set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT); + return; /* We're not done yet with this transaction */ + case USB_RET_BABBLE: + q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); + ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + break; + default: + /* should not be triggerable */ + fprintf(stderr, "USB invalid response %d to handle\n", q->usb_status); + assert(0); + break; + } + } else if ((q->usb_status > q->tbytes) && (q->pid == USB_TOKEN_IN)) { + q->usb_status = USB_RET_BABBLE; + q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE); + ehci_record_interrupt(q->ehci, USBSTS_ERRINT); + } else { + // TODO check 4.12 for splits + + if (q->tbytes && q->pid == USB_TOKEN_IN) { + q->tbytes -= q->usb_status; + } else { + q->tbytes = 0; + } + + DPRINTF("updating tbytes to %d\n", q->tbytes); + set_field(&q->qh.token, q->tbytes, QTD_TOKEN_TBYTES); + } + ehci_finish_transfer(q, q->usb_status); + usb_packet_unmap(&q->packet); + + q->qh.token ^= QTD_TOKEN_DTOGGLE; + q->qh.token &= ~QTD_TOKEN_ACTIVE; + + if (q->qh.token & QTD_TOKEN_IOC) { + ehci_record_interrupt(q->ehci, USBSTS_INT); + } +} + +// 4.10.3 + +static int ehci_execute(EHCIQueue *q) +{ + USBDevice *dev; + USBEndpoint *ep; + int ret; + int endp; + int devadr; + + if ( !(q->qh.token & QTD_TOKEN_ACTIVE)) { + fprintf(stderr, "Attempting to execute inactive QH\n"); + return USB_RET_PROCERR; + } + + q->tbytes = (q->qh.token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH; + if (q->tbytes > BUFF_SIZE) { + fprintf(stderr, "Request for more bytes than allowed\n"); + return USB_RET_PROCERR; + } + + q->pid = (q->qh.token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH; + switch(q->pid) { + case 0: q->pid = USB_TOKEN_OUT; break; + case 1: q->pid = USB_TOKEN_IN; break; + case 2: q->pid = USB_TOKEN_SETUP; break; + default: fprintf(stderr, "bad token\n"); break; + } + + if (ehci_init_transfer(q) != 0) { + return USB_RET_PROCERR; + } + + endp = get_field(q->qh.epchar, QH_EPCHAR_EP); + devadr = get_field(q->qh.epchar, QH_EPCHAR_DEVADDR); + + /* TODO: associating device with ehci port */ + dev = ehci_find_device(q->ehci, devadr); + ep = usb_ep_get(dev, q->pid, endp); + + usb_packet_setup(&q->packet, q->pid, ep); + usb_packet_map(&q->packet, &q->sgl); + + ret = usb_handle_packet(dev, &q->packet); + DPRINTF("submit: qh %x next %x qtd %x pid %x len %zd " + "(total %d) endp %x ret %d\n", + q->qhaddr, q->qh.next, q->qtdaddr, q->pid, + q->packet.iov.size, q->tbytes, endp, ret); + + if (ret > BUFF_SIZE) { + fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n"); + return USB_RET_PROCERR; + } + + return ret; +} + +/* 4.7.2 + */ + +static int ehci_process_itd(EHCIState *ehci, + EHCIitd *itd) +{ + USBDevice *dev; + USBEndpoint *ep; + int ret; + uint32_t i, len, pid, dir, devaddr, endp; + uint32_t pg, off, ptr1, ptr2, max, mult; + + dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION); + devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR); + endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP); + max = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT); + mult = get_field(itd->bufptr[2], ITD_BUFPTR_MULT); + + for(i = 0; i < 8; i++) { + if (itd->transact[i] & ITD_XACT_ACTIVE) { + pg = get_field(itd->transact[i], ITD_XACT_PGSEL); + off = itd->transact[i] & ITD_XACT_OFFSET_MASK; + ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK); + ptr2 = (itd->bufptr[pg+1] & ITD_BUFPTR_MASK); + len = get_field(itd->transact[i], ITD_XACT_LENGTH); + + if (len > max * mult) { + len = max * mult; + } + + if (len > BUFF_SIZE) { + return USB_RET_PROCERR; + } + + pci_dma_sglist_init(&ehci->isgl, &ehci->dev, 2); + if (off + len > 4096) { + /* transfer crosses page border */ + uint32_t len2 = off + len - 4096; + uint32_t len1 = len - len2; + qemu_sglist_add(&ehci->isgl, ptr1 + off, len1); + qemu_sglist_add(&ehci->isgl, ptr2, len2); + } else { + qemu_sglist_add(&ehci->isgl, ptr1 + off, len); + } + + pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT; + + dev = ehci_find_device(ehci, devaddr); + ep = usb_ep_get(dev, pid, endp); + if (ep->type == USB_ENDPOINT_XFER_ISOC) { + usb_packet_setup(&ehci->ipacket, pid, ep); + usb_packet_map(&ehci->ipacket, &ehci->isgl); + ret = usb_handle_packet(dev, &ehci->ipacket); + assert(ret != USB_RET_ASYNC); + usb_packet_unmap(&ehci->ipacket); + } else { + DPRINTF("ISOCH: attempt to addess non-iso endpoint\n"); + ret = USB_RET_NAK; + } + qemu_sglist_destroy(&ehci->isgl); + + if (ret < 0) { + switch (ret) { + default: + fprintf(stderr, "Unexpected iso usb result: %d\n", ret); + /* Fall through */ + case USB_RET_IOERROR: + case USB_RET_NODEV: + /* 3.3.2: XACTERR is only allowed on IN transactions */ + if (dir) { + itd->transact[i] |= ITD_XACT_XACTERR; + ehci_record_interrupt(ehci, USBSTS_ERRINT); + } + break; + case USB_RET_BABBLE: + itd->transact[i] |= ITD_XACT_BABBLE; + ehci_record_interrupt(ehci, USBSTS_ERRINT); + break; + case USB_RET_NAK: + /* no data for us, so do a zero-length transfer */ + ret = 0; + break; + } + } + if (ret >= 0) { + if (!dir) { + /* OUT */ + set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH); + } else { + /* IN */ + set_field(&itd->transact[i], ret, ITD_XACT_LENGTH); + } + } + if (itd->transact[i] & ITD_XACT_IOC) { + ehci_record_interrupt(ehci, USBSTS_INT); + } + itd->transact[i] &= ~ITD_XACT_ACTIVE; + } + } + return 0; +} + +/* This state is the entry point for asynchronous schedule + * processing. Entry here consitutes a EHCI start event state (4.8.5) + */ +static int ehci_state_waitlisthead(EHCIState *ehci, int async) +{ + EHCIqh qh; + int i = 0; + int again = 0; + uint32_t entry = ehci->asynclistaddr; + + /* set reclamation flag at start event (4.8.6) */ + if (async) { + ehci_set_usbsts(ehci, USBSTS_REC); + } + + ehci_queues_rip_unused(ehci, async, 0); + + /* Find the head of the list (4.9.1.1) */ + for(i = 0; i < MAX_QH; i++) { + get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh, + sizeof(EHCIqh) >> 2); + ehci_trace_qh(NULL, NLPTR_GET(entry), &qh); + + if (qh.epchar & QH_EPCHAR_H) { + if (async) { + entry |= (NLPTR_TYPE_QH << 1); + } + + ehci_set_fetch_addr(ehci, async, entry); + ehci_set_state(ehci, async, EST_FETCHENTRY); + again = 1; + goto out; + } + + entry = qh.next; + if (entry == ehci->asynclistaddr) { + break; + } + } + + /* no head found for list. */ + + ehci_set_state(ehci, async, EST_ACTIVE); + +out: + return again; +} + + +/* This state is the entry point for periodic schedule processing as + * well as being a continuation state for async processing. + */ +static int ehci_state_fetchentry(EHCIState *ehci, int async) +{ + int again = 0; + uint32_t entry = ehci_get_fetch_addr(ehci, async); + + if (NLPTR_TBIT(entry)) { + ehci_set_state(ehci, async, EST_ACTIVE); + goto out; + } + + /* section 4.8, only QH in async schedule */ + if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) { + fprintf(stderr, "non queue head request in async schedule\n"); + return -1; + } + + switch (NLPTR_TYPE_GET(entry)) { + case NLPTR_TYPE_QH: + ehci_set_state(ehci, async, EST_FETCHQH); + again = 1; + break; + + case NLPTR_TYPE_ITD: + ehci_set_state(ehci, async, EST_FETCHITD); + again = 1; + break; + + case NLPTR_TYPE_STITD: + ehci_set_state(ehci, async, EST_FETCHSITD); + again = 1; + break; + + default: + /* TODO: handle FSTN type */ + fprintf(stderr, "FETCHENTRY: entry at %X is of type %d " + "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry)); + return -1; + } + +out: + return again; +} + +static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) +{ + uint32_t entry; + EHCIQueue *q; + + entry = ehci_get_fetch_addr(ehci, async); + q = ehci_find_queue_by_qh(ehci, entry, async); + if (NULL == q) { + q = ehci_alloc_queue(ehci, async); + } + q->qhaddr = entry; + q->seen++; + + if (q->seen > 1) { + /* we are going in circles -- stop processing */ + ehci_set_state(ehci, async, EST_ACTIVE); + q = NULL; + goto out; + } + + get_dwords(ehci, NLPTR_GET(q->qhaddr), + (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2); + ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh); + + if (q->async == EHCI_ASYNC_INFLIGHT) { + /* I/O still in progress -- skip queue */ + ehci_set_state(ehci, async, EST_HORIZONTALQH); + goto out; + } + if (q->async == EHCI_ASYNC_FINISHED) { + /* I/O finished -- continue processing queue */ + trace_usb_ehci_queue_action(q, "resume"); + ehci_set_state(ehci, async, EST_EXECUTING); + goto out; + } + + if (async && (q->qh.epchar & QH_EPCHAR_H)) { + + /* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */ + if (ehci->usbsts & USBSTS_REC) { + ehci_clear_usbsts(ehci, USBSTS_REC); + } else { + DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset" + " - done processing\n", q->qhaddr); + ehci_set_state(ehci, async, EST_ACTIVE); + q = NULL; + goto out; + } + } + +#if EHCI_DEBUG + if (q->qhaddr != q->qh.next) { + DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n", + q->qhaddr, + q->qh.epchar & QH_EPCHAR_H, + q->qh.token & QTD_TOKEN_HALT, + q->qh.token & QTD_TOKEN_ACTIVE, + q->qh.next); + } +#endif + + if (q->qh.token & QTD_TOKEN_HALT) { + ehci_set_state(ehci, async, EST_HORIZONTALQH); + + } else if ((q->qh.token & QTD_TOKEN_ACTIVE) && + (NLPTR_TBIT(q->qh.current_qtd) == 0)) { + q->qtdaddr = q->qh.current_qtd; + ehci_set_state(ehci, async, EST_FETCHQTD); + + } else { + /* EHCI spec version 1.0 Section 4.10.2 */ + ehci_set_state(ehci, async, EST_ADVANCEQUEUE); + } + +out: + return q; +} + +static int ehci_state_fetchitd(EHCIState *ehci, int async) +{ + uint32_t entry; + EHCIitd itd; + + assert(!async); + entry = ehci_get_fetch_addr(ehci, async); + + get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, + sizeof(EHCIitd) >> 2); + ehci_trace_itd(ehci, entry, &itd); + + if (ehci_process_itd(ehci, &itd) != 0) { + return -1; + } + + put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, + sizeof(EHCIitd) >> 2); + ehci_set_fetch_addr(ehci, async, itd.next); + ehci_set_state(ehci, async, EST_FETCHENTRY); + + return 1; +} + +static int ehci_state_fetchsitd(EHCIState *ehci, int async) +{ + uint32_t entry; + EHCIsitd sitd; + + assert(!async); + entry = ehci_get_fetch_addr(ehci, async); + + get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd, + sizeof(EHCIsitd) >> 2); + ehci_trace_sitd(ehci, entry, &sitd); + + if (!(sitd.results & SITD_RESULTS_ACTIVE)) { + /* siTD is not active, nothing to do */; + } else { + /* TODO: split transfers are not implemented */ + fprintf(stderr, "WARNING: Skipping active siTD\n"); + } + + ehci_set_fetch_addr(ehci, async, sitd.next); + ehci_set_state(ehci, async, EST_FETCHENTRY); + return 1; +} + +/* Section 4.10.2 - paragraph 3 */ +static int ehci_state_advqueue(EHCIQueue *q, int async) +{ +#if 0 + /* TO-DO: 4.10.2 - paragraph 2 + * if I-bit is set to 1 and QH is not active + * go to horizontal QH + */ + if (I-bit set) { + ehci_set_state(ehci, async, EST_HORIZONTALQH); + goto out; + } +#endif + + /* + * want data and alt-next qTD is valid + */ + if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) && + (NLPTR_TBIT(q->qh.altnext_qtd) == 0)) { + q->qtdaddr = q->qh.altnext_qtd; + ehci_set_state(q->ehci, async, EST_FETCHQTD); + + /* + * next qTD is valid + */ + } else if (NLPTR_TBIT(q->qh.next_qtd) == 0) { + q->qtdaddr = q->qh.next_qtd; + ehci_set_state(q->ehci, async, EST_FETCHQTD); + + /* + * no valid qTD, try next QH + */ + } else { + ehci_set_state(q->ehci, async, EST_HORIZONTALQH); + } + + return 1; +} + +/* Section 4.10.2 - paragraph 4 */ +static int ehci_state_fetchqtd(EHCIQueue *q, int async) +{ + int again = 0; + + get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &q->qtd, + sizeof(EHCIqtd) >> 2); + ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &q->qtd); + + if (q->qtd.token & QTD_TOKEN_ACTIVE) { + ehci_set_state(q->ehci, async, EST_EXECUTE); + again = 1; + } else { + ehci_set_state(q->ehci, async, EST_HORIZONTALQH); + again = 1; + } + + return again; +} + +static int ehci_state_horizqh(EHCIQueue *q, int async) +{ + int again = 0; + + if (ehci_get_fetch_addr(q->ehci, async) != q->qh.next) { + ehci_set_fetch_addr(q->ehci, async, q->qh.next); + ehci_set_state(q->ehci, async, EST_FETCHENTRY); + again = 1; + } else { + ehci_set_state(q->ehci, async, EST_ACTIVE); + } + + return again; +} + +/* + * Write the qh back to guest physical memory. This step isn't + * in the EHCI spec but we need to do it since we don't share + * physical memory with our guest VM. + * + * The first three dwords are read-only for the EHCI, so skip them + * when writing back the qh. + */ +static void ehci_flush_qh(EHCIQueue *q) +{ + uint32_t *qh = (uint32_t *) &q->qh; + uint32_t dwords = sizeof(EHCIqh) >> 2; + uint32_t addr = NLPTR_GET(q->qhaddr); + + put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3); +} + +static int ehci_state_execute(EHCIQueue *q, int async) +{ + int again = 0; + + if (ehci_qh_do_overlay(q) != 0) { + return -1; + } + + // TODO verify enough time remains in the uframe as in 4.4.1.1 + // TODO write back ptr to async list when done or out of time + // TODO Windows does not seem to ever set the MULT field + + if (!async) { + int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT); + if (!transactCtr) { + ehci_set_state(q->ehci, async, EST_HORIZONTALQH); + again = 1; + goto out; + } + } + + if (async) { + ehci_set_usbsts(q->ehci, USBSTS_REC); + } + + q->usb_status = ehci_execute(q); + if (q->usb_status == USB_RET_PROCERR) { + again = -1; + goto out; + } + if (q->usb_status == USB_RET_ASYNC) { + ehci_flush_qh(q); + trace_usb_ehci_queue_action(q, "suspend"); + q->async = EHCI_ASYNC_INFLIGHT; + ehci_set_state(q->ehci, async, EST_HORIZONTALQH); + again = 1; + goto out; + } + + ehci_set_state(q->ehci, async, EST_EXECUTING); + again = 1; + +out: + return again; +} + +static int ehci_state_executing(EHCIQueue *q, int async) +{ + int again = 0; + + ehci_execute_complete(q); + if (q->usb_status == USB_RET_ASYNC) { + goto out; + } + if (q->usb_status == USB_RET_PROCERR) { + again = -1; + goto out; + } + + // 4.10.3 + if (!async) { + int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT); + transactCtr--; + set_field(&q->qh.epcap, transactCtr, QH_EPCAP_MULT); + // 4.10.3, bottom of page 82, should exit this state when transaction + // counter decrements to 0 + } + + /* 4.10.5 */ + if (q->usb_status == USB_RET_NAK) { + ehci_set_state(q->ehci, async, EST_HORIZONTALQH); + } else { + ehci_set_state(q->ehci, async, EST_WRITEBACK); + } + + again = 1; + +out: + ehci_flush_qh(q); + return again; +} + + +static int ehci_state_writeback(EHCIQueue *q, int async) +{ + int again = 0; + + /* Write back the QTD from the QH area */ + ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), (EHCIqtd*) &q->qh.next_qtd); + put_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &q->qh.next_qtd, + sizeof(EHCIqtd) >> 2); + + /* + * EHCI specs say go horizontal here. + * + * We can also advance the queue here for performance reasons. We + * need to take care to only take that shortcut in case we've + * processed the qtd just written back without errors, i.e. halt + * bit is clear. + */ + if (q->qh.token & QTD_TOKEN_HALT) { + ehci_set_state(q->ehci, async, EST_HORIZONTALQH); + again = 1; + } else { + ehci_set_state(q->ehci, async, EST_ADVANCEQUEUE); + again = 1; + } + return again; +} + +/* + * This is the state machine that is common to both async and periodic + */ + +static void ehci_advance_state(EHCIState *ehci, + int async) +{ + EHCIQueue *q = NULL; + int again; + int iter = 0; + + do { + if (ehci_get_state(ehci, async) == EST_FETCHQH) { + iter++; + /* if we are roaming a lot of QH without executing a qTD + * something is wrong with the linked list. TO-DO: why is + * this hack needed? + */ + assert(iter < MAX_ITERATIONS); +#if 0 + if (iter > MAX_ITERATIONS) { + DPRINTF("\n*** advance_state: bailing on MAX ITERATIONS***\n"); + ehci_set_state(ehci, async, EST_ACTIVE); + break; + } +#endif + } + switch(ehci_get_state(ehci, async)) { + case EST_WAITLISTHEAD: + again = ehci_state_waitlisthead(ehci, async); + break; + + case EST_FETCHENTRY: + again = ehci_state_fetchentry(ehci, async); + break; + + case EST_FETCHQH: + q = ehci_state_fetchqh(ehci, async); + again = q ? 1 : 0; + break; + + case EST_FETCHITD: + again = ehci_state_fetchitd(ehci, async); + break; + + case EST_FETCHSITD: + again = ehci_state_fetchsitd(ehci, async); + break; + + case EST_ADVANCEQUEUE: + again = ehci_state_advqueue(q, async); + break; + + case EST_FETCHQTD: + again = ehci_state_fetchqtd(q, async); + break; + + case EST_HORIZONTALQH: + again = ehci_state_horizqh(q, async); + break; + + case EST_EXECUTE: + iter = 0; + again = ehci_state_execute(q, async); + break; + + case EST_EXECUTING: + assert(q != NULL); + again = ehci_state_executing(q, async); + break; + + case EST_WRITEBACK: + assert(q != NULL); + again = ehci_state_writeback(q, async); + break; + + default: + fprintf(stderr, "Bad state!\n"); + again = -1; + assert(0); + break; + } + + if (again < 0) { + fprintf(stderr, "processing error - resetting ehci HC\n"); + ehci_reset(ehci); + again = 0; + assert(0); + } + } + while (again); + + ehci_commit_interrupt(ehci); +} + +static void ehci_advance_async_state(EHCIState *ehci) +{ + const int async = 1; + + switch(ehci_get_state(ehci, async)) { + case EST_INACTIVE: + if (!(ehci->usbcmd & USBCMD_ASE)) { + break; + } + ehci_set_usbsts(ehci, USBSTS_ASS); + ehci_set_state(ehci, async, EST_ACTIVE); + // No break, fall through to ACTIVE + + case EST_ACTIVE: + if ( !(ehci->usbcmd & USBCMD_ASE)) { + ehci_queues_rip_all(ehci, async); + ehci_clear_usbsts(ehci, USBSTS_ASS); + ehci_set_state(ehci, async, EST_INACTIVE); + break; + } + + /* make sure guest has acknowledged the doorbell interrupt */ + /* TO-DO: is this really needed? */ + if (ehci->usbsts & USBSTS_IAA) { + DPRINTF("IAA status bit still set.\n"); + break; + } + + /* check that address register has been set */ + if (ehci->asynclistaddr == 0) { + break; + } + + ehci_set_state(ehci, async, EST_WAITLISTHEAD); + ehci_advance_state(ehci, async); + + /* If the doorbell is set, the guest wants to make a change to the + * schedule. The host controller needs to release cached data. + * (section 4.8.2) + */ + if (ehci->usbcmd & USBCMD_IAAD) { + /* Remove all unseen qhs from the async qhs queue */ + ehci_queues_rip_unused(ehci, async, 1); + DPRINTF("ASYNC: doorbell request acknowledged\n"); + ehci->usbcmd &= ~USBCMD_IAAD; + ehci_set_interrupt(ehci, USBSTS_IAA); + } + break; + + default: + /* this should only be due to a developer mistake */ + fprintf(stderr, "ehci: Bad asynchronous state %d. " + "Resetting to active\n", ehci->astate); + assert(0); + } +} + +static void ehci_advance_periodic_state(EHCIState *ehci) +{ + uint32_t entry; + uint32_t list; + const int async = 0; + + // 4.6 + + switch(ehci_get_state(ehci, async)) { + case EST_INACTIVE: + if ( !(ehci->frindex & 7) && (ehci->usbcmd & USBCMD_PSE)) { + ehci_set_usbsts(ehci, USBSTS_PSS); + ehci_set_state(ehci, async, EST_ACTIVE); + // No break, fall through to ACTIVE + } else + break; + + case EST_ACTIVE: + if ( !(ehci->frindex & 7) && !(ehci->usbcmd & USBCMD_PSE)) { + ehci_queues_rip_all(ehci, async); + ehci_clear_usbsts(ehci, USBSTS_PSS); + ehci_set_state(ehci, async, EST_INACTIVE); + break; + } + + list = ehci->periodiclistbase & 0xfffff000; + /* check that register has been set */ + if (list == 0) { + break; + } + list |= ((ehci->frindex & 0x1ff8) >> 1); + + pci_dma_read(&ehci->dev, list, &entry, sizeof entry); + entry = le32_to_cpu(entry); + + DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n", + ehci->frindex / 8, list, entry); + ehci_set_fetch_addr(ehci, async,entry); + ehci_set_state(ehci, async, EST_FETCHENTRY); + ehci_advance_state(ehci, async); + ehci_queues_rip_unused(ehci, async, 0); + break; + + default: + /* this should only be due to a developer mistake */ + fprintf(stderr, "ehci: Bad periodic state %d. " + "Resetting to active\n", ehci->pstate); + assert(0); + } +} + +static void ehci_frame_timer(void *opaque) +{ + EHCIState *ehci = opaque; + int64_t expire_time, t_now; + uint64_t ns_elapsed; + int frames; + int i; + int skipped_frames = 0; + + t_now = qemu_get_clock_ns(vm_clock); + expire_time = t_now + (get_ticks_per_sec() / ehci->freq); + + ns_elapsed = t_now - ehci->last_run_ns; + frames = ns_elapsed / FRAME_TIMER_NS; + + for (i = 0; i < frames; i++) { + if ( !(ehci->usbsts & USBSTS_HALT)) { + if (ehci->isoch_pause <= 0) { + ehci->frindex += 8; + } + + if (ehci->frindex > 0x00001fff) { + ehci->frindex = 0; + ehci_set_interrupt(ehci, USBSTS_FLR); + } + + ehci->sofv = (ehci->frindex - 1) >> 3; + ehci->sofv &= 0x000003ff; + } + + if (frames - i > ehci->maxframes) { + skipped_frames++; + } else { + ehci_advance_periodic_state(ehci); + } + + ehci->last_run_ns += FRAME_TIMER_NS; + } + +#if 0 + if (skipped_frames) { + DPRINTF("WARNING - EHCI skipped %d frames\n", skipped_frames); + } +#endif + + /* Async is not inside loop since it executes everything it can once + * called + */ + ehci_advance_async_state(ehci); + + qemu_mod_timer(ehci->frame_timer, expire_time); +} + + +static const MemoryRegionOps ehci_mem_ops = { + .old_mmio = { + .read = { ehci_mem_readb, ehci_mem_readw, ehci_mem_readl }, + .write = { ehci_mem_writeb, ehci_mem_writew, ehci_mem_writel }, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int usb_ehci_initfn(PCIDevice *dev); + +static USBPortOps ehci_port_ops = { + .attach = ehci_attach, + .detach = ehci_detach, + .child_detach = ehci_child_detach, + .wakeup = ehci_wakeup, + .complete = ehci_async_complete_packet, +}; + +static USBBusOps ehci_bus_ops = { + .register_companion = ehci_register_companion, +}; + +static const VMStateDescription vmstate_ehci = { + .name = "ehci", + .unmigratable = 1, +}; + +static Property ehci_properties[] = { + DEFINE_PROP_UINT32("freq", EHCIState, freq, FRAME_TIMER_FREQ), + DEFINE_PROP_UINT32("maxframes", EHCIState, maxframes, 128), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ehci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_ehci_initfn; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82801D; /* ich4 */ + k->revision = 0x10; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_ehci; + dc->props = ehci_properties; +} + +static TypeInfo ehci_info = { + .name = "usb-ehci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(EHCIState), + .class_init = ehci_class_init, +}; + +static void ich9_ehci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_ehci_initfn; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI1; + k->revision = 0x03; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_ehci; + dc->props = ehci_properties; +} + +static TypeInfo ich9_ehci_info = { + .name = "ich9-usb-ehci1", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(EHCIState), + .class_init = ich9_ehci_class_init, +}; + +static int usb_ehci_initfn(PCIDevice *dev) +{ + EHCIState *s = DO_UPCAST(EHCIState, dev, dev); + uint8_t *pci_conf = s->dev.config; + int i; + + pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20); + + /* capabilities pointer */ + pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x00); + //pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x50); + + pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); /* interrupt pin D */ + pci_set_byte(&pci_conf[PCI_MIN_GNT], 0); + pci_set_byte(&pci_conf[PCI_MAX_LAT], 0); + + // pci_conf[0x50] = 0x01; // power management caps + + pci_set_byte(&pci_conf[USB_SBRN], USB_RELEASE_2); // release number (2.1.4) + pci_set_byte(&pci_conf[0x61], 0x20); // frame length adjustment (2.1.5) + pci_set_word(&pci_conf[0x62], 0x00); // port wake up capability (2.1.6) + + pci_conf[0x64] = 0x00; + pci_conf[0x65] = 0x00; + pci_conf[0x66] = 0x00; + pci_conf[0x67] = 0x00; + pci_conf[0x68] = 0x01; + pci_conf[0x69] = 0x00; + pci_conf[0x6a] = 0x00; + pci_conf[0x6b] = 0x00; // USBLEGSUP + pci_conf[0x6c] = 0x00; + pci_conf[0x6d] = 0x00; + pci_conf[0x6e] = 0x00; + pci_conf[0x6f] = 0xc0; // USBLEFCTLSTS + + // 2.2 host controller interface version + s->mmio[0x00] = (uint8_t) OPREGBASE; + s->mmio[0x01] = 0x00; + s->mmio[0x02] = 0x00; + s->mmio[0x03] = 0x01; // HC version + s->mmio[0x04] = NB_PORTS; // Number of downstream ports + s->mmio[0x05] = 0x00; // No companion ports at present + s->mmio[0x06] = 0x00; + s->mmio[0x07] = 0x00; + s->mmio[0x08] = 0x80; // We can cache whole frame, not 64-bit capable + s->mmio[0x09] = 0x68; // EECP + s->mmio[0x0a] = 0x00; + s->mmio[0x0b] = 0x00; + + s->irq = s->dev.irq[3]; + + usb_bus_new(&s->bus, &ehci_bus_ops, &s->dev.qdev); + for(i = 0; i < NB_PORTS; i++) { + usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops, + USB_SPEED_MASK_HIGH); + s->ports[i].dev = 0; + } + + s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s); + QTAILQ_INIT(&s->aqueues); + QTAILQ_INIT(&s->pqueues); + + qemu_register_reset(ehci_reset, s); + + memory_region_init_io(&s->mem, &ehci_mem_ops, s, "ehci", MMIO_SIZE); + pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem); + + return 0; +} + +static void ehci_register_types(void) +{ + type_register_static(&ehci_info); + type_register_static(&ich9_ehci_info); +} + +type_init(ehci_register_types) + +/* + * vim: expandtab ts=4 + */ diff --git a/hw/usb/hcd-musb.c b/hw/usb/hcd-musb.c new file mode 100644 index 0000000..fa9385e --- /dev/null +++ b/hw/usb/hcd-musb.c @@ -0,0 +1,1544 @@ +/* + * "Inventra" High-speed Dual-Role Controller (MUSB-HDRC), Mentor Graphics, + * USB2.0 OTG compliant core used in various chips. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Only host-mode and non-DMA accesses are currently supported. + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "hw/usb.h" +#include "hw/irq.h" +#include "hw/hw.h" + +/* Common USB registers */ +#define MUSB_HDRC_FADDR 0x00 /* 8-bit */ +#define MUSB_HDRC_POWER 0x01 /* 8-bit */ + +#define MUSB_HDRC_INTRTX 0x02 /* 16-bit */ +#define MUSB_HDRC_INTRRX 0x04 +#define MUSB_HDRC_INTRTXE 0x06 +#define MUSB_HDRC_INTRRXE 0x08 +#define MUSB_HDRC_INTRUSB 0x0a /* 8 bit */ +#define MUSB_HDRC_INTRUSBE 0x0b /* 8 bit */ +#define MUSB_HDRC_FRAME 0x0c /* 16-bit */ +#define MUSB_HDRC_INDEX 0x0e /* 8 bit */ +#define MUSB_HDRC_TESTMODE 0x0f /* 8 bit */ + +/* Per-EP registers in indexed mode */ +#define MUSB_HDRC_EP_IDX 0x10 /* 8-bit */ + +/* EP FIFOs */ +#define MUSB_HDRC_FIFO 0x20 + +/* Additional Control Registers */ +#define MUSB_HDRC_DEVCTL 0x60 /* 8 bit */ + +/* These are indexed */ +#define MUSB_HDRC_TXFIFOSZ 0x62 /* 8 bit (see masks) */ +#define MUSB_HDRC_RXFIFOSZ 0x63 /* 8 bit (see masks) */ +#define MUSB_HDRC_TXFIFOADDR 0x64 /* 16 bit offset shifted right 3 */ +#define MUSB_HDRC_RXFIFOADDR 0x66 /* 16 bit offset shifted right 3 */ + +/* Some more registers */ +#define MUSB_HDRC_VCTRL 0x68 /* 8 bit */ +#define MUSB_HDRC_HWVERS 0x6c /* 8 bit */ + +/* Added in HDRC 1.9(?) & MHDRC 1.4 */ +/* ULPI pass-through */ +#define MUSB_HDRC_ULPI_VBUSCTL 0x70 +#define MUSB_HDRC_ULPI_REGDATA 0x74 +#define MUSB_HDRC_ULPI_REGADDR 0x75 +#define MUSB_HDRC_ULPI_REGCTL 0x76 + +/* Extended config & PHY control */ +#define MUSB_HDRC_ENDCOUNT 0x78 /* 8 bit */ +#define MUSB_HDRC_DMARAMCFG 0x79 /* 8 bit */ +#define MUSB_HDRC_PHYWAIT 0x7a /* 8 bit */ +#define MUSB_HDRC_PHYVPLEN 0x7b /* 8 bit */ +#define MUSB_HDRC_HS_EOF1 0x7c /* 8 bit, units of 546.1 us */ +#define MUSB_HDRC_FS_EOF1 0x7d /* 8 bit, units of 533.3 ns */ +#define MUSB_HDRC_LS_EOF1 0x7e /* 8 bit, units of 1.067 us */ + +/* Per-EP BUSCTL registers */ +#define MUSB_HDRC_BUSCTL 0x80 + +/* Per-EP registers in flat mode */ +#define MUSB_HDRC_EP 0x100 + +/* offsets to registers in flat model */ +#define MUSB_HDRC_TXMAXP 0x00 /* 16 bit apparently */ +#define MUSB_HDRC_TXCSR 0x02 /* 16 bit apparently */ +#define MUSB_HDRC_CSR0 MUSB_HDRC_TXCSR /* re-used for EP0 */ +#define MUSB_HDRC_RXMAXP 0x04 /* 16 bit apparently */ +#define MUSB_HDRC_RXCSR 0x06 /* 16 bit apparently */ +#define MUSB_HDRC_RXCOUNT 0x08 /* 16 bit apparently */ +#define MUSB_HDRC_COUNT0 MUSB_HDRC_RXCOUNT /* re-used for EP0 */ +#define MUSB_HDRC_TXTYPE 0x0a /* 8 bit apparently */ +#define MUSB_HDRC_TYPE0 MUSB_HDRC_TXTYPE /* re-used for EP0 */ +#define MUSB_HDRC_TXINTERVAL 0x0b /* 8 bit apparently */ +#define MUSB_HDRC_NAKLIMIT0 MUSB_HDRC_TXINTERVAL /* re-used for EP0 */ +#define MUSB_HDRC_RXTYPE 0x0c /* 8 bit apparently */ +#define MUSB_HDRC_RXINTERVAL 0x0d /* 8 bit apparently */ +#define MUSB_HDRC_FIFOSIZE 0x0f /* 8 bit apparently */ +#define MUSB_HDRC_CONFIGDATA MGC_O_HDRC_FIFOSIZE /* re-used for EP0 */ + +/* "Bus control" registers */ +#define MUSB_HDRC_TXFUNCADDR 0x00 +#define MUSB_HDRC_TXHUBADDR 0x02 +#define MUSB_HDRC_TXHUBPORT 0x03 + +#define MUSB_HDRC_RXFUNCADDR 0x04 +#define MUSB_HDRC_RXHUBADDR 0x06 +#define MUSB_HDRC_RXHUBPORT 0x07 + +/* + * MUSBHDRC Register bit masks + */ + +/* POWER */ +#define MGC_M_POWER_ISOUPDATE 0x80 +#define MGC_M_POWER_SOFTCONN 0x40 +#define MGC_M_POWER_HSENAB 0x20 +#define MGC_M_POWER_HSMODE 0x10 +#define MGC_M_POWER_RESET 0x08 +#define MGC_M_POWER_RESUME 0x04 +#define MGC_M_POWER_SUSPENDM 0x02 +#define MGC_M_POWER_ENSUSPEND 0x01 + +/* INTRUSB */ +#define MGC_M_INTR_SUSPEND 0x01 +#define MGC_M_INTR_RESUME 0x02 +#define MGC_M_INTR_RESET 0x04 +#define MGC_M_INTR_BABBLE 0x04 +#define MGC_M_INTR_SOF 0x08 +#define MGC_M_INTR_CONNECT 0x10 +#define MGC_M_INTR_DISCONNECT 0x20 +#define MGC_M_INTR_SESSREQ 0x40 +#define MGC_M_INTR_VBUSERROR 0x80 /* FOR SESSION END */ +#define MGC_M_INTR_EP0 0x01 /* FOR EP0 INTERRUPT */ + +/* DEVCTL */ +#define MGC_M_DEVCTL_BDEVICE 0x80 +#define MGC_M_DEVCTL_FSDEV 0x40 +#define MGC_M_DEVCTL_LSDEV 0x20 +#define MGC_M_DEVCTL_VBUS 0x18 +#define MGC_S_DEVCTL_VBUS 3 +#define MGC_M_DEVCTL_HM 0x04 +#define MGC_M_DEVCTL_HR 0x02 +#define MGC_M_DEVCTL_SESSION 0x01 + +/* TESTMODE */ +#define MGC_M_TEST_FORCE_HOST 0x80 +#define MGC_M_TEST_FIFO_ACCESS 0x40 +#define MGC_M_TEST_FORCE_FS 0x20 +#define MGC_M_TEST_FORCE_HS 0x10 +#define MGC_M_TEST_PACKET 0x08 +#define MGC_M_TEST_K 0x04 +#define MGC_M_TEST_J 0x02 +#define MGC_M_TEST_SE0_NAK 0x01 + +/* CSR0 */ +#define MGC_M_CSR0_FLUSHFIFO 0x0100 +#define MGC_M_CSR0_TXPKTRDY 0x0002 +#define MGC_M_CSR0_RXPKTRDY 0x0001 + +/* CSR0 in Peripheral mode */ +#define MGC_M_CSR0_P_SVDSETUPEND 0x0080 +#define MGC_M_CSR0_P_SVDRXPKTRDY 0x0040 +#define MGC_M_CSR0_P_SENDSTALL 0x0020 +#define MGC_M_CSR0_P_SETUPEND 0x0010 +#define MGC_M_CSR0_P_DATAEND 0x0008 +#define MGC_M_CSR0_P_SENTSTALL 0x0004 + +/* CSR0 in Host mode */ +#define MGC_M_CSR0_H_NO_PING 0x0800 +#define MGC_M_CSR0_H_WR_DATATOGGLE 0x0400 /* set to allow setting: */ +#define MGC_M_CSR0_H_DATATOGGLE 0x0200 /* data toggle control */ +#define MGC_M_CSR0_H_NAKTIMEOUT 0x0080 +#define MGC_M_CSR0_H_STATUSPKT 0x0040 +#define MGC_M_CSR0_H_REQPKT 0x0020 +#define MGC_M_CSR0_H_ERROR 0x0010 +#define MGC_M_CSR0_H_SETUPPKT 0x0008 +#define MGC_M_CSR0_H_RXSTALL 0x0004 + +/* CONFIGDATA */ +#define MGC_M_CONFIGDATA_MPRXE 0x80 /* auto bulk pkt combining */ +#define MGC_M_CONFIGDATA_MPTXE 0x40 /* auto bulk pkt splitting */ +#define MGC_M_CONFIGDATA_BIGENDIAN 0x20 +#define MGC_M_CONFIGDATA_HBRXE 0x10 /* HB-ISO for RX */ +#define MGC_M_CONFIGDATA_HBTXE 0x08 /* HB-ISO for TX */ +#define MGC_M_CONFIGDATA_DYNFIFO 0x04 /* dynamic FIFO sizing */ +#define MGC_M_CONFIGDATA_SOFTCONE 0x02 /* SoftConnect */ +#define MGC_M_CONFIGDATA_UTMIDW 0x01 /* Width, 0 => 8b, 1 => 16b */ + +/* TXCSR in Peripheral and Host mode */ +#define MGC_M_TXCSR_AUTOSET 0x8000 +#define MGC_M_TXCSR_ISO 0x4000 +#define MGC_M_TXCSR_MODE 0x2000 +#define MGC_M_TXCSR_DMAENAB 0x1000 +#define MGC_M_TXCSR_FRCDATATOG 0x0800 +#define MGC_M_TXCSR_DMAMODE 0x0400 +#define MGC_M_TXCSR_CLRDATATOG 0x0040 +#define MGC_M_TXCSR_FLUSHFIFO 0x0008 +#define MGC_M_TXCSR_FIFONOTEMPTY 0x0002 +#define MGC_M_TXCSR_TXPKTRDY 0x0001 + +/* TXCSR in Peripheral mode */ +#define MGC_M_TXCSR_P_INCOMPTX 0x0080 +#define MGC_M_TXCSR_P_SENTSTALL 0x0020 +#define MGC_M_TXCSR_P_SENDSTALL 0x0010 +#define MGC_M_TXCSR_P_UNDERRUN 0x0004 + +/* TXCSR in Host mode */ +#define MGC_M_TXCSR_H_WR_DATATOGGLE 0x0200 +#define MGC_M_TXCSR_H_DATATOGGLE 0x0100 +#define MGC_M_TXCSR_H_NAKTIMEOUT 0x0080 +#define MGC_M_TXCSR_H_RXSTALL 0x0020 +#define MGC_M_TXCSR_H_ERROR 0x0004 + +/* RXCSR in Peripheral and Host mode */ +#define MGC_M_RXCSR_AUTOCLEAR 0x8000 +#define MGC_M_RXCSR_DMAENAB 0x2000 +#define MGC_M_RXCSR_DISNYET 0x1000 +#define MGC_M_RXCSR_DMAMODE 0x0800 +#define MGC_M_RXCSR_INCOMPRX 0x0100 +#define MGC_M_RXCSR_CLRDATATOG 0x0080 +#define MGC_M_RXCSR_FLUSHFIFO 0x0010 +#define MGC_M_RXCSR_DATAERROR 0x0008 +#define MGC_M_RXCSR_FIFOFULL 0x0002 +#define MGC_M_RXCSR_RXPKTRDY 0x0001 + +/* RXCSR in Peripheral mode */ +#define MGC_M_RXCSR_P_ISO 0x4000 +#define MGC_M_RXCSR_P_SENTSTALL 0x0040 +#define MGC_M_RXCSR_P_SENDSTALL 0x0020 +#define MGC_M_RXCSR_P_OVERRUN 0x0004 + +/* RXCSR in Host mode */ +#define MGC_M_RXCSR_H_AUTOREQ 0x4000 +#define MGC_M_RXCSR_H_WR_DATATOGGLE 0x0400 +#define MGC_M_RXCSR_H_DATATOGGLE 0x0200 +#define MGC_M_RXCSR_H_RXSTALL 0x0040 +#define MGC_M_RXCSR_H_REQPKT 0x0020 +#define MGC_M_RXCSR_H_ERROR 0x0004 + +/* HUBADDR */ +#define MGC_M_HUBADDR_MULTI_TT 0x80 + +/* ULPI: Added in HDRC 1.9(?) & MHDRC 1.4 */ +#define MGC_M_ULPI_VBCTL_USEEXTVBUSIND 0x02 +#define MGC_M_ULPI_VBCTL_USEEXTVBUS 0x01 +#define MGC_M_ULPI_REGCTL_INT_ENABLE 0x08 +#define MGC_M_ULPI_REGCTL_READNOTWRITE 0x04 +#define MGC_M_ULPI_REGCTL_COMPLETE 0x02 +#define MGC_M_ULPI_REGCTL_REG 0x01 + +/* #define MUSB_DEBUG */ + +#ifdef MUSB_DEBUG +#define TRACE(fmt,...) fprintf(stderr, "%s@%d: " fmt "\n", __FUNCTION__, \ + __LINE__, ##__VA_ARGS__) +#else +#define TRACE(...) +#endif + + +static void musb_attach(USBPort *port); +static void musb_detach(USBPort *port); +static void musb_child_detach(USBPort *port, USBDevice *child); +static void musb_schedule_cb(USBPort *port, USBPacket *p); +static void musb_async_cancel_device(MUSBState *s, USBDevice *dev); + +static USBPortOps musb_port_ops = { + .attach = musb_attach, + .detach = musb_detach, + .child_detach = musb_child_detach, + .complete = musb_schedule_cb, +}; + +static USBBusOps musb_bus_ops = { +}; + +typedef struct MUSBPacket MUSBPacket; +typedef struct MUSBEndPoint MUSBEndPoint; + +struct MUSBPacket { + USBPacket p; + MUSBEndPoint *ep; + int dir; +}; + +struct MUSBEndPoint { + uint16_t faddr[2]; + uint8_t haddr[2]; + uint8_t hport[2]; + uint16_t csr[2]; + uint16_t maxp[2]; + uint16_t rxcount; + uint8_t type[2]; + uint8_t interval[2]; + uint8_t config; + uint8_t fifosize; + int timeout[2]; /* Always in microframes */ + + uint8_t *buf[2]; + int fifolen[2]; + int fifostart[2]; + int fifoaddr[2]; + MUSBPacket packey[2]; + int status[2]; + int ext_size[2]; + + /* For callbacks' use */ + int epnum; + int interrupt[2]; + MUSBState *musb; + USBCallback *delayed_cb[2]; + QEMUTimer *intv_timer[2]; +}; + +struct MUSBState { + qemu_irq irqs[musb_irq_max]; + USBBus bus; + USBPort port; + + int idx; + uint8_t devctl; + uint8_t power; + uint8_t faddr; + + uint8_t intr; + uint8_t mask; + uint16_t tx_intr; + uint16_t tx_mask; + uint16_t rx_intr; + uint16_t rx_mask; + + int setup_len; + int session; + + uint8_t buf[0x8000]; + + /* Duplicating the world since 2008!... probably we should have 32 + * logical, single endpoints instead. */ + MUSBEndPoint ep[16]; +}; + +void musb_reset(MUSBState *s) +{ + int i; + + s->faddr = 0x00; + s->devctl = 0; + s->power = MGC_M_POWER_HSENAB; + s->tx_intr = 0x0000; + s->rx_intr = 0x0000; + s->tx_mask = 0xffff; + s->rx_mask = 0xffff; + s->intr = 0x00; + s->mask = 0x06; + s->idx = 0; + + s->setup_len = 0; + s->session = 0; + memset(s->buf, 0, sizeof(s->buf)); + + /* TODO: _DW */ + s->ep[0].config = MGC_M_CONFIGDATA_SOFTCONE | MGC_M_CONFIGDATA_DYNFIFO; + for (i = 0; i < 16; i ++) { + s->ep[i].fifosize = 64; + s->ep[i].maxp[0] = 0x40; + s->ep[i].maxp[1] = 0x40; + s->ep[i].musb = s; + s->ep[i].epnum = i; + usb_packet_init(&s->ep[i].packey[0].p); + usb_packet_init(&s->ep[i].packey[1].p); + } +} + +struct MUSBState *musb_init(DeviceState *parent_device, int gpio_base) +{ + MUSBState *s = g_malloc0(sizeof(*s)); + int i; + + for (i = 0; i < musb_irq_max; i++) { + s->irqs[i] = qdev_get_gpio_in(parent_device, gpio_base + i); + } + + musb_reset(s); + + usb_bus_new(&s->bus, &musb_bus_ops, parent_device); + usb_register_port(&s->bus, &s->port, s, 0, &musb_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); + + return s; +} + +static void musb_vbus_set(MUSBState *s, int level) +{ + if (level) + s->devctl |= 3 << MGC_S_DEVCTL_VBUS; + else + s->devctl &= ~MGC_M_DEVCTL_VBUS; + + qemu_set_irq(s->irqs[musb_set_vbus], level); +} + +static void musb_intr_set(MUSBState *s, int line, int level) +{ + if (!level) { + s->intr &= ~(1 << line); + qemu_irq_lower(s->irqs[line]); + } else if (s->mask & (1 << line)) { + s->intr |= 1 << line; + qemu_irq_raise(s->irqs[line]); + } +} + +static void musb_tx_intr_set(MUSBState *s, int line, int level) +{ + if (!level) { + s->tx_intr &= ~(1 << line); + if (!s->tx_intr) + qemu_irq_lower(s->irqs[musb_irq_tx]); + } else if (s->tx_mask & (1 << line)) { + s->tx_intr |= 1 << line; + qemu_irq_raise(s->irqs[musb_irq_tx]); + } +} + +static void musb_rx_intr_set(MUSBState *s, int line, int level) +{ + if (line) { + if (!level) { + s->rx_intr &= ~(1 << line); + if (!s->rx_intr) + qemu_irq_lower(s->irqs[musb_irq_rx]); + } else if (s->rx_mask & (1 << line)) { + s->rx_intr |= 1 << line; + qemu_irq_raise(s->irqs[musb_irq_rx]); + } + } else + musb_tx_intr_set(s, line, level); +} + +uint32_t musb_core_intr_get(MUSBState *s) +{ + return (s->rx_intr << 15) | s->tx_intr; +} + +void musb_core_intr_clear(MUSBState *s, uint32_t mask) +{ + if (s->rx_intr) { + s->rx_intr &= mask >> 15; + if (!s->rx_intr) + qemu_irq_lower(s->irqs[musb_irq_rx]); + } + + if (s->tx_intr) { + s->tx_intr &= mask & 0xffff; + if (!s->tx_intr) + qemu_irq_lower(s->irqs[musb_irq_tx]); + } +} + +void musb_set_size(MUSBState *s, int epnum, int size, int is_tx) +{ + s->ep[epnum].ext_size[!is_tx] = size; + s->ep[epnum].fifostart[0] = 0; + s->ep[epnum].fifostart[1] = 0; + s->ep[epnum].fifolen[0] = 0; + s->ep[epnum].fifolen[1] = 0; +} + +static void musb_session_update(MUSBState *s, int prev_dev, int prev_sess) +{ + int detect_prev = prev_dev && prev_sess; + int detect = !!s->port.dev && s->session; + + if (detect && !detect_prev) { + /* Let's skip the ID pin sense and VBUS sense formalities and + * and signal a successful SRP directly. This should work at least + * for the Linux driver stack. */ + musb_intr_set(s, musb_irq_connect, 1); + + if (s->port.dev->speed == USB_SPEED_LOW) { + s->devctl &= ~MGC_M_DEVCTL_FSDEV; + s->devctl |= MGC_M_DEVCTL_LSDEV; + } else { + s->devctl |= MGC_M_DEVCTL_FSDEV; + s->devctl &= ~MGC_M_DEVCTL_LSDEV; + } + + /* A-mode? */ + s->devctl &= ~MGC_M_DEVCTL_BDEVICE; + + /* Host-mode bit? */ + s->devctl |= MGC_M_DEVCTL_HM; +#if 1 + musb_vbus_set(s, 1); +#endif + } else if (!detect && detect_prev) { +#if 1 + musb_vbus_set(s, 0); +#endif + } +} + +/* Attach or detach a device on our only port. */ +static void musb_attach(USBPort *port) +{ + MUSBState *s = (MUSBState *) port->opaque; + + musb_intr_set(s, musb_irq_vbus_request, 1); + musb_session_update(s, 0, s->session); +} + +static void musb_detach(USBPort *port) +{ + MUSBState *s = (MUSBState *) port->opaque; + + musb_async_cancel_device(s, port->dev); + + musb_intr_set(s, musb_irq_disconnect, 1); + musb_session_update(s, 1, s->session); +} + +static void musb_child_detach(USBPort *port, USBDevice *child) +{ + MUSBState *s = (MUSBState *) port->opaque; + + musb_async_cancel_device(s, child); +} + +static void musb_cb_tick0(void *opaque) +{ + MUSBEndPoint *ep = (MUSBEndPoint *) opaque; + + ep->delayed_cb[0](&ep->packey[0].p, opaque); +} + +static void musb_cb_tick1(void *opaque) +{ + MUSBEndPoint *ep = (MUSBEndPoint *) opaque; + + ep->delayed_cb[1](&ep->packey[1].p, opaque); +} + +#define musb_cb_tick (dir ? musb_cb_tick1 : musb_cb_tick0) + +static void musb_schedule_cb(USBPort *port, USBPacket *packey) +{ + MUSBPacket *p = container_of(packey, MUSBPacket, p); + MUSBEndPoint *ep = p->ep; + int dir = p->dir; + int timeout = 0; + + if (ep->status[dir] == USB_RET_NAK) + timeout = ep->timeout[dir]; + else if (ep->interrupt[dir]) + timeout = 8; + else + return musb_cb_tick(ep); + + if (!ep->intv_timer[dir]) + ep->intv_timer[dir] = qemu_new_timer_ns(vm_clock, musb_cb_tick, ep); + + qemu_mod_timer(ep->intv_timer[dir], qemu_get_clock_ns(vm_clock) + + muldiv64(timeout, get_ticks_per_sec(), 8000)); +} + +static int musb_timeout(int ttype, int speed, int val) +{ +#if 1 + return val << 3; +#endif + + switch (ttype) { + case USB_ENDPOINT_XFER_CONTROL: + if (val < 2) + return 0; + else if (speed == USB_SPEED_HIGH) + return 1 << (val - 1); + else + return 8 << (val - 1); + + case USB_ENDPOINT_XFER_INT: + if (speed == USB_SPEED_HIGH) + if (val < 2) + return 0; + else + return 1 << (val - 1); + else + return val << 3; + + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_ISOC: + if (val < 2) + return 0; + else if (speed == USB_SPEED_HIGH) + return 1 << (val - 1); + else + return 8 << (val - 1); + /* TODO: what with low-speed Bulk and Isochronous? */ + } + + hw_error("bad interval\n"); +} + +static void musb_packet(MUSBState *s, MUSBEndPoint *ep, + int epnum, int pid, int len, USBCallback cb, int dir) +{ + USBDevice *dev; + USBEndpoint *uep; + int ret; + int idx = epnum && dir; + int ttype; + + /* ep->type[0,1] contains: + * in bits 7:6 the speed (0 - invalid, 1 - high, 2 - full, 3 - slow) + * in bits 5:4 the transfer type (BULK / INT) + * in bits 3:0 the EP num + */ + ttype = epnum ? (ep->type[idx] >> 4) & 3 : 0; + + ep->timeout[dir] = musb_timeout(ttype, + ep->type[idx] >> 6, ep->interval[idx]); + ep->interrupt[dir] = ttype == USB_ENDPOINT_XFER_INT; + ep->delayed_cb[dir] = cb; + + /* A wild guess on the FADDR semantics... */ + dev = usb_find_device(&s->port, ep->faddr[idx]); + uep = usb_ep_get(dev, pid, ep->type[idx] & 0xf); + usb_packet_setup(&ep->packey[dir].p, pid, uep); + usb_packet_addbuf(&ep->packey[dir].p, ep->buf[idx], len); + ep->packey[dir].ep = ep; + ep->packey[dir].dir = dir; + + ret = usb_handle_packet(dev, &ep->packey[dir].p); + + if (ret == USB_RET_ASYNC) { + ep->status[dir] = len; + return; + } + + ep->status[dir] = ret; + musb_schedule_cb(&s->port, &ep->packey[dir].p); +} + +static void musb_tx_packet_complete(USBPacket *packey, void *opaque) +{ + /* Unfortunately we can't use packey->devep because that's the remote + * endpoint number and may be different than our local. */ + MUSBEndPoint *ep = (MUSBEndPoint *) opaque; + int epnum = ep->epnum; + MUSBState *s = ep->musb; + + ep->fifostart[0] = 0; + ep->fifolen[0] = 0; +#ifdef CLEAR_NAK + if (ep->status[0] != USB_RET_NAK) { +#endif + if (epnum) + ep->csr[0] &= ~(MGC_M_TXCSR_FIFONOTEMPTY | MGC_M_TXCSR_TXPKTRDY); + else + ep->csr[0] &= ~MGC_M_CSR0_TXPKTRDY; +#ifdef CLEAR_NAK + } +#endif + + /* Clear all of the error bits first */ + if (epnum) + ep->csr[0] &= ~(MGC_M_TXCSR_H_ERROR | MGC_M_TXCSR_H_RXSTALL | + MGC_M_TXCSR_H_NAKTIMEOUT); + else + ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL | + MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING); + + if (ep->status[0] == USB_RET_STALL) { + /* Command not supported by target! */ + ep->status[0] = 0; + + if (epnum) + ep->csr[0] |= MGC_M_TXCSR_H_RXSTALL; + else + ep->csr[0] |= MGC_M_CSR0_H_RXSTALL; + } + + if (ep->status[0] == USB_RET_NAK) { + ep->status[0] = 0; + + /* NAK timeouts are only generated in Bulk transfers and + * Data-errors in Isochronous. */ + if (ep->interrupt[0]) { + return; + } + + if (epnum) + ep->csr[0] |= MGC_M_TXCSR_H_NAKTIMEOUT; + else + ep->csr[0] |= MGC_M_CSR0_H_NAKTIMEOUT; + } + + if (ep->status[0] < 0) { + if (ep->status[0] == USB_RET_BABBLE) + musb_intr_set(s, musb_irq_rst_babble, 1); + + /* Pretend we've tried three times already and failed (in + * case of USB_TOKEN_SETUP). */ + if (epnum) + ep->csr[0] |= MGC_M_TXCSR_H_ERROR; + else + ep->csr[0] |= MGC_M_CSR0_H_ERROR; + + musb_tx_intr_set(s, epnum, 1); + return; + } + /* TODO: check len for over/underruns of an OUT packet? */ + +#ifdef SETUPLEN_HACK + if (!epnum && ep->packey[0].pid == USB_TOKEN_SETUP) + s->setup_len = ep->packey[0].data[6]; +#endif + + /* In DMA mode: if no error, assert DMA request for this EP, + * and skip the interrupt. */ + musb_tx_intr_set(s, epnum, 1); +} + +static void musb_rx_packet_complete(USBPacket *packey, void *opaque) +{ + /* Unfortunately we can't use packey->devep because that's the remote + * endpoint number and may be different than our local. */ + MUSBEndPoint *ep = (MUSBEndPoint *) opaque; + int epnum = ep->epnum; + MUSBState *s = ep->musb; + + ep->fifostart[1] = 0; + ep->fifolen[1] = 0; + +#ifdef CLEAR_NAK + if (ep->status[1] != USB_RET_NAK) { +#endif + ep->csr[1] &= ~MGC_M_RXCSR_H_REQPKT; + if (!epnum) + ep->csr[0] &= ~MGC_M_CSR0_H_REQPKT; +#ifdef CLEAR_NAK + } +#endif + + /* Clear all of the imaginable error bits first */ + ep->csr[1] &= ~(MGC_M_RXCSR_H_ERROR | MGC_M_RXCSR_H_RXSTALL | + MGC_M_RXCSR_DATAERROR); + if (!epnum) + ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL | + MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING); + + if (ep->status[1] == USB_RET_STALL) { + ep->status[1] = 0; + packey->result = 0; + + ep->csr[1] |= MGC_M_RXCSR_H_RXSTALL; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_H_RXSTALL; + } + + if (ep->status[1] == USB_RET_NAK) { + ep->status[1] = 0; + + /* NAK timeouts are only generated in Bulk transfers and + * Data-errors in Isochronous. */ + if (ep->interrupt[1]) + return musb_packet(s, ep, epnum, USB_TOKEN_IN, + packey->iov.size, musb_rx_packet_complete, 1); + + ep->csr[1] |= MGC_M_RXCSR_DATAERROR; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_H_NAKTIMEOUT; + } + + if (ep->status[1] < 0) { + if (ep->status[1] == USB_RET_BABBLE) { + musb_intr_set(s, musb_irq_rst_babble, 1); + return; + } + + /* Pretend we've tried three times already and failed (in + * case of a control transfer). */ + ep->csr[1] |= MGC_M_RXCSR_H_ERROR; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_H_ERROR; + + musb_rx_intr_set(s, epnum, 1); + return; + } + /* TODO: check len for over/underruns of an OUT packet? */ + /* TODO: perhaps make use of e->ext_size[1] here. */ + + packey->result = ep->status[1]; + + if (!(ep->csr[1] & (MGC_M_RXCSR_H_RXSTALL | MGC_M_RXCSR_DATAERROR))) { + ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_RXPKTRDY; + + ep->rxcount = packey->result; /* XXX: MIN(packey->len, ep->maxp[1]); */ + /* In DMA mode: assert DMA request for this EP */ + } + + /* Only if DMA has not been asserted */ + musb_rx_intr_set(s, epnum, 1); +} + +static void musb_async_cancel_device(MUSBState *s, USBDevice *dev) +{ + int ep, dir; + + for (ep = 0; ep < 16; ep++) { + for (dir = 0; dir < 2; dir++) { + if (!usb_packet_is_inflight(&s->ep[ep].packey[dir].p) || + s->ep[ep].packey[dir].p.ep->dev != dev) { + continue; + } + usb_cancel_packet(&s->ep[ep].packey[dir].p); + /* status updates needed here? */ + } + } +} + +static void musb_tx_rdy(MUSBState *s, int epnum) +{ + MUSBEndPoint *ep = s->ep + epnum; + int pid; + int total, valid = 0; + TRACE("start %d, len %d", ep->fifostart[0], ep->fifolen[0] ); + ep->fifostart[0] += ep->fifolen[0]; + ep->fifolen[0] = 0; + + /* XXX: how's the total size of the packet retrieved exactly in + * the generic case? */ + total = ep->maxp[0] & 0x3ff; + + if (ep->ext_size[0]) { + total = ep->ext_size[0]; + ep->ext_size[0] = 0; + valid = 1; + } + + /* If the packet is not fully ready yet, wait for a next segment. */ + if (epnum && (ep->fifostart[0]) < total) + return; + + if (!valid) + total = ep->fifostart[0]; + + pid = USB_TOKEN_OUT; + if (!epnum && (ep->csr[0] & MGC_M_CSR0_H_SETUPPKT)) { + pid = USB_TOKEN_SETUP; + if (total != 8) { + TRACE("illegal SETUPPKT length of %i bytes", total); + } + /* Controller should retry SETUP packets three times on errors + * but it doesn't make sense for us to do that. */ + } + + return musb_packet(s, ep, epnum, pid, + total, musb_tx_packet_complete, 0); +} + +static void musb_rx_req(MUSBState *s, int epnum) +{ + MUSBEndPoint *ep = s->ep + epnum; + int total; + + /* If we already have a packet, which didn't fit into the + * 64 bytes of the FIFO, only move the FIFO start and return. (Obsolete) */ + if (ep->packey[1].p.pid == USB_TOKEN_IN && ep->status[1] >= 0 && + (ep->fifostart[1]) + ep->rxcount < + ep->packey[1].p.iov.size) { + TRACE("0x%08x, %d", ep->fifostart[1], ep->rxcount ); + ep->fifostart[1] += ep->rxcount; + ep->fifolen[1] = 0; + + ep->rxcount = MIN(ep->packey[0].p.iov.size - (ep->fifostart[1]), + ep->maxp[1]); + + ep->csr[1] &= ~MGC_M_RXCSR_H_REQPKT; + if (!epnum) + ep->csr[0] &= ~MGC_M_CSR0_H_REQPKT; + + /* Clear all of the error bits first */ + ep->csr[1] &= ~(MGC_M_RXCSR_H_ERROR | MGC_M_RXCSR_H_RXSTALL | + MGC_M_RXCSR_DATAERROR); + if (!epnum) + ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL | + MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING); + + ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_RXPKTRDY; + musb_rx_intr_set(s, epnum, 1); + return; + } + + /* The driver sets maxp[1] to 64 or less because it knows the hardware + * FIFO is this deep. Bigger packets get split in + * usb_generic_handle_packet but we can also do the splitting locally + * for performance. It turns out we can also have a bigger FIFO and + * ignore the limit set in ep->maxp[1]. The Linux MUSB driver deals + * OK with single packets of even 32KB and we avoid splitting, however + * usb_msd.c sometimes sends a packet bigger than what Linux expects + * (e.g. 8192 bytes instead of 4096) and we get an OVERRUN. Splitting + * hides this overrun from Linux. Up to 4096 everything is fine + * though. Currently this is disabled. + * + * XXX: mind ep->fifosize. */ + total = MIN(ep->maxp[1] & 0x3ff, sizeof(s->buf)); + +#ifdef SETUPLEN_HACK + /* Why should *we* do that instead of Linux? */ + if (!epnum) { + if (ep->packey[0].p.devaddr == 2) { + total = MIN(s->setup_len, 8); + } else { + total = MIN(s->setup_len, 64); + } + s->setup_len -= total; + } +#endif + + return musb_packet(s, ep, epnum, USB_TOKEN_IN, + total, musb_rx_packet_complete, 1); +} + +static uint8_t musb_read_fifo(MUSBEndPoint *ep) +{ + uint8_t value; + if (ep->fifolen[1] >= 64) { + /* We have a FIFO underrun */ + TRACE("EP%d FIFO is now empty, stop reading", ep->epnum); + return 0x00000000; + } + /* In DMA mode clear RXPKTRDY and set REQPKT automatically + * (if AUTOREQ is set) */ + + ep->csr[1] &= ~MGC_M_RXCSR_FIFOFULL; + value=ep->buf[1][ep->fifostart[1] + ep->fifolen[1] ++]; + TRACE("EP%d 0x%02x, %d", ep->epnum, value, ep->fifolen[1] ); + return value; +} + +static void musb_write_fifo(MUSBEndPoint *ep, uint8_t value) +{ + TRACE("EP%d = %02x", ep->epnum, value); + if (ep->fifolen[0] >= 64) { + /* We have a FIFO overrun */ + TRACE("EP%d FIFO exceeded 64 bytes, stop feeding data", ep->epnum); + return; + } + + ep->buf[0][ep->fifostart[0] + ep->fifolen[0] ++] = value; + ep->csr[0] |= MGC_M_TXCSR_FIFONOTEMPTY; +} + +static void musb_ep_frame_cancel(MUSBEndPoint *ep, int dir) +{ + if (ep->intv_timer[dir]) + qemu_del_timer(ep->intv_timer[dir]); +} + +/* Bus control */ +static uint8_t musb_busctl_readb(void *opaque, int ep, int addr) +{ + MUSBState *s = (MUSBState *) opaque; + + switch (addr) { + /* For USB2.0 HS hubs only */ + case MUSB_HDRC_TXHUBADDR: + return s->ep[ep].haddr[0]; + case MUSB_HDRC_TXHUBPORT: + return s->ep[ep].hport[0]; + case MUSB_HDRC_RXHUBADDR: + return s->ep[ep].haddr[1]; + case MUSB_HDRC_RXHUBPORT: + return s->ep[ep].hport[1]; + + default: + TRACE("unknown register 0x%02x", addr); + return 0x00; + }; +} + +static void musb_busctl_writeb(void *opaque, int ep, int addr, uint8_t value) +{ + MUSBState *s = (MUSBState *) opaque; + + switch (addr) { + case MUSB_HDRC_TXFUNCADDR: + s->ep[ep].faddr[0] = value; + break; + case MUSB_HDRC_RXFUNCADDR: + s->ep[ep].faddr[1] = value; + break; + case MUSB_HDRC_TXHUBADDR: + s->ep[ep].haddr[0] = value; + break; + case MUSB_HDRC_TXHUBPORT: + s->ep[ep].hport[0] = value; + break; + case MUSB_HDRC_RXHUBADDR: + s->ep[ep].haddr[1] = value; + break; + case MUSB_HDRC_RXHUBPORT: + s->ep[ep].hport[1] = value; + break; + + default: + TRACE("unknown register 0x%02x", addr); + break; + }; +} + +static uint16_t musb_busctl_readh(void *opaque, int ep, int addr) +{ + MUSBState *s = (MUSBState *) opaque; + + switch (addr) { + case MUSB_HDRC_TXFUNCADDR: + return s->ep[ep].faddr[0]; + case MUSB_HDRC_RXFUNCADDR: + return s->ep[ep].faddr[1]; + + default: + return musb_busctl_readb(s, ep, addr) | + (musb_busctl_readb(s, ep, addr | 1) << 8); + }; +} + +static void musb_busctl_writeh(void *opaque, int ep, int addr, uint16_t value) +{ + MUSBState *s = (MUSBState *) opaque; + + switch (addr) { + case MUSB_HDRC_TXFUNCADDR: + s->ep[ep].faddr[0] = value; + break; + case MUSB_HDRC_RXFUNCADDR: + s->ep[ep].faddr[1] = value; + break; + + default: + musb_busctl_writeb(s, ep, addr, value & 0xff); + musb_busctl_writeb(s, ep, addr | 1, value >> 8); + }; +} + +/* Endpoint control */ +static uint8_t musb_ep_readb(void *opaque, int ep, int addr) +{ + MUSBState *s = (MUSBState *) opaque; + + switch (addr) { + case MUSB_HDRC_TXTYPE: + return s->ep[ep].type[0]; + case MUSB_HDRC_TXINTERVAL: + return s->ep[ep].interval[0]; + case MUSB_HDRC_RXTYPE: + return s->ep[ep].type[1]; + case MUSB_HDRC_RXINTERVAL: + return s->ep[ep].interval[1]; + case (MUSB_HDRC_FIFOSIZE & ~1): + return 0x00; + case MUSB_HDRC_FIFOSIZE: + return ep ? s->ep[ep].fifosize : s->ep[ep].config; + case MUSB_HDRC_RXCOUNT: + return s->ep[ep].rxcount; + + default: + TRACE("unknown register 0x%02x", addr); + return 0x00; + }; +} + +static void musb_ep_writeb(void *opaque, int ep, int addr, uint8_t value) +{ + MUSBState *s = (MUSBState *) opaque; + + switch (addr) { + case MUSB_HDRC_TXTYPE: + s->ep[ep].type[0] = value; + break; + case MUSB_HDRC_TXINTERVAL: + s->ep[ep].interval[0] = value; + musb_ep_frame_cancel(&s->ep[ep], 0); + break; + case MUSB_HDRC_RXTYPE: + s->ep[ep].type[1] = value; + break; + case MUSB_HDRC_RXINTERVAL: + s->ep[ep].interval[1] = value; + musb_ep_frame_cancel(&s->ep[ep], 1); + break; + case (MUSB_HDRC_FIFOSIZE & ~1): + break; + case MUSB_HDRC_FIFOSIZE: + TRACE("somebody messes with fifosize (now %i bytes)", value); + s->ep[ep].fifosize = value; + break; + default: + TRACE("unknown register 0x%02x", addr); + break; + }; +} + +static uint16_t musb_ep_readh(void *opaque, int ep, int addr) +{ + MUSBState *s = (MUSBState *) opaque; + uint16_t ret; + + switch (addr) { + case MUSB_HDRC_TXMAXP: + return s->ep[ep].maxp[0]; + case MUSB_HDRC_TXCSR: + return s->ep[ep].csr[0]; + case MUSB_HDRC_RXMAXP: + return s->ep[ep].maxp[1]; + case MUSB_HDRC_RXCSR: + ret = s->ep[ep].csr[1]; + + /* TODO: This and other bits probably depend on + * ep->csr[1] & MGC_M_RXCSR_AUTOCLEAR. */ + if (s->ep[ep].csr[1] & MGC_M_RXCSR_AUTOCLEAR) + s->ep[ep].csr[1] &= ~MGC_M_RXCSR_RXPKTRDY; + + return ret; + case MUSB_HDRC_RXCOUNT: + return s->ep[ep].rxcount; + + default: + return musb_ep_readb(s, ep, addr) | + (musb_ep_readb(s, ep, addr | 1) << 8); + }; +} + +static void musb_ep_writeh(void *opaque, int ep, int addr, uint16_t value) +{ + MUSBState *s = (MUSBState *) opaque; + + switch (addr) { + case MUSB_HDRC_TXMAXP: + s->ep[ep].maxp[0] = value; + break; + case MUSB_HDRC_TXCSR: + if (ep) { + s->ep[ep].csr[0] &= value & 0xa6; + s->ep[ep].csr[0] |= value & 0xff59; + } else { + s->ep[ep].csr[0] &= value & 0x85; + s->ep[ep].csr[0] |= value & 0xf7a; + } + + musb_ep_frame_cancel(&s->ep[ep], 0); + + if ((ep && (value & MGC_M_TXCSR_FLUSHFIFO)) || + (!ep && (value & MGC_M_CSR0_FLUSHFIFO))) { + s->ep[ep].fifolen[0] = 0; + s->ep[ep].fifostart[0] = 0; + if (ep) + s->ep[ep].csr[0] &= + ~(MGC_M_TXCSR_FIFONOTEMPTY | MGC_M_TXCSR_TXPKTRDY); + else + s->ep[ep].csr[0] &= + ~(MGC_M_CSR0_TXPKTRDY | MGC_M_CSR0_RXPKTRDY); + } + if ( + (ep && +#ifdef CLEAR_NAK + (value & MGC_M_TXCSR_TXPKTRDY) && + !(value & MGC_M_TXCSR_H_NAKTIMEOUT)) || +#else + (value & MGC_M_TXCSR_TXPKTRDY)) || +#endif + (!ep && +#ifdef CLEAR_NAK + (value & MGC_M_CSR0_TXPKTRDY) && + !(value & MGC_M_CSR0_H_NAKTIMEOUT))) +#else + (value & MGC_M_CSR0_TXPKTRDY))) +#endif + musb_tx_rdy(s, ep); + if (!ep && + (value & MGC_M_CSR0_H_REQPKT) && +#ifdef CLEAR_NAK + !(value & (MGC_M_CSR0_H_NAKTIMEOUT | + MGC_M_CSR0_RXPKTRDY))) +#else + !(value & MGC_M_CSR0_RXPKTRDY)) +#endif + musb_rx_req(s, ep); + break; + + case MUSB_HDRC_RXMAXP: + s->ep[ep].maxp[1] = value; + break; + case MUSB_HDRC_RXCSR: + /* (DMA mode only) */ + if ( + (value & MGC_M_RXCSR_H_AUTOREQ) && + !(value & MGC_M_RXCSR_RXPKTRDY) && + (s->ep[ep].csr[1] & MGC_M_RXCSR_RXPKTRDY)) + value |= MGC_M_RXCSR_H_REQPKT; + + s->ep[ep].csr[1] &= 0x102 | (value & 0x4d); + s->ep[ep].csr[1] |= value & 0xfeb0; + + musb_ep_frame_cancel(&s->ep[ep], 1); + + if (value & MGC_M_RXCSR_FLUSHFIFO) { + s->ep[ep].fifolen[1] = 0; + s->ep[ep].fifostart[1] = 0; + s->ep[ep].csr[1] &= ~(MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY); + /* If double buffering and we have two packets ready, flush + * only the first one and set up the fifo at the second packet. */ + } +#ifdef CLEAR_NAK + if ((value & MGC_M_RXCSR_H_REQPKT) && !(value & MGC_M_RXCSR_DATAERROR)) +#else + if (value & MGC_M_RXCSR_H_REQPKT) +#endif + musb_rx_req(s, ep); + break; + case MUSB_HDRC_RXCOUNT: + s->ep[ep].rxcount = value; + break; + + default: + musb_ep_writeb(s, ep, addr, value & 0xff); + musb_ep_writeb(s, ep, addr | 1, value >> 8); + }; +} + +/* Generic control */ +static uint32_t musb_readb(void *opaque, target_phys_addr_t addr) +{ + MUSBState *s = (MUSBState *) opaque; + int ep, i; + uint8_t ret; + + switch (addr) { + case MUSB_HDRC_FADDR: + return s->faddr; + case MUSB_HDRC_POWER: + return s->power; + case MUSB_HDRC_INTRUSB: + ret = s->intr; + for (i = 0; i < sizeof(ret) * 8; i ++) + if (ret & (1 << i)) + musb_intr_set(s, i, 0); + return ret; + case MUSB_HDRC_INTRUSBE: + return s->mask; + case MUSB_HDRC_INDEX: + return s->idx; + case MUSB_HDRC_TESTMODE: + return 0x00; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + return musb_ep_readb(s, s->idx, addr & 0xf); + + case MUSB_HDRC_DEVCTL: + return s->devctl; + + case MUSB_HDRC_TXFIFOSZ: + case MUSB_HDRC_RXFIFOSZ: + case MUSB_HDRC_VCTRL: + /* TODO */ + return 0x00; + + case MUSB_HDRC_HWVERS: + return (1 << 10) | 400; + + case (MUSB_HDRC_VCTRL | 1): + case (MUSB_HDRC_HWVERS | 1): + case (MUSB_HDRC_DEVCTL | 1): + return 0x00; + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + return musb_busctl_readb(s, ep, addr & 0x7); + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + return musb_ep_readb(s, ep, addr & 0xf); + + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + return musb_read_fifo(s->ep + ep); + + default: + TRACE("unknown register 0x%02x", (int) addr); + return 0x00; + }; +} + +static void musb_writeb(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + MUSBState *s = (MUSBState *) opaque; + int ep; + + switch (addr) { + case MUSB_HDRC_FADDR: + s->faddr = value & 0x7f; + break; + case MUSB_HDRC_POWER: + s->power = (value & 0xef) | (s->power & 0x10); + /* MGC_M_POWER_RESET is also read-only in Peripheral Mode */ + if ((value & MGC_M_POWER_RESET) && s->port.dev) { + usb_device_reset(s->port.dev); + /* Negotiate high-speed operation if MGC_M_POWER_HSENAB is set. */ + if ((value & MGC_M_POWER_HSENAB) && + s->port.dev->speed == USB_SPEED_HIGH) + s->power |= MGC_M_POWER_HSMODE; /* Success */ + /* Restart frame counting. */ + } + if (value & MGC_M_POWER_SUSPENDM) { + /* When all transfers finish, suspend and if MGC_M_POWER_ENSUSPEND + * is set, also go into low power mode. Frame counting stops. */ + /* XXX: Cleared when the interrupt register is read */ + } + if (value & MGC_M_POWER_RESUME) { + /* Wait 20ms and signal resuming on the bus. Frame counting + * restarts. */ + } + break; + case MUSB_HDRC_INTRUSB: + break; + case MUSB_HDRC_INTRUSBE: + s->mask = value & 0xff; + break; + case MUSB_HDRC_INDEX: + s->idx = value & 0xf; + break; + case MUSB_HDRC_TESTMODE: + break; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + musb_ep_writeb(s, s->idx, addr & 0xf, value); + break; + + case MUSB_HDRC_DEVCTL: + s->session = !!(value & MGC_M_DEVCTL_SESSION); + musb_session_update(s, + !!s->port.dev, + !!(s->devctl & MGC_M_DEVCTL_SESSION)); + + /* It seems this is the only R/W bit in this register? */ + s->devctl &= ~MGC_M_DEVCTL_SESSION; + s->devctl |= value & MGC_M_DEVCTL_SESSION; + break; + + case MUSB_HDRC_TXFIFOSZ: + case MUSB_HDRC_RXFIFOSZ: + case MUSB_HDRC_VCTRL: + /* TODO */ + break; + + case (MUSB_HDRC_VCTRL | 1): + case (MUSB_HDRC_DEVCTL | 1): + break; + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + musb_busctl_writeb(s, ep, addr & 0x7, value); + break; + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + musb_ep_writeb(s, ep, addr & 0xf, value); + break; + + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + musb_write_fifo(s->ep + ep, value & 0xff); + break; + + default: + TRACE("unknown register 0x%02x", (int) addr); + break; + }; +} + +static uint32_t musb_readh(void *opaque, target_phys_addr_t addr) +{ + MUSBState *s = (MUSBState *) opaque; + int ep, i; + uint16_t ret; + + switch (addr) { + case MUSB_HDRC_INTRTX: + ret = s->tx_intr; + /* Auto clear */ + for (i = 0; i < sizeof(ret) * 8; i ++) + if (ret & (1 << i)) + musb_tx_intr_set(s, i, 0); + return ret; + case MUSB_HDRC_INTRRX: + ret = s->rx_intr; + /* Auto clear */ + for (i = 0; i < sizeof(ret) * 8; i ++) + if (ret & (1 << i)) + musb_rx_intr_set(s, i, 0); + return ret; + case MUSB_HDRC_INTRTXE: + return s->tx_mask; + case MUSB_HDRC_INTRRXE: + return s->rx_mask; + + case MUSB_HDRC_FRAME: + /* TODO */ + return 0x0000; + case MUSB_HDRC_TXFIFOADDR: + return s->ep[s->idx].fifoaddr[0]; + case MUSB_HDRC_RXFIFOADDR: + return s->ep[s->idx].fifoaddr[1]; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + return musb_ep_readh(s, s->idx, addr & 0xf); + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + return musb_busctl_readh(s, ep, addr & 0x7); + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + return musb_ep_readh(s, ep, addr & 0xf); + + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + return (musb_read_fifo(s->ep + ep) | musb_read_fifo(s->ep + ep) << 8); + + default: + return musb_readb(s, addr) | (musb_readb(s, addr | 1) << 8); + }; +} + +static void musb_writeh(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + MUSBState *s = (MUSBState *) opaque; + int ep; + + switch (addr) { + case MUSB_HDRC_INTRTXE: + s->tx_mask = value; + /* XXX: the masks seem to apply on the raising edge like with + * edge-triggered interrupts, thus no need to update. I may be + * wrong though. */ + break; + case MUSB_HDRC_INTRRXE: + s->rx_mask = value; + break; + + case MUSB_HDRC_FRAME: + /* TODO */ + break; + case MUSB_HDRC_TXFIFOADDR: + s->ep[s->idx].fifoaddr[0] = value; + s->ep[s->idx].buf[0] = + s->buf + ((value << 3) & 0x7ff ); + break; + case MUSB_HDRC_RXFIFOADDR: + s->ep[s->idx].fifoaddr[1] = value; + s->ep[s->idx].buf[1] = + s->buf + ((value << 3) & 0x7ff); + break; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + musb_ep_writeh(s, s->idx, addr & 0xf, value); + break; + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + musb_busctl_writeh(s, ep, addr & 0x7, value); + break; + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + musb_ep_writeh(s, ep, addr & 0xf, value); + break; + + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + musb_write_fifo(s->ep + ep, value & 0xff); + musb_write_fifo(s->ep + ep, (value >> 8) & 0xff); + break; + + default: + musb_writeb(s, addr, value & 0xff); + musb_writeb(s, addr | 1, value >> 8); + }; +} + +static uint32_t musb_readw(void *opaque, target_phys_addr_t addr) +{ + MUSBState *s = (MUSBState *) opaque; + int ep; + + switch (addr) { + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + return ( musb_read_fifo(s->ep + ep) | + musb_read_fifo(s->ep + ep) << 8 | + musb_read_fifo(s->ep + ep) << 16 | + musb_read_fifo(s->ep + ep) << 24 ); + default: + TRACE("unknown register 0x%02x", (int) addr); + return 0x00000000; + }; +} + +static void musb_writew(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + MUSBState *s = (MUSBState *) opaque; + int ep; + + switch (addr) { + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + musb_write_fifo(s->ep + ep, value & 0xff); + musb_write_fifo(s->ep + ep, (value >> 8 ) & 0xff); + musb_write_fifo(s->ep + ep, (value >> 16) & 0xff); + musb_write_fifo(s->ep + ep, (value >> 24) & 0xff); + break; + default: + TRACE("unknown register 0x%02x", (int) addr); + break; + }; +} + +CPUReadMemoryFunc * const musb_read[] = { + musb_readb, + musb_readh, + musb_readw, +}; + +CPUWriteMemoryFunc * const musb_write[] = { + musb_writeb, + musb_writeh, + musb_writew, +}; diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c new file mode 100644 index 0000000..dd79cef --- /dev/null +++ b/hw/usb/hcd-ohci.c @@ -0,0 +1,1898 @@ +/* + * QEMU USB OHCI Emulation + * Copyright (c) 2004 Gianni Tedesco + * Copyright (c) 2006 CodeSourcery + * Copyright (c) 2006 Openedhand Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * TODO: + * o Isochronous transfers + * o Allocate bandwidth in frames properly + * o Disable timers when nothing needs to be done, or remove timer usage + * all together. + * o Handle unrecoverable errors properly + * o BIOS work to boot from USB storage +*/ + +#include "hw/hw.h" +#include "qemu-timer.h" +#include "hw/usb.h" +#include "hw/pci.h" +#include "hw/usb-ohci.h" +#include "hw/sysbus.h" +#include "hw/qdev-addr.h" + +//#define DEBUG_OHCI +/* Dump packet contents. */ +//#define DEBUG_PACKET +//#define DEBUG_ISOCH +/* This causes frames to occur 1000x slower */ +//#define OHCI_TIME_WARP 1 + +#ifdef DEBUG_OHCI +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +/* Number of Downstream Ports on the root hub. */ + +#define OHCI_MAX_PORTS 15 + +static int64_t usb_frame_time; +static int64_t usb_bit_time; + +typedef struct OHCIPort { + USBPort port; + uint32_t ctrl; +} OHCIPort; + +typedef struct { + USBBus bus; + qemu_irq irq; + MemoryRegion mem; + int num_ports; + const char *name; + + QEMUTimer *eof_timer; + int64_t sof_time; + + /* OHCI state */ + /* Control partition */ + uint32_t ctl, status; + uint32_t intr_status; + uint32_t intr; + + /* memory pointer partition */ + uint32_t hcca; + uint32_t ctrl_head, ctrl_cur; + uint32_t bulk_head, bulk_cur; + uint32_t per_cur; + uint32_t done; + int done_count; + + /* Frame counter partition */ + uint32_t fsmps:15; + uint32_t fit:1; + uint32_t fi:14; + uint32_t frt:1; + uint16_t frame_number; + uint16_t padding; + uint32_t pstart; + uint32_t lst; + + /* Root Hub partition */ + uint32_t rhdesc_a, rhdesc_b; + uint32_t rhstatus; + OHCIPort rhport[OHCI_MAX_PORTS]; + + /* PXA27x Non-OHCI events */ + uint32_t hstatus; + uint32_t hmask; + uint32_t hreset; + uint32_t htest; + + /* SM501 local memory offset */ + target_phys_addr_t localmem_base; + + /* Active packets. */ + uint32_t old_ctl; + USBPacket usb_packet; + uint8_t usb_buf[8192]; + uint32_t async_td; + int async_complete; + +} OHCIState; + +/* Host Controller Communications Area */ +struct ohci_hcca { + uint32_t intr[32]; + uint16_t frame, pad; + uint32_t done; +}; + +static void ohci_bus_stop(OHCIState *ohci); +static void ohci_async_cancel_device(OHCIState *ohci, USBDevice *dev); + +/* Bitfields for the first word of an Endpoint Desciptor. */ +#define OHCI_ED_FA_SHIFT 0 +#define OHCI_ED_FA_MASK (0x7f<<OHCI_ED_FA_SHIFT) +#define OHCI_ED_EN_SHIFT 7 +#define OHCI_ED_EN_MASK (0xf<<OHCI_ED_EN_SHIFT) +#define OHCI_ED_D_SHIFT 11 +#define OHCI_ED_D_MASK (3<<OHCI_ED_D_SHIFT) +#define OHCI_ED_S (1<<13) +#define OHCI_ED_K (1<<14) +#define OHCI_ED_F (1<<15) +#define OHCI_ED_MPS_SHIFT 16 +#define OHCI_ED_MPS_MASK (0x7ff<<OHCI_ED_MPS_SHIFT) + +/* Flags in the head field of an Endpoint Desciptor. */ +#define OHCI_ED_H 1 +#define OHCI_ED_C 2 + +/* Bitfields for the first word of a Transfer Desciptor. */ +#define OHCI_TD_R (1<<18) +#define OHCI_TD_DP_SHIFT 19 +#define OHCI_TD_DP_MASK (3<<OHCI_TD_DP_SHIFT) +#define OHCI_TD_DI_SHIFT 21 +#define OHCI_TD_DI_MASK (7<<OHCI_TD_DI_SHIFT) +#define OHCI_TD_T0 (1<<24) +#define OHCI_TD_T1 (1<<25) +#define OHCI_TD_EC_SHIFT 26 +#define OHCI_TD_EC_MASK (3<<OHCI_TD_EC_SHIFT) +#define OHCI_TD_CC_SHIFT 28 +#define OHCI_TD_CC_MASK (0xf<<OHCI_TD_CC_SHIFT) + +/* Bitfields for the first word of an Isochronous Transfer Desciptor. */ +/* CC & DI - same as in the General Transfer Desciptor */ +#define OHCI_TD_SF_SHIFT 0 +#define OHCI_TD_SF_MASK (0xffff<<OHCI_TD_SF_SHIFT) +#define OHCI_TD_FC_SHIFT 24 +#define OHCI_TD_FC_MASK (7<<OHCI_TD_FC_SHIFT) + +/* Isochronous Transfer Desciptor - Offset / PacketStatusWord */ +#define OHCI_TD_PSW_CC_SHIFT 12 +#define OHCI_TD_PSW_CC_MASK (0xf<<OHCI_TD_PSW_CC_SHIFT) +#define OHCI_TD_PSW_SIZE_SHIFT 0 +#define OHCI_TD_PSW_SIZE_MASK (0xfff<<OHCI_TD_PSW_SIZE_SHIFT) + +#define OHCI_PAGE_MASK 0xfffff000 +#define OHCI_OFFSET_MASK 0xfff + +#define OHCI_DPTR_MASK 0xfffffff0 + +#define OHCI_BM(val, field) \ + (((val) & OHCI_##field##_MASK) >> OHCI_##field##_SHIFT) + +#define OHCI_SET_BM(val, field, newval) do { \ + val &= ~OHCI_##field##_MASK; \ + val |= ((newval) << OHCI_##field##_SHIFT) & OHCI_##field##_MASK; \ + } while(0) + +/* endpoint descriptor */ +struct ohci_ed { + uint32_t flags; + uint32_t tail; + uint32_t head; + uint32_t next; +}; + +/* General transfer descriptor */ +struct ohci_td { + uint32_t flags; + uint32_t cbp; + uint32_t next; + uint32_t be; +}; + +/* Isochronous transfer descriptor */ +struct ohci_iso_td { + uint32_t flags; + uint32_t bp; + uint32_t next; + uint32_t be; + uint16_t offset[8]; +}; + +#define USB_HZ 12000000 + +/* OHCI Local stuff */ +#define OHCI_CTL_CBSR ((1<<0)|(1<<1)) +#define OHCI_CTL_PLE (1<<2) +#define OHCI_CTL_IE (1<<3) +#define OHCI_CTL_CLE (1<<4) +#define OHCI_CTL_BLE (1<<5) +#define OHCI_CTL_HCFS ((1<<6)|(1<<7)) +#define OHCI_USB_RESET 0x00 +#define OHCI_USB_RESUME 0x40 +#define OHCI_USB_OPERATIONAL 0x80 +#define OHCI_USB_SUSPEND 0xc0 +#define OHCI_CTL_IR (1<<8) +#define OHCI_CTL_RWC (1<<9) +#define OHCI_CTL_RWE (1<<10) + +#define OHCI_STATUS_HCR (1<<0) +#define OHCI_STATUS_CLF (1<<1) +#define OHCI_STATUS_BLF (1<<2) +#define OHCI_STATUS_OCR (1<<3) +#define OHCI_STATUS_SOC ((1<<6)|(1<<7)) + +#define OHCI_INTR_SO (1<<0) /* Scheduling overrun */ +#define OHCI_INTR_WD (1<<1) /* HcDoneHead writeback */ +#define OHCI_INTR_SF (1<<2) /* Start of frame */ +#define OHCI_INTR_RD (1<<3) /* Resume detect */ +#define OHCI_INTR_UE (1<<4) /* Unrecoverable error */ +#define OHCI_INTR_FNO (1<<5) /* Frame number overflow */ +#define OHCI_INTR_RHSC (1<<6) /* Root hub status change */ +#define OHCI_INTR_OC (1<<30) /* Ownership change */ +#define OHCI_INTR_MIE (1<<31) /* Master Interrupt Enable */ + +#define OHCI_HCCA_SIZE 0x100 +#define OHCI_HCCA_MASK 0xffffff00 + +#define OHCI_EDPTR_MASK 0xfffffff0 + +#define OHCI_FMI_FI 0x00003fff +#define OHCI_FMI_FSMPS 0xffff0000 +#define OHCI_FMI_FIT 0x80000000 + +#define OHCI_FR_RT (1<<31) + +#define OHCI_LS_THRESH 0x628 + +#define OHCI_RHA_RW_MASK 0x00000000 /* Mask of supported features. */ +#define OHCI_RHA_PSM (1<<8) +#define OHCI_RHA_NPS (1<<9) +#define OHCI_RHA_DT (1<<10) +#define OHCI_RHA_OCPM (1<<11) +#define OHCI_RHA_NOCP (1<<12) +#define OHCI_RHA_POTPGT_MASK 0xff000000 + +#define OHCI_RHS_LPS (1<<0) +#define OHCI_RHS_OCI (1<<1) +#define OHCI_RHS_DRWE (1<<15) +#define OHCI_RHS_LPSC (1<<16) +#define OHCI_RHS_OCIC (1<<17) +#define OHCI_RHS_CRWE (1<<31) + +#define OHCI_PORT_CCS (1<<0) +#define OHCI_PORT_PES (1<<1) +#define OHCI_PORT_PSS (1<<2) +#define OHCI_PORT_POCI (1<<3) +#define OHCI_PORT_PRS (1<<4) +#define OHCI_PORT_PPS (1<<8) +#define OHCI_PORT_LSDA (1<<9) +#define OHCI_PORT_CSC (1<<16) +#define OHCI_PORT_PESC (1<<17) +#define OHCI_PORT_PSSC (1<<18) +#define OHCI_PORT_OCIC (1<<19) +#define OHCI_PORT_PRSC (1<<20) +#define OHCI_PORT_WTC (OHCI_PORT_CSC|OHCI_PORT_PESC|OHCI_PORT_PSSC \ + |OHCI_PORT_OCIC|OHCI_PORT_PRSC) + +#define OHCI_TD_DIR_SETUP 0x0 +#define OHCI_TD_DIR_OUT 0x1 +#define OHCI_TD_DIR_IN 0x2 +#define OHCI_TD_DIR_RESERVED 0x3 + +#define OHCI_CC_NOERROR 0x0 +#define OHCI_CC_CRC 0x1 +#define OHCI_CC_BITSTUFFING 0x2 +#define OHCI_CC_DATATOGGLEMISMATCH 0x3 +#define OHCI_CC_STALL 0x4 +#define OHCI_CC_DEVICENOTRESPONDING 0x5 +#define OHCI_CC_PIDCHECKFAILURE 0x6 +#define OHCI_CC_UNDEXPETEDPID 0x7 +#define OHCI_CC_DATAOVERRUN 0x8 +#define OHCI_CC_DATAUNDERRUN 0x9 +#define OHCI_CC_BUFFEROVERRUN 0xc +#define OHCI_CC_BUFFERUNDERRUN 0xd + +#define OHCI_HRESET_FSBIR (1 << 0) + +/* Update IRQ levels */ +static inline void ohci_intr_update(OHCIState *ohci) +{ + int level = 0; + + if ((ohci->intr & OHCI_INTR_MIE) && + (ohci->intr_status & ohci->intr)) + level = 1; + + qemu_set_irq(ohci->irq, level); +} + +/* Set an interrupt */ +static inline void ohci_set_interrupt(OHCIState *ohci, uint32_t intr) +{ + ohci->intr_status |= intr; + ohci_intr_update(ohci); +} + +/* Attach or detach a device on a root hub port. */ +static void ohci_attach(USBPort *port1) +{ + OHCIState *s = port1->opaque; + OHCIPort *port = &s->rhport[port1->index]; + uint32_t old_state = port->ctrl; + + /* set connect status */ + port->ctrl |= OHCI_PORT_CCS | OHCI_PORT_CSC; + + /* update speed */ + if (port->port.dev->speed == USB_SPEED_LOW) { + port->ctrl |= OHCI_PORT_LSDA; + } else { + port->ctrl &= ~OHCI_PORT_LSDA; + } + + /* notify of remote-wakeup */ + if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) { + ohci_set_interrupt(s, OHCI_INTR_RD); + } + + DPRINTF("usb-ohci: Attached port %d\n", port1->index); + + if (old_state != port->ctrl) { + ohci_set_interrupt(s, OHCI_INTR_RHSC); + } +} + +static void ohci_detach(USBPort *port1) +{ + OHCIState *s = port1->opaque; + OHCIPort *port = &s->rhport[port1->index]; + uint32_t old_state = port->ctrl; + + ohci_async_cancel_device(s, port1->dev); + + /* set connect status */ + if (port->ctrl & OHCI_PORT_CCS) { + port->ctrl &= ~OHCI_PORT_CCS; + port->ctrl |= OHCI_PORT_CSC; + } + /* disable port */ + if (port->ctrl & OHCI_PORT_PES) { + port->ctrl &= ~OHCI_PORT_PES; + port->ctrl |= OHCI_PORT_PESC; + } + DPRINTF("usb-ohci: Detached port %d\n", port1->index); + + if (old_state != port->ctrl) { + ohci_set_interrupt(s, OHCI_INTR_RHSC); + } +} + +static void ohci_wakeup(USBPort *port1) +{ + OHCIState *s = port1->opaque; + OHCIPort *port = &s->rhport[port1->index]; + uint32_t intr = 0; + if (port->ctrl & OHCI_PORT_PSS) { + DPRINTF("usb-ohci: port %d: wakeup\n", port1->index); + port->ctrl |= OHCI_PORT_PSSC; + port->ctrl &= ~OHCI_PORT_PSS; + intr = OHCI_INTR_RHSC; + } + /* Note that the controller can be suspended even if this port is not */ + if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) { + DPRINTF("usb-ohci: remote-wakeup: SUSPEND->RESUME\n"); + /* This is the one state transition the controller can do by itself */ + s->ctl &= ~OHCI_CTL_HCFS; + s->ctl |= OHCI_USB_RESUME; + /* In suspend mode only ResumeDetected is possible, not RHSC: + * see the OHCI spec 5.1.2.3. + */ + intr = OHCI_INTR_RD; + } + ohci_set_interrupt(s, intr); +} + +static void ohci_child_detach(USBPort *port1, USBDevice *child) +{ + OHCIState *s = port1->opaque; + + ohci_async_cancel_device(s, child); +} + +static USBDevice *ohci_find_device(OHCIState *ohci, uint8_t addr) +{ + USBDevice *dev; + int i; + + for (i = 0; i < ohci->num_ports; i++) { + if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) { + continue; + } + dev = usb_find_device(&ohci->rhport[i].port, addr); + if (dev != NULL) { + return dev; + } + } + return NULL; +} + +/* Reset the controller */ +static void ohci_reset(void *opaque) +{ + OHCIState *ohci = opaque; + OHCIPort *port; + int i; + + ohci_bus_stop(ohci); + ohci->ctl = 0; + ohci->old_ctl = 0; + ohci->status = 0; + ohci->intr_status = 0; + ohci->intr = OHCI_INTR_MIE; + + ohci->hcca = 0; + ohci->ctrl_head = ohci->ctrl_cur = 0; + ohci->bulk_head = ohci->bulk_cur = 0; + ohci->per_cur = 0; + ohci->done = 0; + ohci->done_count = 7; + + /* FSMPS is marked TBD in OCHI 1.0, what gives ffs? + * I took the value linux sets ... + */ + ohci->fsmps = 0x2778; + ohci->fi = 0x2edf; + ohci->fit = 0; + ohci->frt = 0; + ohci->frame_number = 0; + ohci->pstart = 0; + ohci->lst = OHCI_LS_THRESH; + + ohci->rhdesc_a = OHCI_RHA_NPS | ohci->num_ports; + ohci->rhdesc_b = 0x0; /* Impl. specific */ + ohci->rhstatus = 0; + + for (i = 0; i < ohci->num_ports; i++) + { + port = &ohci->rhport[i]; + port->ctrl = 0; + if (port->port.dev && port->port.dev->attached) { + usb_port_reset(&port->port); + } + } + if (ohci->async_td) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + DPRINTF("usb-ohci: Reset %s\n", ohci->name); +} + +/* Get an array of dwords from main memory */ +static inline int get_dwords(OHCIState *ohci, + uint32_t addr, uint32_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + cpu_physical_memory_read(addr, buf, sizeof(*buf)); + *buf = le32_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of dwords in to main memory */ +static inline int put_dwords(OHCIState *ohci, + uint32_t addr, uint32_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + uint32_t tmp = cpu_to_le32(*buf); + cpu_physical_memory_write(addr, &tmp, sizeof(tmp)); + } + + return 1; +} + +/* Get an array of words from main memory */ +static inline int get_words(OHCIState *ohci, + uint32_t addr, uint16_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + cpu_physical_memory_read(addr, buf, sizeof(*buf)); + *buf = le16_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of words in to main memory */ +static inline int put_words(OHCIState *ohci, + uint32_t addr, uint16_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + uint16_t tmp = cpu_to_le16(*buf); + cpu_physical_memory_write(addr, &tmp, sizeof(tmp)); + } + + return 1; +} + +static inline int ohci_read_ed(OHCIState *ohci, + uint32_t addr, struct ohci_ed *ed) +{ + return get_dwords(ohci, addr, (uint32_t *)ed, sizeof(*ed) >> 2); +} + +static inline int ohci_read_td(OHCIState *ohci, + uint32_t addr, struct ohci_td *td) +{ + return get_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2); +} + +static inline int ohci_read_iso_td(OHCIState *ohci, + uint32_t addr, struct ohci_iso_td *td) +{ + return (get_dwords(ohci, addr, (uint32_t *)td, 4) && + get_words(ohci, addr + 16, td->offset, 8)); +} + +static inline int ohci_read_hcca(OHCIState *ohci, + uint32_t addr, struct ohci_hcca *hcca) +{ + cpu_physical_memory_read(addr + ohci->localmem_base, hcca, sizeof(*hcca)); + return 1; +} + +static inline int ohci_put_ed(OHCIState *ohci, + uint32_t addr, struct ohci_ed *ed) +{ + return put_dwords(ohci, addr, (uint32_t *)ed, sizeof(*ed) >> 2); +} + +static inline int ohci_put_td(OHCIState *ohci, + uint32_t addr, struct ohci_td *td) +{ + return put_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2); +} + +static inline int ohci_put_iso_td(OHCIState *ohci, + uint32_t addr, struct ohci_iso_td *td) +{ + return (put_dwords(ohci, addr, (uint32_t *)td, 4) && + put_words(ohci, addr + 16, td->offset, 8)); +} + +static inline int ohci_put_hcca(OHCIState *ohci, + uint32_t addr, struct ohci_hcca *hcca) +{ + cpu_physical_memory_write(addr + ohci->localmem_base, hcca, sizeof(*hcca)); + return 1; +} + +/* Read/Write the contents of a TD from/to main memory. */ +static void ohci_copy_td(OHCIState *ohci, struct ohci_td *td, + uint8_t *buf, int len, int write) +{ + uint32_t ptr; + uint32_t n; + + ptr = td->cbp; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, n, write); + if (n == len) + return; + ptr = td->be & ~0xfffu; + buf += n; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, len - n, write); +} + +/* Read/Write the contents of an ISO TD from/to main memory. */ +static void ohci_copy_iso_td(OHCIState *ohci, + uint32_t start_addr, uint32_t end_addr, + uint8_t *buf, int len, int write) +{ + uint32_t ptr; + uint32_t n; + + ptr = start_addr; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, n, write); + if (n == len) + return; + ptr = end_addr & ~0xfffu; + buf += n; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, len - n, write); +} + +static void ohci_process_lists(OHCIState *ohci, int completion); + +static void ohci_async_complete_packet(USBPort *port, USBPacket *packet) +{ + OHCIState *ohci = container_of(packet, OHCIState, usb_packet); +#ifdef DEBUG_PACKET + DPRINTF("Async packet complete\n"); +#endif + ohci->async_complete = 1; + ohci_process_lists(ohci, 1); +} + +#define USUB(a, b) ((int16_t)((uint16_t)(a) - (uint16_t)(b))) + +static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, + int completion) +{ + int dir; + size_t len = 0; +#ifdef DEBUG_ISOCH + const char *str = NULL; +#endif + int pid; + int ret; + int i; + USBDevice *dev; + USBEndpoint *ep; + struct ohci_iso_td iso_td; + uint32_t addr; + uint16_t starting_frame; + int16_t relative_frame_number; + int frame_count; + uint32_t start_offset, next_offset, end_offset = 0; + uint32_t start_addr, end_addr; + + addr = ed->head & OHCI_DPTR_MASK; + + if (!ohci_read_iso_td(ohci, addr, &iso_td)) { + printf("usb-ohci: ISO_TD read error at %x\n", addr); + return 0; + } + + starting_frame = OHCI_BM(iso_td.flags, TD_SF); + frame_count = OHCI_BM(iso_td.flags, TD_FC); + relative_frame_number = USUB(ohci->frame_number, starting_frame); + +#ifdef DEBUG_ISOCH + printf("--- ISO_TD ED head 0x%.8x tailp 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "frame_number 0x%.8x starting_frame 0x%.8x\n" + "frame_count 0x%.8x relative %d\n" + "di 0x%.8x cc 0x%.8x\n", + ed->head & OHCI_DPTR_MASK, ed->tail & OHCI_DPTR_MASK, + iso_td.flags, iso_td.bp, iso_td.next, iso_td.be, + iso_td.offset[0], iso_td.offset[1], iso_td.offset[2], iso_td.offset[3], + iso_td.offset[4], iso_td.offset[5], iso_td.offset[6], iso_td.offset[7], + ohci->frame_number, starting_frame, + frame_count, relative_frame_number, + OHCI_BM(iso_td.flags, TD_DI), OHCI_BM(iso_td.flags, TD_CC)); +#endif + + if (relative_frame_number < 0) { + DPRINTF("usb-ohci: ISO_TD R=%d < 0\n", relative_frame_number); + return 1; + } else if (relative_frame_number > frame_count) { + /* ISO TD expired - retire the TD to the Done Queue and continue with + the next ISO TD of the same ED */ + DPRINTF("usb-ohci: ISO_TD R=%d > FC=%d\n", relative_frame_number, + frame_count); + OHCI_SET_BM(iso_td.flags, TD_CC, OHCI_CC_DATAOVERRUN); + ed->head &= ~OHCI_DPTR_MASK; + ed->head |= (iso_td.next & OHCI_DPTR_MASK); + iso_td.next = ohci->done; + ohci->done = addr; + i = OHCI_BM(iso_td.flags, TD_DI); + if (i < ohci->done_count) + ohci->done_count = i; + ohci_put_iso_td(ohci, addr, &iso_td); + return 0; + } + + dir = OHCI_BM(ed->flags, ED_D); + switch (dir) { + case OHCI_TD_DIR_IN: +#ifdef DEBUG_ISOCH + str = "in"; +#endif + pid = USB_TOKEN_IN; + break; + case OHCI_TD_DIR_OUT: +#ifdef DEBUG_ISOCH + str = "out"; +#endif + pid = USB_TOKEN_OUT; + break; + case OHCI_TD_DIR_SETUP: +#ifdef DEBUG_ISOCH + str = "setup"; +#endif + pid = USB_TOKEN_SETUP; + break; + default: + printf("usb-ohci: Bad direction %d\n", dir); + return 1; + } + + if (!iso_td.bp || !iso_td.be) { + printf("usb-ohci: ISO_TD bp 0x%.8x be 0x%.8x\n", iso_td.bp, iso_td.be); + return 1; + } + + start_offset = iso_td.offset[relative_frame_number]; + next_offset = iso_td.offset[relative_frame_number + 1]; + + if (!(OHCI_BM(start_offset, TD_PSW_CC) & 0xe) || + ((relative_frame_number < frame_count) && + !(OHCI_BM(next_offset, TD_PSW_CC) & 0xe))) { + printf("usb-ohci: ISO_TD cc != not accessed 0x%.8x 0x%.8x\n", + start_offset, next_offset); + return 1; + } + + if ((relative_frame_number < frame_count) && (start_offset > next_offset)) { + printf("usb-ohci: ISO_TD start_offset=0x%.8x > next_offset=0x%.8x\n", + start_offset, next_offset); + return 1; + } + + if ((start_offset & 0x1000) == 0) { + start_addr = (iso_td.bp & OHCI_PAGE_MASK) | + (start_offset & OHCI_OFFSET_MASK); + } else { + start_addr = (iso_td.be & OHCI_PAGE_MASK) | + (start_offset & OHCI_OFFSET_MASK); + } + + if (relative_frame_number < frame_count) { + end_offset = next_offset - 1; + if ((end_offset & 0x1000) == 0) { + end_addr = (iso_td.bp & OHCI_PAGE_MASK) | + (end_offset & OHCI_OFFSET_MASK); + } else { + end_addr = (iso_td.be & OHCI_PAGE_MASK) | + (end_offset & OHCI_OFFSET_MASK); + } + } else { + /* Last packet in the ISO TD */ + end_addr = iso_td.be; + } + + if ((start_addr & OHCI_PAGE_MASK) != (end_addr & OHCI_PAGE_MASK)) { + len = (end_addr & OHCI_OFFSET_MASK) + 0x1001 + - (start_addr & OHCI_OFFSET_MASK); + } else { + len = end_addr - start_addr + 1; + } + + if (len && dir != OHCI_TD_DIR_IN) { + ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, len, 0); + } + + if (completion) { + ret = ohci->usb_packet.result; + } else { + dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA)); + ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN)); + usb_packet_setup(&ohci->usb_packet, pid, ep); + usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, len); + ret = usb_handle_packet(dev, &ohci->usb_packet); + if (ret == USB_RET_ASYNC) { + return 1; + } + } + +#ifdef DEBUG_ISOCH + printf("so 0x%.8x eo 0x%.8x\nsa 0x%.8x ea 0x%.8x\ndir %s len %zu ret %d\n", + start_offset, end_offset, start_addr, end_addr, str, len, ret); +#endif + + /* Writeback */ + if (dir == OHCI_TD_DIR_IN && ret >= 0 && ret <= len) { + /* IN transfer succeeded */ + ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, ret, 1); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_NOERROR); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, ret); + } else if (dir == OHCI_TD_DIR_OUT && ret == len) { + /* OUT transfer succeeded */ + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_NOERROR); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, 0); + } else { + if (ret > (ssize_t) len) { + printf("usb-ohci: DataOverrun %d > %zu\n", ret, len); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_DATAOVERRUN); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, + len); + } else if (ret >= 0) { + printf("usb-ohci: DataUnderrun %d\n", ret); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_DATAUNDERRUN); + } else { + switch (ret) { + case USB_RET_IOERROR: + case USB_RET_NODEV: + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_DEVICENOTRESPONDING); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, + 0); + break; + case USB_RET_NAK: + case USB_RET_STALL: + printf("usb-ohci: got NAK/STALL %d\n", ret); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_STALL); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, + 0); + break; + default: + printf("usb-ohci: Bad device response %d\n", ret); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_UNDEXPETEDPID); + break; + } + } + } + + if (relative_frame_number == frame_count) { + /* Last data packet of ISO TD - retire the TD to the Done Queue */ + OHCI_SET_BM(iso_td.flags, TD_CC, OHCI_CC_NOERROR); + ed->head &= ~OHCI_DPTR_MASK; + ed->head |= (iso_td.next & OHCI_DPTR_MASK); + iso_td.next = ohci->done; + ohci->done = addr; + i = OHCI_BM(iso_td.flags, TD_DI); + if (i < ohci->done_count) + ohci->done_count = i; + } + ohci_put_iso_td(ohci, addr, &iso_td); + return 1; +} + +/* Service a transport descriptor. + Returns nonzero to terminate processing of this endpoint. */ + +static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) +{ + int dir; + size_t len = 0, pktlen = 0; +#ifdef DEBUG_PACKET + const char *str = NULL; +#endif + int pid; + int ret; + int i; + USBDevice *dev; + USBEndpoint *ep; + struct ohci_td td; + uint32_t addr; + int flag_r; + int completion; + + addr = ed->head & OHCI_DPTR_MASK; + /* See if this TD has already been submitted to the device. */ + completion = (addr == ohci->async_td); + if (completion && !ohci->async_complete) { +#ifdef DEBUG_PACKET + DPRINTF("Skipping async TD\n"); +#endif + return 1; + } + if (!ohci_read_td(ohci, addr, &td)) { + fprintf(stderr, "usb-ohci: TD read error at %x\n", addr); + return 0; + } + + dir = OHCI_BM(ed->flags, ED_D); + switch (dir) { + case OHCI_TD_DIR_OUT: + case OHCI_TD_DIR_IN: + /* Same value. */ + break; + default: + dir = OHCI_BM(td.flags, TD_DP); + break; + } + + switch (dir) { + case OHCI_TD_DIR_IN: +#ifdef DEBUG_PACKET + str = "in"; +#endif + pid = USB_TOKEN_IN; + break; + case OHCI_TD_DIR_OUT: +#ifdef DEBUG_PACKET + str = "out"; +#endif + pid = USB_TOKEN_OUT; + break; + case OHCI_TD_DIR_SETUP: +#ifdef DEBUG_PACKET + str = "setup"; +#endif + pid = USB_TOKEN_SETUP; + break; + default: + fprintf(stderr, "usb-ohci: Bad direction\n"); + return 1; + } + if (td.cbp && td.be) { + if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) { + len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff); + } else { + len = (td.be - td.cbp) + 1; + } + + pktlen = len; + if (len && dir != OHCI_TD_DIR_IN) { + /* The endpoint may not allow us to transfer it all now */ + pktlen = (ed->flags & OHCI_ED_MPS_MASK) >> OHCI_ED_MPS_SHIFT; + if (pktlen > len) { + pktlen = len; + } + if (!completion) { + ohci_copy_td(ohci, &td, ohci->usb_buf, pktlen, 0); + } + } + } + + flag_r = (td.flags & OHCI_TD_R) != 0; +#ifdef DEBUG_PACKET + DPRINTF(" TD @ 0x%.8x %" PRId64 " of %" PRId64 + " bytes %s r=%d cbp=0x%.8x be=0x%.8x\n", + addr, (int64_t)pktlen, (int64_t)len, str, flag_r, td.cbp, td.be); + + if (pktlen > 0 && dir != OHCI_TD_DIR_IN) { + DPRINTF(" data:"); + for (i = 0; i < pktlen; i++) { + printf(" %.2x", ohci->usb_buf[i]); + } + DPRINTF("\n"); + } +#endif + if (completion) { + ret = ohci->usb_packet.result; + ohci->async_td = 0; + ohci->async_complete = 0; + } else { + if (ohci->async_td) { + /* ??? The hardware should allow one active packet per + endpoint. We only allow one active packet per controller. + This should be sufficient as long as devices respond in a + timely manner. + */ +#ifdef DEBUG_PACKET + DPRINTF("Too many pending packets\n"); +#endif + return 1; + } + dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA)); + ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN)); + usb_packet_setup(&ohci->usb_packet, pid, ep); + usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, pktlen); + ret = usb_handle_packet(dev, &ohci->usb_packet); +#ifdef DEBUG_PACKET + DPRINTF("ret=%d\n", ret); +#endif + if (ret == USB_RET_ASYNC) { + ohci->async_td = addr; + return 1; + } + } + if (ret >= 0) { + if (dir == OHCI_TD_DIR_IN) { + ohci_copy_td(ohci, &td, ohci->usb_buf, ret, 1); +#ifdef DEBUG_PACKET + DPRINTF(" data:"); + for (i = 0; i < ret; i++) + printf(" %.2x", ohci->usb_buf[i]); + DPRINTF("\n"); +#endif + } else { + ret = pktlen; + } + } + + /* Writeback */ + if (ret == pktlen || (dir == OHCI_TD_DIR_IN && ret >= 0 && flag_r)) { + /* Transmission succeeded. */ + if (ret == len) { + td.cbp = 0; + } else { + if ((td.cbp & 0xfff) + ret > 0xfff) { + td.cbp = (td.be & ~0xfff) + ((td.cbp + ret) & 0xfff); + } else { + td.cbp += ret; + } + } + td.flags |= OHCI_TD_T1; + td.flags ^= OHCI_TD_T0; + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_NOERROR); + OHCI_SET_BM(td.flags, TD_EC, 0); + + if ((dir != OHCI_TD_DIR_IN) && (ret != len)) { + /* Partial packet transfer: TD not ready to retire yet */ + goto exit_no_retire; + } + + /* Setting ED_C is part of the TD retirement process */ + ed->head &= ~OHCI_ED_C; + if (td.flags & OHCI_TD_T0) + ed->head |= OHCI_ED_C; + } else { + if (ret >= 0) { + DPRINTF("usb-ohci: Underrun\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN); + } else { + switch (ret) { + case USB_RET_IOERROR: + case USB_RET_NODEV: + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING); + case USB_RET_NAK: + DPRINTF("usb-ohci: got NAK\n"); + return 1; + case USB_RET_STALL: + DPRINTF("usb-ohci: got STALL\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_STALL); + break; + case USB_RET_BABBLE: + DPRINTF("usb-ohci: got BABBLE\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAOVERRUN); + break; + default: + fprintf(stderr, "usb-ohci: Bad device response %d\n", ret); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_UNDEXPETEDPID); + OHCI_SET_BM(td.flags, TD_EC, 3); + break; + } + } + ed->head |= OHCI_ED_H; + } + + /* Retire this TD */ + ed->head &= ~OHCI_DPTR_MASK; + ed->head |= td.next & OHCI_DPTR_MASK; + td.next = ohci->done; + ohci->done = addr; + i = OHCI_BM(td.flags, TD_DI); + if (i < ohci->done_count) + ohci->done_count = i; +exit_no_retire: + ohci_put_td(ohci, addr, &td); + return OHCI_BM(td.flags, TD_CC) != OHCI_CC_NOERROR; +} + +/* Service an endpoint list. Returns nonzero if active TD were found. */ +static int ohci_service_ed_list(OHCIState *ohci, uint32_t head, int completion) +{ + struct ohci_ed ed; + uint32_t next_ed; + uint32_t cur; + int active; + + active = 0; + + if (head == 0) + return 0; + + for (cur = head; cur; cur = next_ed) { + if (!ohci_read_ed(ohci, cur, &ed)) { + fprintf(stderr, "usb-ohci: ED read error at %x\n", cur); + return 0; + } + + next_ed = ed.next & OHCI_DPTR_MASK; + + if ((ed.head & OHCI_ED_H) || (ed.flags & OHCI_ED_K)) { + uint32_t addr; + /* Cancel pending packets for ED that have been paused. */ + addr = ed.head & OHCI_DPTR_MASK; + if (ohci->async_td && addr == ohci->async_td) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + continue; + } + + while ((ed.head & OHCI_DPTR_MASK) != ed.tail) { +#ifdef DEBUG_PACKET + DPRINTF("ED @ 0x%.8x fa=%u en=%u d=%u s=%u k=%u f=%u mps=%u " + "h=%u c=%u\n head=0x%.8x tailp=0x%.8x next=0x%.8x\n", cur, + OHCI_BM(ed.flags, ED_FA), OHCI_BM(ed.flags, ED_EN), + OHCI_BM(ed.flags, ED_D), (ed.flags & OHCI_ED_S)!= 0, + (ed.flags & OHCI_ED_K) != 0, (ed.flags & OHCI_ED_F) != 0, + OHCI_BM(ed.flags, ED_MPS), (ed.head & OHCI_ED_H) != 0, + (ed.head & OHCI_ED_C) != 0, ed.head & OHCI_DPTR_MASK, + ed.tail & OHCI_DPTR_MASK, ed.next & OHCI_DPTR_MASK); +#endif + active = 1; + + if ((ed.flags & OHCI_ED_F) == 0) { + if (ohci_service_td(ohci, &ed)) + break; + } else { + /* Handle isochronous endpoints */ + if (ohci_service_iso_td(ohci, &ed, completion)) + break; + } + } + + ohci_put_ed(ohci, cur, &ed); + } + + return active; +} + +/* Generate a SOF event, and set a timer for EOF */ +static void ohci_sof(OHCIState *ohci) +{ + ohci->sof_time = qemu_get_clock_ns(vm_clock); + qemu_mod_timer(ohci->eof_timer, ohci->sof_time + usb_frame_time); + ohci_set_interrupt(ohci, OHCI_INTR_SF); +} + +/* Process Control and Bulk lists. */ +static void ohci_process_lists(OHCIState *ohci, int completion) +{ + if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) { + if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head) { + DPRINTF("usb-ohci: head %x, cur %x\n", + ohci->ctrl_head, ohci->ctrl_cur); + } + if (!ohci_service_ed_list(ohci, ohci->ctrl_head, completion)) { + ohci->ctrl_cur = 0; + ohci->status &= ~OHCI_STATUS_CLF; + } + } + + if ((ohci->ctl & OHCI_CTL_BLE) && (ohci->status & OHCI_STATUS_BLF)) { + if (!ohci_service_ed_list(ohci, ohci->bulk_head, completion)) { + ohci->bulk_cur = 0; + ohci->status &= ~OHCI_STATUS_BLF; + } + } +} + +/* Do frame processing on frame boundary */ +static void ohci_frame_boundary(void *opaque) +{ + OHCIState *ohci = opaque; + struct ohci_hcca hcca; + + ohci_read_hcca(ohci, ohci->hcca, &hcca); + + /* Process all the lists at the end of the frame */ + if (ohci->ctl & OHCI_CTL_PLE) { + int n; + + n = ohci->frame_number & 0x1f; + ohci_service_ed_list(ohci, le32_to_cpu(hcca.intr[n]), 0); + } + + /* Cancel all pending packets if either of the lists has been disabled. */ + if (ohci->async_td && + ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + ohci->old_ctl = ohci->ctl; + ohci_process_lists(ohci, 0); + + /* Frame boundary, so do EOF stuf here */ + ohci->frt = ohci->fit; + + /* Increment frame number and take care of endianness. */ + ohci->frame_number = (ohci->frame_number + 1) & 0xffff; + hcca.frame = cpu_to_le16(ohci->frame_number); + + if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) { + if (!ohci->done) + abort(); + if (ohci->intr & ohci->intr_status) + ohci->done |= 1; + hcca.done = cpu_to_le32(ohci->done); + ohci->done = 0; + ohci->done_count = 7; + ohci_set_interrupt(ohci, OHCI_INTR_WD); + } + + if (ohci->done_count != 7 && ohci->done_count != 0) + ohci->done_count--; + + /* Do SOF stuff here */ + ohci_sof(ohci); + + /* Writeback HCCA */ + ohci_put_hcca(ohci, ohci->hcca, &hcca); +} + +/* Start sending SOF tokens across the USB bus, lists are processed in + * next frame + */ +static int ohci_bus_start(OHCIState *ohci) +{ + ohci->eof_timer = qemu_new_timer_ns(vm_clock, + ohci_frame_boundary, + ohci); + + if (ohci->eof_timer == NULL) { + fprintf(stderr, "usb-ohci: %s: qemu_new_timer_ns failed\n", ohci->name); + /* TODO: Signal unrecoverable error */ + return 0; + } + + DPRINTF("usb-ohci: %s: USB Operational\n", ohci->name); + + ohci_sof(ohci); + + return 1; +} + +/* Stop sending SOF tokens on the bus */ +static void ohci_bus_stop(OHCIState *ohci) +{ + if (ohci->eof_timer) + qemu_del_timer(ohci->eof_timer); + ohci->eof_timer = NULL; +} + +/* Sets a flag in a port status register but only set it if the port is + * connected, if not set ConnectStatusChange flag. If flag is enabled + * return 1. + */ +static int ohci_port_set_if_connected(OHCIState *ohci, int i, uint32_t val) +{ + int ret = 1; + + /* writing a 0 has no effect */ + if (val == 0) + return 0; + + /* If CurrentConnectStatus is cleared we set + * ConnectStatusChange + */ + if (!(ohci->rhport[i].ctrl & OHCI_PORT_CCS)) { + ohci->rhport[i].ctrl |= OHCI_PORT_CSC; + if (ohci->rhstatus & OHCI_RHS_DRWE) { + /* TODO: CSC is a wakeup event */ + } + return 0; + } + + if (ohci->rhport[i].ctrl & val) + ret = 0; + + /* set the bit */ + ohci->rhport[i].ctrl |= val; + + return ret; +} + +/* Set the frame interval - frame interval toggle is manipulated by the hcd only */ +static void ohci_set_frame_interval(OHCIState *ohci, uint16_t val) +{ + val &= OHCI_FMI_FI; + + if (val != ohci->fi) { + DPRINTF("usb-ohci: %s: FrameInterval = 0x%x (%u)\n", + ohci->name, ohci->fi, ohci->fi); + } + + ohci->fi = val; +} + +static void ohci_port_power(OHCIState *ohci, int i, int p) +{ + if (p) { + ohci->rhport[i].ctrl |= OHCI_PORT_PPS; + } else { + ohci->rhport[i].ctrl &= ~(OHCI_PORT_PPS| + OHCI_PORT_CCS| + OHCI_PORT_PSS| + OHCI_PORT_PRS); + } +} + +/* Set HcControlRegister */ +static void ohci_set_ctl(OHCIState *ohci, uint32_t val) +{ + uint32_t old_state; + uint32_t new_state; + + old_state = ohci->ctl & OHCI_CTL_HCFS; + ohci->ctl = val; + new_state = ohci->ctl & OHCI_CTL_HCFS; + + /* no state change */ + if (old_state == new_state) + return; + + switch (new_state) { + case OHCI_USB_OPERATIONAL: + ohci_bus_start(ohci); + break; + case OHCI_USB_SUSPEND: + ohci_bus_stop(ohci); + DPRINTF("usb-ohci: %s: USB Suspended\n", ohci->name); + break; + case OHCI_USB_RESUME: + DPRINTF("usb-ohci: %s: USB Resume\n", ohci->name); + break; + case OHCI_USB_RESET: + ohci_reset(ohci); + DPRINTF("usb-ohci: %s: USB Reset\n", ohci->name); + break; + } +} + +static uint32_t ohci_get_frame_remaining(OHCIState *ohci) +{ + uint16_t fr; + int64_t tks; + + if ((ohci->ctl & OHCI_CTL_HCFS) != OHCI_USB_OPERATIONAL) + return (ohci->frt << 31); + + /* Being in USB operational state guarnatees sof_time was + * set already. + */ + tks = qemu_get_clock_ns(vm_clock) - ohci->sof_time; + + /* avoid muldiv if possible */ + if (tks >= usb_frame_time) + return (ohci->frt << 31); + + tks = muldiv64(1, tks, usb_bit_time); + fr = (uint16_t)(ohci->fi - tks); + + return (ohci->frt << 31) | fr; +} + + +/* Set root hub status */ +static void ohci_set_hub_status(OHCIState *ohci, uint32_t val) +{ + uint32_t old_state; + + old_state = ohci->rhstatus; + + /* write 1 to clear OCIC */ + if (val & OHCI_RHS_OCIC) + ohci->rhstatus &= ~OHCI_RHS_OCIC; + + if (val & OHCI_RHS_LPS) { + int i; + + for (i = 0; i < ohci->num_ports; i++) + ohci_port_power(ohci, i, 0); + DPRINTF("usb-ohci: powered down all ports\n"); + } + + if (val & OHCI_RHS_LPSC) { + int i; + + for (i = 0; i < ohci->num_ports; i++) + ohci_port_power(ohci, i, 1); + DPRINTF("usb-ohci: powered up all ports\n"); + } + + if (val & OHCI_RHS_DRWE) + ohci->rhstatus |= OHCI_RHS_DRWE; + + if (val & OHCI_RHS_CRWE) + ohci->rhstatus &= ~OHCI_RHS_DRWE; + + if (old_state != ohci->rhstatus) + ohci_set_interrupt(ohci, OHCI_INTR_RHSC); +} + +/* Set root hub port status */ +static void ohci_port_set_status(OHCIState *ohci, int portnum, uint32_t val) +{ + uint32_t old_state; + OHCIPort *port; + + port = &ohci->rhport[portnum]; + old_state = port->ctrl; + + /* Write to clear CSC, PESC, PSSC, OCIC, PRSC */ + if (val & OHCI_PORT_WTC) + port->ctrl &= ~(val & OHCI_PORT_WTC); + + if (val & OHCI_PORT_CCS) + port->ctrl &= ~OHCI_PORT_PES; + + ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PES); + + if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PSS)) { + DPRINTF("usb-ohci: port %d: SUSPEND\n", portnum); + } + + if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PRS)) { + DPRINTF("usb-ohci: port %d: RESET\n", portnum); + usb_device_reset(port->port.dev); + port->ctrl &= ~OHCI_PORT_PRS; + /* ??? Should this also set OHCI_PORT_PESC. */ + port->ctrl |= OHCI_PORT_PES | OHCI_PORT_PRSC; + } + + /* Invert order here to ensure in ambiguous case, device is + * powered up... + */ + if (val & OHCI_PORT_LSDA) + ohci_port_power(ohci, portnum, 0); + if (val & OHCI_PORT_PPS) + ohci_port_power(ohci, portnum, 1); + + if (old_state != port->ctrl) + ohci_set_interrupt(ohci, OHCI_INTR_RHSC); + + return; +} + +static uint64_t ohci_mem_read(void *opaque, + target_phys_addr_t addr, + unsigned size) +{ + OHCIState *ohci = opaque; + uint32_t retval; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ohci: Mis-aligned read\n"); + return 0xffffffff; + } else if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) { + /* HcRhPortStatus */ + retval = ohci->rhport[(addr - 0x54) >> 2].ctrl | OHCI_PORT_PPS; + } else { + switch (addr >> 2) { + case 0: /* HcRevision */ + retval = 0x10; + break; + + case 1: /* HcControl */ + retval = ohci->ctl; + break; + + case 2: /* HcCommandStatus */ + retval = ohci->status; + break; + + case 3: /* HcInterruptStatus */ + retval = ohci->intr_status; + break; + + case 4: /* HcInterruptEnable */ + case 5: /* HcInterruptDisable */ + retval = ohci->intr; + break; + + case 6: /* HcHCCA */ + retval = ohci->hcca; + break; + + case 7: /* HcPeriodCurrentED */ + retval = ohci->per_cur; + break; + + case 8: /* HcControlHeadED */ + retval = ohci->ctrl_head; + break; + + case 9: /* HcControlCurrentED */ + retval = ohci->ctrl_cur; + break; + + case 10: /* HcBulkHeadED */ + retval = ohci->bulk_head; + break; + + case 11: /* HcBulkCurrentED */ + retval = ohci->bulk_cur; + break; + + case 12: /* HcDoneHead */ + retval = ohci->done; + break; + + case 13: /* HcFmInterretval */ + retval = (ohci->fit << 31) | (ohci->fsmps << 16) | (ohci->fi); + break; + + case 14: /* HcFmRemaining */ + retval = ohci_get_frame_remaining(ohci); + break; + + case 15: /* HcFmNumber */ + retval = ohci->frame_number; + break; + + case 16: /* HcPeriodicStart */ + retval = ohci->pstart; + break; + + case 17: /* HcLSThreshold */ + retval = ohci->lst; + break; + + case 18: /* HcRhDescriptorA */ + retval = ohci->rhdesc_a; + break; + + case 19: /* HcRhDescriptorB */ + retval = ohci->rhdesc_b; + break; + + case 20: /* HcRhStatus */ + retval = ohci->rhstatus; + break; + + /* PXA27x specific registers */ + case 24: /* HcStatus */ + retval = ohci->hstatus & ohci->hmask; + break; + + case 25: /* HcHReset */ + retval = ohci->hreset; + break; + + case 26: /* HcHInterruptEnable */ + retval = ohci->hmask; + break; + + case 27: /* HcHInterruptTest */ + retval = ohci->htest; + break; + + default: + fprintf(stderr, "ohci_read: Bad offset %x\n", (int)addr); + retval = 0xffffffff; + } + } + + return retval; +} + +static void ohci_mem_write(void *opaque, + target_phys_addr_t addr, + uint64_t val, + unsigned size) +{ + OHCIState *ohci = opaque; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ohci: Mis-aligned write\n"); + return; + } + + if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) { + /* HcRhPortStatus */ + ohci_port_set_status(ohci, (addr - 0x54) >> 2, val); + return; + } + + switch (addr >> 2) { + case 1: /* HcControl */ + ohci_set_ctl(ohci, val); + break; + + case 2: /* HcCommandStatus */ + /* SOC is read-only */ + val = (val & ~OHCI_STATUS_SOC); + + /* Bits written as '0' remain unchanged in the register */ + ohci->status |= val; + + if (ohci->status & OHCI_STATUS_HCR) + ohci_reset(ohci); + break; + + case 3: /* HcInterruptStatus */ + ohci->intr_status &= ~val; + ohci_intr_update(ohci); + break; + + case 4: /* HcInterruptEnable */ + ohci->intr |= val; + ohci_intr_update(ohci); + break; + + case 5: /* HcInterruptDisable */ + ohci->intr &= ~val; + ohci_intr_update(ohci); + break; + + case 6: /* HcHCCA */ + ohci->hcca = val & OHCI_HCCA_MASK; + break; + + case 7: /* HcPeriodCurrentED */ + /* Ignore writes to this read-only register, Linux does them */ + break; + + case 8: /* HcControlHeadED */ + ohci->ctrl_head = val & OHCI_EDPTR_MASK; + break; + + case 9: /* HcControlCurrentED */ + ohci->ctrl_cur = val & OHCI_EDPTR_MASK; + break; + + case 10: /* HcBulkHeadED */ + ohci->bulk_head = val & OHCI_EDPTR_MASK; + break; + + case 11: /* HcBulkCurrentED */ + ohci->bulk_cur = val & OHCI_EDPTR_MASK; + break; + + case 13: /* HcFmInterval */ + ohci->fsmps = (val & OHCI_FMI_FSMPS) >> 16; + ohci->fit = (val & OHCI_FMI_FIT) >> 31; + ohci_set_frame_interval(ohci, val); + break; + + case 15: /* HcFmNumber */ + break; + + case 16: /* HcPeriodicStart */ + ohci->pstart = val & 0xffff; + break; + + case 17: /* HcLSThreshold */ + ohci->lst = val & 0xffff; + break; + + case 18: /* HcRhDescriptorA */ + ohci->rhdesc_a &= ~OHCI_RHA_RW_MASK; + ohci->rhdesc_a |= val & OHCI_RHA_RW_MASK; + break; + + case 19: /* HcRhDescriptorB */ + break; + + case 20: /* HcRhStatus */ + ohci_set_hub_status(ohci, val); + break; + + /* PXA27x specific registers */ + case 24: /* HcStatus */ + ohci->hstatus &= ~(val & ohci->hmask); + + case 25: /* HcHReset */ + ohci->hreset = val & ~OHCI_HRESET_FSBIR; + if (val & OHCI_HRESET_FSBIR) + ohci_reset(ohci); + break; + + case 26: /* HcHInterruptEnable */ + ohci->hmask = val; + break; + + case 27: /* HcHInterruptTest */ + ohci->htest = val; + break; + + default: + fprintf(stderr, "ohci_write: Bad offset %x\n", (int)addr); + break; + } +} + +static void ohci_async_cancel_device(OHCIState *ohci, USBDevice *dev) +{ + if (ohci->async_td && + usb_packet_is_inflight(&ohci->usb_packet) && + ohci->usb_packet.ep->dev == dev) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } +} + +static const MemoryRegionOps ohci_mem_ops = { + .read = ohci_mem_read, + .write = ohci_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static USBPortOps ohci_port_ops = { + .attach = ohci_attach, + .detach = ohci_detach, + .child_detach = ohci_child_detach, + .wakeup = ohci_wakeup, + .complete = ohci_async_complete_packet, +}; + +static USBBusOps ohci_bus_ops = { +}; + +static int usb_ohci_init(OHCIState *ohci, DeviceState *dev, + int num_ports, uint32_t localmem_base, + char *masterbus, uint32_t firstport) +{ + int i; + + if (usb_frame_time == 0) { +#ifdef OHCI_TIME_WARP + usb_frame_time = get_ticks_per_sec(); + usb_bit_time = muldiv64(1, get_ticks_per_sec(), USB_HZ/1000); +#else + usb_frame_time = muldiv64(1, get_ticks_per_sec(), 1000); + if (get_ticks_per_sec() >= USB_HZ) { + usb_bit_time = muldiv64(1, get_ticks_per_sec(), USB_HZ); + } else { + usb_bit_time = 1; + } +#endif + DPRINTF("usb-ohci: usb_bit_time=%" PRId64 " usb_frame_time=%" PRId64 "\n", + usb_frame_time, usb_bit_time); + } + + ohci->num_ports = num_ports; + if (masterbus) { + USBPort *ports[OHCI_MAX_PORTS]; + for(i = 0; i < num_ports; i++) { + ports[i] = &ohci->rhport[i].port; + } + if (usb_register_companion(masterbus, ports, num_ports, + firstport, ohci, &ohci_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL) != 0) { + return -1; + } + } else { + usb_bus_new(&ohci->bus, &ohci_bus_ops, dev); + for (i = 0; i < num_ports; i++) { + usb_register_port(&ohci->bus, &ohci->rhport[i].port, + ohci, i, &ohci_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); + } + } + + memory_region_init_io(&ohci->mem, &ohci_mem_ops, ohci, "ohci", 256); + ohci->localmem_base = localmem_base; + + ohci->name = object_get_typename(OBJECT(dev)); + usb_packet_init(&ohci->usb_packet); + + ohci->async_td = 0; + qemu_register_reset(ohci_reset, ohci); + + return 0; +} + +typedef struct { + PCIDevice pci_dev; + OHCIState state; + char *masterbus; + uint32_t num_ports; + uint32_t firstport; +} OHCIPCIState; + +static int usb_ohci_initfn_pci(struct PCIDevice *dev) +{ + OHCIPCIState *ohci = DO_UPCAST(OHCIPCIState, pci_dev, dev); + + ohci->pci_dev.config[PCI_CLASS_PROG] = 0x10; /* OHCI */ + ohci->pci_dev.config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ + + if (usb_ohci_init(&ohci->state, &dev->qdev, ohci->num_ports, 0, + ohci->masterbus, ohci->firstport) != 0) { + return -1; + } + ohci->state.irq = ohci->pci_dev.irq[0]; + + /* TODO: avoid cast below by using dev */ + pci_register_bar(&ohci->pci_dev, 0, 0, &ohci->state.mem); + return 0; +} + +void usb_ohci_init_pci(struct PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "pci-ohci"); +} + +typedef struct { + SysBusDevice busdev; + OHCIState ohci; + uint32_t num_ports; + target_phys_addr_t dma_offset; +} OHCISysBusState; + +static int ohci_init_pxa(SysBusDevice *dev) +{ + OHCISysBusState *s = FROM_SYSBUS(OHCISysBusState, dev); + + /* Cannot fail as we pass NULL for masterbus */ + usb_ohci_init(&s->ohci, &dev->qdev, s->num_ports, s->dma_offset, NULL, 0); + sysbus_init_irq(dev, &s->ohci.irq); + sysbus_init_mmio(dev, &s->ohci.mem); + + return 0; +} + +static Property ohci_pci_properties[] = { + DEFINE_PROP_STRING("masterbus", OHCIPCIState, masterbus), + DEFINE_PROP_UINT32("num-ports", OHCIPCIState, num_ports, 3), + DEFINE_PROP_UINT32("firstport", OHCIPCIState, firstport, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ohci_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_ohci_initfn_pci; + k->vendor_id = PCI_VENDOR_ID_APPLE; + k->device_id = PCI_DEVICE_ID_APPLE_IPID_USB; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->desc = "Apple USB Controller"; + dc->props = ohci_pci_properties; +} + +static TypeInfo ohci_pci_info = { + .name = "pci-ohci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(OHCIPCIState), + .class_init = ohci_pci_class_init, +}; + +static Property ohci_sysbus_properties[] = { + DEFINE_PROP_UINT32("num-ports", OHCISysBusState, num_ports, 3), + DEFINE_PROP_TADDR("dma-offset", OHCISysBusState, dma_offset, 3), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ohci_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass); + + sbc->init = ohci_init_pxa; + dc->desc = "OHCI USB Controller"; + dc->props = ohci_sysbus_properties; +} + +static TypeInfo ohci_sysbus_info = { + .name = "sysbus-ohci", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(OHCISysBusState), + .class_init = ohci_sysbus_class_init, +}; + +static void ohci_register_types(void) +{ + type_register_static(&ohci_pci_info); + type_register_static(&ohci_sysbus_info); +} + +type_init(ohci_register_types) diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c new file mode 100644 index 0000000..7c2e9b3 --- /dev/null +++ b/hw/usb/hcd-uhci.c @@ -0,0 +1,1408 @@ +/* + * USB UHCI controller emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * Copyright (c) 2008 Max Krasnyansky + * Magor rewrite of the UHCI data structures parser and frame processor + * Support for fully async operation and multiple outstanding transactions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "hw/usb.h" +#include "hw/pci.h" +#include "qemu-timer.h" +#include "hw/usb-uhci.h" +#include "iov.h" +#include "dma.h" + +//#define DEBUG +//#define DEBUG_DUMP_DATA + +#define UHCI_CMD_FGR (1 << 4) +#define UHCI_CMD_EGSM (1 << 3) +#define UHCI_CMD_GRESET (1 << 2) +#define UHCI_CMD_HCRESET (1 << 1) +#define UHCI_CMD_RS (1 << 0) + +#define UHCI_STS_HCHALTED (1 << 5) +#define UHCI_STS_HCPERR (1 << 4) +#define UHCI_STS_HSERR (1 << 3) +#define UHCI_STS_RD (1 << 2) +#define UHCI_STS_USBERR (1 << 1) +#define UHCI_STS_USBINT (1 << 0) + +#define TD_CTRL_SPD (1 << 29) +#define TD_CTRL_ERROR_SHIFT 27 +#define TD_CTRL_IOS (1 << 25) +#define TD_CTRL_IOC (1 << 24) +#define TD_CTRL_ACTIVE (1 << 23) +#define TD_CTRL_STALL (1 << 22) +#define TD_CTRL_BABBLE (1 << 20) +#define TD_CTRL_NAK (1 << 19) +#define TD_CTRL_TIMEOUT (1 << 18) + +#define UHCI_PORT_SUSPEND (1 << 12) +#define UHCI_PORT_RESET (1 << 9) +#define UHCI_PORT_LSDA (1 << 8) +#define UHCI_PORT_RD (1 << 6) +#define UHCI_PORT_ENC (1 << 3) +#define UHCI_PORT_EN (1 << 2) +#define UHCI_PORT_CSC (1 << 1) +#define UHCI_PORT_CCS (1 << 0) + +#define UHCI_PORT_READ_ONLY (0x1bb) +#define UHCI_PORT_WRITE_CLEAR (UHCI_PORT_CSC | UHCI_PORT_ENC) + +#define FRAME_TIMER_FREQ 1000 + +#define FRAME_MAX_LOOPS 256 + +#define NB_PORTS 2 + +#ifdef DEBUG +#define DPRINTF printf + +static const char *pid2str(int pid) +{ + switch (pid) { + case USB_TOKEN_SETUP: return "SETUP"; + case USB_TOKEN_IN: return "IN"; + case USB_TOKEN_OUT: return "OUT"; + } + return "?"; +} + +#else +#define DPRINTF(...) +#endif + +typedef struct UHCIState UHCIState; +typedef struct UHCIAsync UHCIAsync; +typedef struct UHCIQueue UHCIQueue; + +/* + * Pending async transaction. + * 'packet' must be the first field because completion + * handler does "(UHCIAsync *) pkt" cast. + */ + +struct UHCIAsync { + USBPacket packet; + QEMUSGList sgl; + UHCIQueue *queue; + QTAILQ_ENTRY(UHCIAsync) next; + uint32_t td; + uint8_t isoc; + uint8_t done; +}; + +struct UHCIQueue { + uint32_t token; + UHCIState *uhci; + QTAILQ_ENTRY(UHCIQueue) next; + QTAILQ_HEAD(, UHCIAsync) asyncs; + int8_t valid; +}; + +typedef struct UHCIPort { + USBPort port; + uint16_t ctrl; +} UHCIPort; + +struct UHCIState { + PCIDevice dev; + MemoryRegion io_bar; + USBBus bus; /* Note unused when we're a companion controller */ + uint16_t cmd; /* cmd register */ + uint16_t status; + uint16_t intr; /* interrupt enable register */ + uint16_t frnum; /* frame number */ + uint32_t fl_base_addr; /* frame list base address */ + uint8_t sof_timing; + uint8_t status2; /* bit 0 and 1 are used to generate UHCI_STS_USBINT */ + int64_t expire_time; + QEMUTimer *frame_timer; + UHCIPort ports[NB_PORTS]; + + /* Interrupts that should be raised at the end of the current frame. */ + uint32_t pending_int_mask; + + /* Active packets */ + QTAILQ_HEAD(, UHCIQueue) queues; + uint8_t num_ports_vmstate; + + /* Properties */ + char *masterbus; + uint32_t firstport; +}; + +typedef struct UHCI_TD { + uint32_t link; + uint32_t ctrl; /* see TD_CTRL_xxx */ + uint32_t token; + uint32_t buffer; +} UHCI_TD; + +typedef struct UHCI_QH { + uint32_t link; + uint32_t el_link; +} UHCI_QH; + +static inline int32_t uhci_queue_token(UHCI_TD *td) +{ + /* covers ep, dev, pid -> identifies the endpoint */ + return td->token & 0x7ffff; +} + +static UHCIQueue *uhci_queue_get(UHCIState *s, UHCI_TD *td) +{ + uint32_t token = uhci_queue_token(td); + UHCIQueue *queue; + + QTAILQ_FOREACH(queue, &s->queues, next) { + if (queue->token == token) { + return queue; + } + } + + queue = g_new0(UHCIQueue, 1); + queue->uhci = s; + queue->token = token; + QTAILQ_INIT(&queue->asyncs); + QTAILQ_INSERT_HEAD(&s->queues, queue, next); + return queue; +} + +static void uhci_queue_free(UHCIQueue *queue) +{ + UHCIState *s = queue->uhci; + + QTAILQ_REMOVE(&s->queues, queue, next); + g_free(queue); +} + +static UHCIAsync *uhci_async_alloc(UHCIQueue *queue) +{ + UHCIAsync *async = g_new0(UHCIAsync, 1); + + async->queue = queue; + usb_packet_init(&async->packet); + pci_dma_sglist_init(&async->sgl, &queue->uhci->dev, 1); + + return async; +} + +static void uhci_async_free(UHCIAsync *async) +{ + usb_packet_cleanup(&async->packet); + qemu_sglist_destroy(&async->sgl); + g_free(async); +} + +static void uhci_async_link(UHCIAsync *async) +{ + UHCIQueue *queue = async->queue; + QTAILQ_INSERT_TAIL(&queue->asyncs, async, next); +} + +static void uhci_async_unlink(UHCIAsync *async) +{ + UHCIQueue *queue = async->queue; + QTAILQ_REMOVE(&queue->asyncs, async, next); +} + +static void uhci_async_cancel(UHCIAsync *async) +{ + DPRINTF("uhci: cancel td 0x%x token 0x%x done %u\n", + async->td, async->token, async->done); + + if (!async->done) + usb_cancel_packet(&async->packet); + uhci_async_free(async); +} + +/* + * Mark all outstanding async packets as invalid. + * This is used for canceling them when TDs are removed by the HCD. + */ +static void uhci_async_validate_begin(UHCIState *s) +{ + UHCIQueue *queue; + + QTAILQ_FOREACH(queue, &s->queues, next) { + queue->valid--; + } +} + +/* + * Cancel async packets that are no longer valid + */ +static void uhci_async_validate_end(UHCIState *s) +{ + UHCIQueue *queue, *n; + UHCIAsync *async; + + QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) { + if (queue->valid > 0) { + continue; + } + while (!QTAILQ_EMPTY(&queue->asyncs)) { + async = QTAILQ_FIRST(&queue->asyncs); + uhci_async_unlink(async); + uhci_async_cancel(async); + } + uhci_queue_free(queue); + } +} + +static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev) +{ + UHCIQueue *queue; + UHCIAsync *curr, *n; + + QTAILQ_FOREACH(queue, &s->queues, next) { + QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) { + if (!usb_packet_is_inflight(&curr->packet) || + curr->packet.ep->dev != dev) { + continue; + } + uhci_async_unlink(curr); + uhci_async_cancel(curr); + } + } +} + +static void uhci_async_cancel_all(UHCIState *s) +{ + UHCIQueue *queue; + UHCIAsync *curr, *n; + + QTAILQ_FOREACH(queue, &s->queues, next) { + QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) { + uhci_async_unlink(curr); + uhci_async_cancel(curr); + } + } +} + +static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, UHCI_TD *td) +{ + uint32_t token = uhci_queue_token(td); + UHCIQueue *queue; + UHCIAsync *async; + + QTAILQ_FOREACH(queue, &s->queues, next) { + if (queue->token == token) { + break; + } + } + if (queue == NULL) { + return NULL; + } + + QTAILQ_FOREACH(async, &queue->asyncs, next) { + if (async->td == addr) { + return async; + } + } + + return NULL; +} + +static void uhci_update_irq(UHCIState *s) +{ + int level; + if (((s->status2 & 1) && (s->intr & (1 << 2))) || + ((s->status2 & 2) && (s->intr & (1 << 3))) || + ((s->status & UHCI_STS_USBERR) && (s->intr & (1 << 0))) || + ((s->status & UHCI_STS_RD) && (s->intr & (1 << 1))) || + (s->status & UHCI_STS_HSERR) || + (s->status & UHCI_STS_HCPERR)) { + level = 1; + } else { + level = 0; + } + qemu_set_irq(s->dev.irq[3], level); +} + +static void uhci_reset(void *opaque) +{ + UHCIState *s = opaque; + uint8_t *pci_conf; + int i; + UHCIPort *port; + + DPRINTF("uhci: full reset\n"); + + pci_conf = s->dev.config; + + pci_conf[0x6a] = 0x01; /* usb clock */ + pci_conf[0x6b] = 0x00; + s->cmd = 0; + s->status = 0; + s->status2 = 0; + s->intr = 0; + s->fl_base_addr = 0; + s->sof_timing = 64; + + for(i = 0; i < NB_PORTS; i++) { + port = &s->ports[i]; + port->ctrl = 0x0080; + if (port->port.dev && port->port.dev->attached) { + usb_port_reset(&port->port); + } + } + + uhci_async_cancel_all(s); +} + +static void uhci_pre_save(void *opaque) +{ + UHCIState *s = opaque; + + uhci_async_cancel_all(s); +} + +static const VMStateDescription vmstate_uhci_port = { + .name = "uhci port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT16(ctrl, UHCIPort), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_uhci = { + .name = "uhci", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = uhci_pre_save, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, UHCIState), + VMSTATE_UINT8_EQUAL(num_ports_vmstate, UHCIState), + VMSTATE_STRUCT_ARRAY(ports, UHCIState, NB_PORTS, 1, + vmstate_uhci_port, UHCIPort), + VMSTATE_UINT16(cmd, UHCIState), + VMSTATE_UINT16(status, UHCIState), + VMSTATE_UINT16(intr, UHCIState), + VMSTATE_UINT16(frnum, UHCIState), + VMSTATE_UINT32(fl_base_addr, UHCIState), + VMSTATE_UINT8(sof_timing, UHCIState), + VMSTATE_UINT8(status2, UHCIState), + VMSTATE_TIMER(frame_timer, UHCIState), + VMSTATE_INT64_V(expire_time, UHCIState, 2), + VMSTATE_END_OF_LIST() + } +}; + +static void uhci_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; + switch(addr) { + case 0x0c: + s->sof_timing = val; + break; + } +} + +static uint32_t uhci_ioport_readb(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x0c: + val = s->sof_timing; + break; + default: + val = 0xff; + break; + } + return val; +} + +static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; + DPRINTF("uhci: writew port=0x%04x val=0x%04x\n", addr, val); + + switch(addr) { + case 0x00: + if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) { + /* start frame processing */ + s->expire_time = qemu_get_clock_ns(vm_clock) + + (get_ticks_per_sec() / FRAME_TIMER_FREQ); + qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock)); + s->status &= ~UHCI_STS_HCHALTED; + } else if (!(val & UHCI_CMD_RS)) { + s->status |= UHCI_STS_HCHALTED; + } + if (val & UHCI_CMD_GRESET) { + UHCIPort *port; + int i; + + /* send reset on the USB bus */ + for(i = 0; i < NB_PORTS; i++) { + port = &s->ports[i]; + usb_device_reset(port->port.dev); + } + uhci_reset(s); + return; + } + if (val & UHCI_CMD_HCRESET) { + uhci_reset(s); + return; + } + s->cmd = val; + break; + case 0x02: + s->status &= ~val; + /* XXX: the chip spec is not coherent, so we add a hidden + register to distinguish between IOC and SPD */ + if (val & UHCI_STS_USBINT) + s->status2 = 0; + uhci_update_irq(s); + break; + case 0x04: + s->intr = val; + uhci_update_irq(s); + break; + case 0x06: + if (s->status & UHCI_STS_HCHALTED) + s->frnum = val & 0x7ff; + break; + case 0x10 ... 0x1f: + { + UHCIPort *port; + USBDevice *dev; + int n; + + n = (addr >> 1) & 7; + if (n >= NB_PORTS) + return; + port = &s->ports[n]; + dev = port->port.dev; + if (dev && dev->attached) { + /* port reset */ + if ( (val & UHCI_PORT_RESET) && + !(port->ctrl & UHCI_PORT_RESET) ) { + usb_device_reset(dev); + } + } + port->ctrl &= UHCI_PORT_READ_ONLY; + port->ctrl |= (val & ~UHCI_PORT_READ_ONLY); + /* some bits are reset when a '1' is written to them */ + port->ctrl &= ~(val & UHCI_PORT_WRITE_CLEAR); + } + break; + } +} + +static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x00: + val = s->cmd; + break; + case 0x02: + val = s->status; + break; + case 0x04: + val = s->intr; + break; + case 0x06: + val = s->frnum; + break; + case 0x10 ... 0x1f: + { + UHCIPort *port; + int n; + n = (addr >> 1) & 7; + if (n >= NB_PORTS) + goto read_default; + port = &s->ports[n]; + val = port->ctrl; + } + break; + default: + read_default: + val = 0xff7f; /* disabled port */ + break; + } + + DPRINTF("uhci: readw port=0x%04x val=0x%04x\n", addr, val); + + return val; +} + +static void uhci_ioport_writel(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; + DPRINTF("uhci: writel port=0x%04x val=0x%08x\n", addr, val); + + switch(addr) { + case 0x08: + s->fl_base_addr = val & ~0xfff; + break; + } +} + +static uint32_t uhci_ioport_readl(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x08: + val = s->fl_base_addr; + break; + default: + val = 0xffffffff; + break; + } + return val; +} + +/* signal resume if controller suspended */ +static void uhci_resume (void *opaque) +{ + UHCIState *s = (UHCIState *)opaque; + + if (!s) + return; + + if (s->cmd & UHCI_CMD_EGSM) { + s->cmd |= UHCI_CMD_FGR; + s->status |= UHCI_STS_RD; + uhci_update_irq(s); + } +} + +static void uhci_attach(USBPort *port1) +{ + UHCIState *s = port1->opaque; + UHCIPort *port = &s->ports[port1->index]; + + /* set connect status */ + port->ctrl |= UHCI_PORT_CCS | UHCI_PORT_CSC; + + /* update speed */ + if (port->port.dev->speed == USB_SPEED_LOW) { + port->ctrl |= UHCI_PORT_LSDA; + } else { + port->ctrl &= ~UHCI_PORT_LSDA; + } + + uhci_resume(s); +} + +static void uhci_detach(USBPort *port1) +{ + UHCIState *s = port1->opaque; + UHCIPort *port = &s->ports[port1->index]; + + uhci_async_cancel_device(s, port1->dev); + + /* set connect status */ + if (port->ctrl & UHCI_PORT_CCS) { + port->ctrl &= ~UHCI_PORT_CCS; + port->ctrl |= UHCI_PORT_CSC; + } + /* disable port */ + if (port->ctrl & UHCI_PORT_EN) { + port->ctrl &= ~UHCI_PORT_EN; + port->ctrl |= UHCI_PORT_ENC; + } + + uhci_resume(s); +} + +static void uhci_child_detach(USBPort *port1, USBDevice *child) +{ + UHCIState *s = port1->opaque; + + uhci_async_cancel_device(s, child); +} + +static void uhci_wakeup(USBPort *port1) +{ + UHCIState *s = port1->opaque; + UHCIPort *port = &s->ports[port1->index]; + + if (port->ctrl & UHCI_PORT_SUSPEND && !(port->ctrl & UHCI_PORT_RD)) { + port->ctrl |= UHCI_PORT_RD; + uhci_resume(s); + } +} + +static USBDevice *uhci_find_device(UHCIState *s, uint8_t addr) +{ + USBDevice *dev; + int i; + + for (i = 0; i < NB_PORTS; i++) { + UHCIPort *port = &s->ports[i]; + if (!(port->ctrl & UHCI_PORT_EN)) { + continue; + } + dev = usb_find_device(&port->port, addr); + if (dev != NULL) { + return dev; + } + } + return NULL; +} + +static void uhci_async_complete(USBPort *port, USBPacket *packet); +static void uhci_process_frame(UHCIState *s); + +/* return -1 if fatal error (frame must be stopped) + 0 if TD successful + 1 if TD unsuccessful or inactive +*/ +static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_t *int_mask) +{ + int len = 0, max_len, err, ret; + uint8_t pid; + + max_len = ((td->token >> 21) + 1) & 0x7ff; + pid = td->token & 0xff; + + ret = async->packet.result; + + if (td->ctrl & TD_CTRL_IOS) + td->ctrl &= ~TD_CTRL_ACTIVE; + + if (ret < 0) + goto out; + + len = async->packet.result; + td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff); + + /* The NAK bit may have been set by a previous frame, so clear it + here. The docs are somewhat unclear, but win2k relies on this + behavior. */ + td->ctrl &= ~(TD_CTRL_ACTIVE | TD_CTRL_NAK); + if (td->ctrl & TD_CTRL_IOC) + *int_mask |= 0x01; + + if (pid == USB_TOKEN_IN) { + if (len > max_len) { + ret = USB_RET_BABBLE; + goto out; + } + + if ((td->ctrl & TD_CTRL_SPD) && len < max_len) { + *int_mask |= 0x02; + /* short packet: do not update QH */ + DPRINTF("uhci: short packet. td 0x%x token 0x%x\n", async->td, async->token); + return 1; + } + } + + /* success */ + return 0; + +out: + switch(ret) { + case USB_RET_STALL: + td->ctrl |= TD_CTRL_STALL; + td->ctrl &= ~TD_CTRL_ACTIVE; + s->status |= UHCI_STS_USBERR; + if (td->ctrl & TD_CTRL_IOC) { + *int_mask |= 0x01; + } + uhci_update_irq(s); + return 1; + + case USB_RET_BABBLE: + td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; + td->ctrl &= ~TD_CTRL_ACTIVE; + s->status |= UHCI_STS_USBERR; + if (td->ctrl & TD_CTRL_IOC) { + *int_mask |= 0x01; + } + uhci_update_irq(s); + /* frame interrupted */ + return -1; + + case USB_RET_NAK: + td->ctrl |= TD_CTRL_NAK; + if (pid == USB_TOKEN_SETUP) + break; + return 1; + + case USB_RET_IOERROR: + case USB_RET_NODEV: + default: + break; + } + + /* Retry the TD if error count is not zero */ + + td->ctrl |= TD_CTRL_TIMEOUT; + err = (td->ctrl >> TD_CTRL_ERROR_SHIFT) & 3; + if (err != 0) { + err--; + if (err == 0) { + td->ctrl &= ~TD_CTRL_ACTIVE; + s->status |= UHCI_STS_USBERR; + if (td->ctrl & TD_CTRL_IOC) + *int_mask |= 0x01; + uhci_update_irq(s); + } + } + td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) | + (err << TD_CTRL_ERROR_SHIFT); + return 1; +} + +static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *int_mask) +{ + UHCIAsync *async; + int len = 0, max_len; + uint8_t pid; + USBDevice *dev; + USBEndpoint *ep; + + /* Is active ? */ + if (!(td->ctrl & TD_CTRL_ACTIVE)) + return 1; + + async = uhci_async_find_td(s, addr, td); + if (async) { + /* Already submitted */ + async->queue->valid = 32; + + if (!async->done) + return 1; + + uhci_async_unlink(async); + goto done; + } + + /* Allocate new packet */ + async = uhci_async_alloc(uhci_queue_get(s, td)); + if (!async) + return 1; + + /* valid needs to be large enough to handle 10 frame delay + * for initial isochronous requests + */ + async->queue->valid = 32; + async->td = addr; + async->isoc = td->ctrl & TD_CTRL_IOS; + + max_len = ((td->token >> 21) + 1) & 0x7ff; + pid = td->token & 0xff; + + dev = uhci_find_device(s, (td->token >> 8) & 0x7f); + ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf); + usb_packet_setup(&async->packet, pid, ep); + qemu_sglist_add(&async->sgl, td->buffer, max_len); + usb_packet_map(&async->packet, &async->sgl); + + switch(pid) { + case USB_TOKEN_OUT: + case USB_TOKEN_SETUP: + len = usb_handle_packet(dev, &async->packet); + if (len >= 0) + len = max_len; + break; + + case USB_TOKEN_IN: + len = usb_handle_packet(dev, &async->packet); + break; + + default: + /* invalid pid : frame interrupted */ + uhci_async_free(async); + s->status |= UHCI_STS_HCPERR; + uhci_update_irq(s); + return -1; + } + + if (len == USB_RET_ASYNC) { + uhci_async_link(async); + return 2; + } + + async->packet.result = len; + +done: + len = uhci_complete_td(s, td, async, int_mask); + usb_packet_unmap(&async->packet); + uhci_async_free(async); + return len; +} + +static void uhci_async_complete(USBPort *port, USBPacket *packet) +{ + UHCIAsync *async = container_of(packet, UHCIAsync, packet); + UHCIState *s = async->queue->uhci; + + DPRINTF("uhci: async complete. td 0x%x token 0x%x\n", async->td, async->token); + + if (async->isoc) { + UHCI_TD td; + uint32_t link = async->td; + uint32_t int_mask = 0, val; + + pci_dma_read(&s->dev, link & ~0xf, &td, sizeof(td)); + le32_to_cpus(&td.link); + le32_to_cpus(&td.ctrl); + le32_to_cpus(&td.token); + le32_to_cpus(&td.buffer); + + uhci_async_unlink(async); + uhci_complete_td(s, &td, async, &int_mask); + s->pending_int_mask |= int_mask; + + /* update the status bits of the TD */ + val = cpu_to_le32(td.ctrl); + pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val)); + uhci_async_free(async); + } else { + async->done = 1; + uhci_process_frame(s); + } +} + +static int is_valid(uint32_t link) +{ + return (link & 1) == 0; +} + +static int is_qh(uint32_t link) +{ + return (link & 2) != 0; +} + +static int depth_first(uint32_t link) +{ + return (link & 4) != 0; +} + +/* QH DB used for detecting QH loops */ +#define UHCI_MAX_QUEUES 128 +typedef struct { + uint32_t addr[UHCI_MAX_QUEUES]; + int count; +} QhDb; + +static void qhdb_reset(QhDb *db) +{ + db->count = 0; +} + +/* Add QH to DB. Returns 1 if already present or DB is full. */ +static int qhdb_insert(QhDb *db, uint32_t addr) +{ + int i; + for (i = 0; i < db->count; i++) + if (db->addr[i] == addr) + return 1; + + if (db->count >= UHCI_MAX_QUEUES) + return 1; + + db->addr[db->count++] = addr; + return 0; +} + +static void uhci_fill_queue(UHCIState *s, UHCI_TD *td) +{ + uint32_t int_mask = 0; + uint32_t plink = td->link; + uint32_t token = uhci_queue_token(td); + UHCI_TD ptd; + int ret; + + while (is_valid(plink)) { + pci_dma_read(&s->dev, plink & ~0xf, &ptd, sizeof(ptd)); + le32_to_cpus(&ptd.link); + le32_to_cpus(&ptd.ctrl); + le32_to_cpus(&ptd.token); + le32_to_cpus(&ptd.buffer); + if (!(ptd.ctrl & TD_CTRL_ACTIVE)) { + break; + } + if (uhci_queue_token(&ptd) != token) { + break; + } + ret = uhci_handle_td(s, plink, &ptd, &int_mask); + assert(ret == 2); /* got USB_RET_ASYNC */ + assert(int_mask == 0); + plink = ptd.link; + } +} + +static void uhci_process_frame(UHCIState *s) +{ + uint32_t frame_addr, link, old_td_ctrl, val, int_mask; + uint32_t curr_qh, td_count = 0, bytes_count = 0; + int cnt, ret; + UHCI_TD td; + UHCI_QH qh; + QhDb qhdb; + + frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2); + + DPRINTF("uhci: processing frame %d addr 0x%x\n" , s->frnum, frame_addr); + + pci_dma_read(&s->dev, frame_addr, &link, 4); + le32_to_cpus(&link); + + int_mask = 0; + curr_qh = 0; + + qhdb_reset(&qhdb); + + for (cnt = FRAME_MAX_LOOPS; is_valid(link) && cnt; cnt--) { + if (is_qh(link)) { + /* QH */ + + if (qhdb_insert(&qhdb, link)) { + /* + * We're going in circles. Which is not a bug because + * HCD is allowed to do that as part of the BW management. + * + * Stop processing here if + * (a) no transaction has been done since we've been + * here last time, or + * (b) we've reached the usb 1.1 bandwidth, which is + * 1280 bytes/frame. + */ + DPRINTF("uhci: detected loop. qh 0x%x\n", link); + if (td_count == 0) { + DPRINTF("uhci: no transaction last round, stop\n"); + break; + } else if (bytes_count >= 1280) { + DPRINTF("uhci: bandwidth limit reached, stop\n"); + break; + } else { + td_count = 0; + qhdb_reset(&qhdb); + qhdb_insert(&qhdb, link); + } + } + + pci_dma_read(&s->dev, link & ~0xf, &qh, sizeof(qh)); + le32_to_cpus(&qh.link); + le32_to_cpus(&qh.el_link); + + DPRINTF("uhci: QH 0x%x load. link 0x%x elink 0x%x\n", + link, qh.link, qh.el_link); + + if (!is_valid(qh.el_link)) { + /* QH w/o elements */ + curr_qh = 0; + link = qh.link; + } else { + /* QH with elements */ + curr_qh = link; + link = qh.el_link; + } + continue; + } + + /* TD */ + pci_dma_read(&s->dev, link & ~0xf, &td, sizeof(td)); + le32_to_cpus(&td.link); + le32_to_cpus(&td.ctrl); + le32_to_cpus(&td.token); + le32_to_cpus(&td.buffer); + + DPRINTF("uhci: TD 0x%x load. link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", + link, td.link, td.ctrl, td.token, curr_qh); + + old_td_ctrl = td.ctrl; + ret = uhci_handle_td(s, link, &td, &int_mask); + if (old_td_ctrl != td.ctrl) { + /* update the status bits of the TD */ + val = cpu_to_le32(td.ctrl); + pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val)); + } + + switch (ret) { + case -1: /* interrupted frame */ + goto out; + + case 1: /* goto next queue */ + DPRINTF("uhci: TD 0x%x skip. " + "link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", + link, td.link, td.ctrl, td.token, curr_qh); + link = curr_qh ? qh.link : td.link; + continue; + + case 2: /* got USB_RET_ASYNC */ + DPRINTF("uhci: TD 0x%x async. " + "link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", + link, td.link, td.ctrl, td.token, curr_qh); + if (is_valid(td.link)) { + uhci_fill_queue(s, &td); + } + link = curr_qh ? qh.link : td.link; + continue; + + case 0: /* completed TD */ + DPRINTF("uhci: TD 0x%x done. " + "link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", + link, td.link, td.ctrl, td.token, curr_qh); + + link = td.link; + td_count++; + bytes_count += (td.ctrl & 0x7ff) + 1; + + if (curr_qh) { + /* update QH element link */ + qh.el_link = link; + val = cpu_to_le32(qh.el_link); + pci_dma_write(&s->dev, (curr_qh & ~0xf) + 4, &val, sizeof(val)); + + if (!depth_first(link)) { + /* done with this QH */ + + DPRINTF("uhci: QH 0x%x done. link 0x%x elink 0x%x\n", + curr_qh, qh.link, qh.el_link); + + curr_qh = 0; + link = qh.link; + } + } + break; + + default: + assert(!"unknown return code"); + } + + /* go to the next entry */ + } + +out: + s->pending_int_mask |= int_mask; +} + +static void uhci_frame_timer(void *opaque) +{ + UHCIState *s = opaque; + + /* prepare the timer for the next frame */ + s->expire_time += (get_ticks_per_sec() / FRAME_TIMER_FREQ); + + if (!(s->cmd & UHCI_CMD_RS)) { + /* Full stop */ + qemu_del_timer(s->frame_timer); + /* set hchalted bit in status - UHCI11D 2.1.2 */ + s->status |= UHCI_STS_HCHALTED; + + DPRINTF("uhci: halted\n"); + return; + } + + /* Complete the previous frame */ + if (s->pending_int_mask) { + s->status2 |= s->pending_int_mask; + s->status |= UHCI_STS_USBINT; + uhci_update_irq(s); + } + s->pending_int_mask = 0; + + /* Start new frame */ + s->frnum = (s->frnum + 1) & 0x7ff; + + DPRINTF("uhci: new frame #%u\n" , s->frnum); + + uhci_async_validate_begin(s); + + uhci_process_frame(s); + + uhci_async_validate_end(s); + + qemu_mod_timer(s->frame_timer, s->expire_time); +} + +static const MemoryRegionPortio uhci_portio[] = { + { 0, 32, 2, .write = uhci_ioport_writew, }, + { 0, 32, 2, .read = uhci_ioport_readw, }, + { 0, 32, 4, .write = uhci_ioport_writel, }, + { 0, 32, 4, .read = uhci_ioport_readl, }, + { 0, 32, 1, .write = uhci_ioport_writeb, }, + { 0, 32, 1, .read = uhci_ioport_readb, }, + PORTIO_END_OF_LIST() +}; + +static const MemoryRegionOps uhci_ioport_ops = { + .old_portio = uhci_portio, +}; + +static USBPortOps uhci_port_ops = { + .attach = uhci_attach, + .detach = uhci_detach, + .child_detach = uhci_child_detach, + .wakeup = uhci_wakeup, + .complete = uhci_async_complete, +}; + +static USBBusOps uhci_bus_ops = { +}; + +static int usb_uhci_common_initfn(PCIDevice *dev) +{ + UHCIState *s = DO_UPCAST(UHCIState, dev, dev); + uint8_t *pci_conf = s->dev.config; + int i; + + pci_conf[PCI_CLASS_PROG] = 0x00; + /* TODO: reset value should be 0. */ + pci_conf[PCI_INTERRUPT_PIN] = 4; /* interrupt pin D */ + pci_conf[USB_SBRN] = USB_RELEASE_1; // release number + + if (s->masterbus) { + USBPort *ports[NB_PORTS]; + for(i = 0; i < NB_PORTS; i++) { + ports[i] = &s->ports[i].port; + } + if (usb_register_companion(s->masterbus, ports, NB_PORTS, + s->firstport, s, &uhci_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL) != 0) { + return -1; + } + } else { + usb_bus_new(&s->bus, &uhci_bus_ops, &s->dev.qdev); + for (i = 0; i < NB_PORTS; i++) { + usb_register_port(&s->bus, &s->ports[i].port, s, i, &uhci_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); + } + } + s->frame_timer = qemu_new_timer_ns(vm_clock, uhci_frame_timer, s); + s->num_ports_vmstate = NB_PORTS; + QTAILQ_INIT(&s->queues); + + qemu_register_reset(uhci_reset, s); + + memory_region_init_io(&s->io_bar, &uhci_ioport_ops, s, "uhci", 0x20); + /* Use region 4 for consistency with real hardware. BSD guests seem + to rely on this. */ + pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar); + + return 0; +} + +static int usb_uhci_vt82c686b_initfn(PCIDevice *dev) +{ + UHCIState *s = DO_UPCAST(UHCIState, dev, dev); + uint8_t *pci_conf = s->dev.config; + + /* USB misc control 1/2 */ + pci_set_long(pci_conf + 0x40,0x00001000); + /* PM capability */ + pci_set_long(pci_conf + 0x80,0x00020001); + /* USB legacy support */ + pci_set_long(pci_conf + 0xc0,0x00002000); + + return usb_uhci_common_initfn(dev); +} + +static int usb_uhci_exit(PCIDevice *dev) +{ + UHCIState *s = DO_UPCAST(UHCIState, dev, dev); + + memory_region_destroy(&s->io_bar); + return 0; +} + +static Property uhci_properties[] = { + DEFINE_PROP_STRING("masterbus", UHCIState, masterbus), + DEFINE_PROP_UINT32("firstport", UHCIState, firstport, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void piix3_uhci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_uhci_common_initfn; + k->exit = usb_uhci_exit; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82371SB_2; + k->revision = 0x01; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_uhci; + dc->props = uhci_properties; +} + +static TypeInfo piix3_uhci_info = { + .name = "piix3-usb-uhci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_init = piix3_uhci_class_init, +}; + +static void piix4_uhci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_uhci_common_initfn; + k->exit = usb_uhci_exit; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82371AB_2; + k->revision = 0x01; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_uhci; + dc->props = uhci_properties; +} + +static TypeInfo piix4_uhci_info = { + .name = "piix4-usb-uhci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_init = piix4_uhci_class_init, +}; + +static void vt82c686b_uhci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_uhci_vt82c686b_initfn; + k->exit = usb_uhci_exit; + k->vendor_id = PCI_VENDOR_ID_VIA; + k->device_id = PCI_DEVICE_ID_VIA_UHCI; + k->revision = 0x01; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_uhci; + dc->props = uhci_properties; +} + +static TypeInfo vt82c686b_uhci_info = { + .name = "vt82c686b-usb-uhci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_init = vt82c686b_uhci_class_init, +}; + +static void ich9_uhci1_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_uhci_common_initfn; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI1; + k->revision = 0x03; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_uhci; + dc->props = uhci_properties; +} + +static TypeInfo ich9_uhci1_info = { + .name = "ich9-usb-uhci1", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_init = ich9_uhci1_class_init, +}; + +static void ich9_uhci2_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_uhci_common_initfn; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI2; + k->revision = 0x03; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_uhci; + dc->props = uhci_properties; +} + +static TypeInfo ich9_uhci2_info = { + .name = "ich9-usb-uhci2", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_init = ich9_uhci2_class_init, +}; + +static void ich9_uhci3_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = usb_uhci_common_initfn; + k->vendor_id = PCI_VENDOR_ID_INTEL; + k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI3; + k->revision = 0x03; + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_uhci; + dc->props = uhci_properties; +} + +static TypeInfo ich9_uhci3_info = { + .name = "ich9-usb-uhci3", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_init = ich9_uhci3_class_init, +}; + +static void uhci_register_types(void) +{ + type_register_static(&piix3_uhci_info); + type_register_static(&piix4_uhci_info); + type_register_static(&vt82c686b_uhci_info); + type_register_static(&ich9_uhci1_info); + type_register_static(&ich9_uhci2_info); + type_register_static(&ich9_uhci3_info); +} + +type_init(uhci_register_types) + +void usb_uhci_piix3_init(PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "piix3-usb-uhci"); +} + +void usb_uhci_piix4_init(PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "piix4-usb-uhci"); +} + +void usb_uhci_vt82c686b_init(PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "vt82c686b-usb-uhci"); +} diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c new file mode 100644 index 0000000..73b0c7f --- /dev/null +++ b/hw/usb/hcd-xhci.c @@ -0,0 +1,2925 @@ +/* + * USB xHCI controller emulation + * + * Copyright (c) 2011 Securiforest + * Date: 2011-05-11 ; Author: Hector Martin <hector@marcansoft.com> + * Based on usb-ohci.c, emulates Renesas NEC USB 3.0 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "qemu-timer.h" +#include "hw/usb.h" +#include "hw/pci.h" +#include "hw/qdev-addr.h" +#include "hw/msi.h" + +//#define DEBUG_XHCI +//#define DEBUG_DATA + +#ifdef DEBUG_XHCI +#define DPRINTF(...) fprintf(stderr, __VA_ARGS__) +#else +#define DPRINTF(...) do {} while (0) +#endif +#define FIXME() do { fprintf(stderr, "FIXME %s:%d\n", \ + __func__, __LINE__); abort(); } while (0) + +#define MAXSLOTS 8 +#define MAXINTRS 1 + +#define USB2_PORTS 4 +#define USB3_PORTS 4 + +#define MAXPORTS (USB2_PORTS+USB3_PORTS) + +#define TD_QUEUE 24 +#define BG_XFERS 8 +#define BG_PKTS 8 + +/* Very pessimistic, let's hope it's enough for all cases */ +#define EV_QUEUE (((3*TD_QUEUE)+16)*MAXSLOTS) +/* Do not deliver ER Full events. NEC's driver does some things not bound + * to the specs when it gets them */ +#define ER_FULL_HACK + +#define LEN_CAP 0x40 +#define OFF_OPER LEN_CAP +#define LEN_OPER (0x400 + 0x10 * MAXPORTS) +#define OFF_RUNTIME ((OFF_OPER + LEN_OPER + 0x20) & ~0x1f) +#define LEN_RUNTIME (0x20 + MAXINTRS * 0x20) +#define OFF_DOORBELL (OFF_RUNTIME + LEN_RUNTIME) +#define LEN_DOORBELL ((MAXSLOTS + 1) * 0x20) + +/* must be power of 2 */ +#define LEN_REGS 0x2000 + +#if (OFF_DOORBELL + LEN_DOORBELL) > LEN_REGS +# error Increase LEN_REGS +#endif + +#if MAXINTRS > 1 +# error TODO: only one interrupter supported +#endif + +/* bit definitions */ +#define USBCMD_RS (1<<0) +#define USBCMD_HCRST (1<<1) +#define USBCMD_INTE (1<<2) +#define USBCMD_HSEE (1<<3) +#define USBCMD_LHCRST (1<<7) +#define USBCMD_CSS (1<<8) +#define USBCMD_CRS (1<<9) +#define USBCMD_EWE (1<<10) +#define USBCMD_EU3S (1<<11) + +#define USBSTS_HCH (1<<0) +#define USBSTS_HSE (1<<2) +#define USBSTS_EINT (1<<3) +#define USBSTS_PCD (1<<4) +#define USBSTS_SSS (1<<8) +#define USBSTS_RSS (1<<9) +#define USBSTS_SRE (1<<10) +#define USBSTS_CNR (1<<11) +#define USBSTS_HCE (1<<12) + + +#define PORTSC_CCS (1<<0) +#define PORTSC_PED (1<<1) +#define PORTSC_OCA (1<<3) +#define PORTSC_PR (1<<4) +#define PORTSC_PLS_SHIFT 5 +#define PORTSC_PLS_MASK 0xf +#define PORTSC_PP (1<<9) +#define PORTSC_SPEED_SHIFT 10 +#define PORTSC_SPEED_MASK 0xf +#define PORTSC_SPEED_FULL (1<<10) +#define PORTSC_SPEED_LOW (2<<10) +#define PORTSC_SPEED_HIGH (3<<10) +#define PORTSC_SPEED_SUPER (4<<10) +#define PORTSC_PIC_SHIFT 14 +#define PORTSC_PIC_MASK 0x3 +#define PORTSC_LWS (1<<16) +#define PORTSC_CSC (1<<17) +#define PORTSC_PEC (1<<18) +#define PORTSC_WRC (1<<19) +#define PORTSC_OCC (1<<20) +#define PORTSC_PRC (1<<21) +#define PORTSC_PLC (1<<22) +#define PORTSC_CEC (1<<23) +#define PORTSC_CAS (1<<24) +#define PORTSC_WCE (1<<25) +#define PORTSC_WDE (1<<26) +#define PORTSC_WOE (1<<27) +#define PORTSC_DR (1<<30) +#define PORTSC_WPR (1<<31) + +#define CRCR_RCS (1<<0) +#define CRCR_CS (1<<1) +#define CRCR_CA (1<<2) +#define CRCR_CRR (1<<3) + +#define IMAN_IP (1<<0) +#define IMAN_IE (1<<1) + +#define ERDP_EHB (1<<3) + +#define TRB_SIZE 16 +typedef struct XHCITRB { + uint64_t parameter; + uint32_t status; + uint32_t control; + target_phys_addr_t addr; + bool ccs; +} XHCITRB; + + +typedef enum TRBType { + TRB_RESERVED = 0, + TR_NORMAL, + TR_SETUP, + TR_DATA, + TR_STATUS, + TR_ISOCH, + TR_LINK, + TR_EVDATA, + TR_NOOP, + CR_ENABLE_SLOT, + CR_DISABLE_SLOT, + CR_ADDRESS_DEVICE, + CR_CONFIGURE_ENDPOINT, + CR_EVALUATE_CONTEXT, + CR_RESET_ENDPOINT, + CR_STOP_ENDPOINT, + CR_SET_TR_DEQUEUE, + CR_RESET_DEVICE, + CR_FORCE_EVENT, + CR_NEGOTIATE_BW, + CR_SET_LATENCY_TOLERANCE, + CR_GET_PORT_BANDWIDTH, + CR_FORCE_HEADER, + CR_NOOP, + ER_TRANSFER = 32, + ER_COMMAND_COMPLETE, + ER_PORT_STATUS_CHANGE, + ER_BANDWIDTH_REQUEST, + ER_DOORBELL, + ER_HOST_CONTROLLER, + ER_DEVICE_NOTIFICATION, + ER_MFINDEX_WRAP, + /* vendor specific bits */ + CR_VENDOR_VIA_CHALLENGE_RESPONSE = 48, + CR_VENDOR_NEC_FIRMWARE_REVISION = 49, + CR_VENDOR_NEC_CHALLENGE_RESPONSE = 50, +} TRBType; + +#define CR_LINK TR_LINK + +typedef enum TRBCCode { + CC_INVALID = 0, + CC_SUCCESS, + CC_DATA_BUFFER_ERROR, + CC_BABBLE_DETECTED, + CC_USB_TRANSACTION_ERROR, + CC_TRB_ERROR, + CC_STALL_ERROR, + CC_RESOURCE_ERROR, + CC_BANDWIDTH_ERROR, + CC_NO_SLOTS_ERROR, + CC_INVALID_STREAM_TYPE_ERROR, + CC_SLOT_NOT_ENABLED_ERROR, + CC_EP_NOT_ENABLED_ERROR, + CC_SHORT_PACKET, + CC_RING_UNDERRUN, + CC_RING_OVERRUN, + CC_VF_ER_FULL, + CC_PARAMETER_ERROR, + CC_BANDWIDTH_OVERRUN, + CC_CONTEXT_STATE_ERROR, + CC_NO_PING_RESPONSE_ERROR, + CC_EVENT_RING_FULL_ERROR, + CC_INCOMPATIBLE_DEVICE_ERROR, + CC_MISSED_SERVICE_ERROR, + CC_COMMAND_RING_STOPPED, + CC_COMMAND_ABORTED, + CC_STOPPED, + CC_STOPPED_LENGTH_INVALID, + CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR = 29, + CC_ISOCH_BUFFER_OVERRUN = 31, + CC_EVENT_LOST_ERROR, + CC_UNDEFINED_ERROR, + CC_INVALID_STREAM_ID_ERROR, + CC_SECONDARY_BANDWIDTH_ERROR, + CC_SPLIT_TRANSACTION_ERROR +} TRBCCode; + +#define TRB_C (1<<0) +#define TRB_TYPE_SHIFT 10 +#define TRB_TYPE_MASK 0x3f +#define TRB_TYPE(t) (((t).control >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK) + +#define TRB_EV_ED (1<<2) + +#define TRB_TR_ENT (1<<1) +#define TRB_TR_ISP (1<<2) +#define TRB_TR_NS (1<<3) +#define TRB_TR_CH (1<<4) +#define TRB_TR_IOC (1<<5) +#define TRB_TR_IDT (1<<6) +#define TRB_TR_TBC_SHIFT 7 +#define TRB_TR_TBC_MASK 0x3 +#define TRB_TR_BEI (1<<9) +#define TRB_TR_TLBPC_SHIFT 16 +#define TRB_TR_TLBPC_MASK 0xf +#define TRB_TR_FRAMEID_SHIFT 20 +#define TRB_TR_FRAMEID_MASK 0x7ff +#define TRB_TR_SIA (1<<31) + +#define TRB_TR_DIR (1<<16) + +#define TRB_CR_SLOTID_SHIFT 24 +#define TRB_CR_SLOTID_MASK 0xff +#define TRB_CR_EPID_SHIFT 16 +#define TRB_CR_EPID_MASK 0x1f + +#define TRB_CR_BSR (1<<9) +#define TRB_CR_DC (1<<9) + +#define TRB_LK_TC (1<<1) + +#define EP_TYPE_MASK 0x7 +#define EP_TYPE_SHIFT 3 + +#define EP_STATE_MASK 0x7 +#define EP_DISABLED (0<<0) +#define EP_RUNNING (1<<0) +#define EP_HALTED (2<<0) +#define EP_STOPPED (3<<0) +#define EP_ERROR (4<<0) + +#define SLOT_STATE_MASK 0x1f +#define SLOT_STATE_SHIFT 27 +#define SLOT_STATE(s) (((s)>>SLOT_STATE_SHIFT)&SLOT_STATE_MASK) +#define SLOT_ENABLED 0 +#define SLOT_DEFAULT 1 +#define SLOT_ADDRESSED 2 +#define SLOT_CONFIGURED 3 + +#define SLOT_CONTEXT_ENTRIES_MASK 0x1f +#define SLOT_CONTEXT_ENTRIES_SHIFT 27 + +typedef enum EPType { + ET_INVALID = 0, + ET_ISO_OUT, + ET_BULK_OUT, + ET_INTR_OUT, + ET_CONTROL, + ET_ISO_IN, + ET_BULK_IN, + ET_INTR_IN, +} EPType; + +typedef struct XHCIRing { + target_phys_addr_t base; + target_phys_addr_t dequeue; + bool ccs; +} XHCIRing; + +typedef struct XHCIPort { + USBPort port; + uint32_t portsc; +} XHCIPort; + +struct XHCIState; +typedef struct XHCIState XHCIState; + +typedef struct XHCITransfer { + XHCIState *xhci; + USBPacket packet; + bool running_async; + bool running_retry; + bool cancelled; + bool complete; + bool backgrounded; + unsigned int iso_pkts; + unsigned int slotid; + unsigned int epid; + bool in_xfer; + bool iso_xfer; + bool bg_xfer; + + unsigned int trb_count; + unsigned int trb_alloced; + XHCITRB *trbs; + + unsigned int data_length; + unsigned int data_alloced; + uint8_t *data; + + TRBCCode status; + + unsigned int pkts; + unsigned int pktsize; + unsigned int cur_pkt; +} XHCITransfer; + +typedef struct XHCIEPContext { + XHCIRing ring; + unsigned int next_xfer; + unsigned int comp_xfer; + XHCITransfer transfers[TD_QUEUE]; + XHCITransfer *retry; + bool bg_running; + bool bg_updating; + unsigned int next_bg; + XHCITransfer bg_transfers[BG_XFERS]; + EPType type; + target_phys_addr_t pctx; + unsigned int max_psize; + bool has_bg; + uint32_t state; +} XHCIEPContext; + +typedef struct XHCISlot { + bool enabled; + target_phys_addr_t ctx; + unsigned int port; + unsigned int devaddr; + XHCIEPContext * eps[31]; +} XHCISlot; + +typedef struct XHCIEvent { + TRBType type; + TRBCCode ccode; + uint64_t ptr; + uint32_t length; + uint32_t flags; + uint8_t slotid; + uint8_t epid; +} XHCIEvent; + +struct XHCIState { + PCIDevice pci_dev; + USBBus bus; + qemu_irq irq; + MemoryRegion mem; + const char *name; + uint32_t msi; + unsigned int devaddr; + + /* Operational Registers */ + uint32_t usbcmd; + uint32_t usbsts; + uint32_t dnctrl; + uint32_t crcr_low; + uint32_t crcr_high; + uint32_t dcbaap_low; + uint32_t dcbaap_high; + uint32_t config; + + XHCIPort ports[MAXPORTS]; + XHCISlot slots[MAXSLOTS]; + + /* Runtime Registers */ + uint32_t mfindex; + /* note: we only support one interrupter */ + uint32_t iman; + uint32_t imod; + uint32_t erstsz; + uint32_t erstba_low; + uint32_t erstba_high; + uint32_t erdp_low; + uint32_t erdp_high; + + target_phys_addr_t er_start; + uint32_t er_size; + bool er_pcs; + unsigned int er_ep_idx; + bool er_full; + + XHCIEvent ev_buffer[EV_QUEUE]; + unsigned int ev_buffer_put; + unsigned int ev_buffer_get; + + XHCIRing cmd_ring; +}; + +typedef struct XHCIEvRingSeg { + uint32_t addr_low; + uint32_t addr_high; + uint32_t size; + uint32_t rsvd; +} XHCIEvRingSeg; + +#ifdef DEBUG_XHCI +static const char *TRBType_names[] = { + [TRB_RESERVED] = "TRB_RESERVED", + [TR_NORMAL] = "TR_NORMAL", + [TR_SETUP] = "TR_SETUP", + [TR_DATA] = "TR_DATA", + [TR_STATUS] = "TR_STATUS", + [TR_ISOCH] = "TR_ISOCH", + [TR_LINK] = "TR_LINK", + [TR_EVDATA] = "TR_EVDATA", + [TR_NOOP] = "TR_NOOP", + [CR_ENABLE_SLOT] = "CR_ENABLE_SLOT", + [CR_DISABLE_SLOT] = "CR_DISABLE_SLOT", + [CR_ADDRESS_DEVICE] = "CR_ADDRESS_DEVICE", + [CR_CONFIGURE_ENDPOINT] = "CR_CONFIGURE_ENDPOINT", + [CR_EVALUATE_CONTEXT] = "CR_EVALUATE_CONTEXT", + [CR_RESET_ENDPOINT] = "CR_RESET_ENDPOINT", + [CR_STOP_ENDPOINT] = "CR_STOP_ENDPOINT", + [CR_SET_TR_DEQUEUE] = "CR_SET_TR_DEQUEUE", + [CR_RESET_DEVICE] = "CR_RESET_DEVICE", + [CR_FORCE_EVENT] = "CR_FORCE_EVENT", + [CR_NEGOTIATE_BW] = "CR_NEGOTIATE_BW", + [CR_SET_LATENCY_TOLERANCE] = "CR_SET_LATENCY_TOLERANCE", + [CR_GET_PORT_BANDWIDTH] = "CR_GET_PORT_BANDWIDTH", + [CR_FORCE_HEADER] = "CR_FORCE_HEADER", + [CR_NOOP] = "CR_NOOP", + [ER_TRANSFER] = "ER_TRANSFER", + [ER_COMMAND_COMPLETE] = "ER_COMMAND_COMPLETE", + [ER_PORT_STATUS_CHANGE] = "ER_PORT_STATUS_CHANGE", + [ER_BANDWIDTH_REQUEST] = "ER_BANDWIDTH_REQUEST", + [ER_DOORBELL] = "ER_DOORBELL", + [ER_HOST_CONTROLLER] = "ER_HOST_CONTROLLER", + [ER_DEVICE_NOTIFICATION] = "ER_DEVICE_NOTIFICATION", + [ER_MFINDEX_WRAP] = "ER_MFINDEX_WRAP", + [CR_VENDOR_VIA_CHALLENGE_RESPONSE] = "CR_VENDOR_VIA_CHALLENGE_RESPONSE", + [CR_VENDOR_NEC_FIRMWARE_REVISION] = "CR_VENDOR_NEC_FIRMWARE_REVISION", + [CR_VENDOR_NEC_CHALLENGE_RESPONSE] = "CR_VENDOR_NEC_CHALLENGE_RESPONSE", +}; + +static const char *lookup_name(uint32_t index, const char **list, uint32_t llen) +{ + if (index >= llen || list[index] == NULL) { + return "???"; + } + return list[index]; +} + +static const char *trb_name(XHCITRB *trb) +{ + return lookup_name(TRB_TYPE(*trb), TRBType_names, + ARRAY_SIZE(TRBType_names)); +} +#endif + +static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid); + +static inline target_phys_addr_t xhci_addr64(uint32_t low, uint32_t high) +{ +#if TARGET_PHYS_ADDR_BITS > 32 + return low | ((target_phys_addr_t)high << 32); +#else + return low; +#endif +} + +static inline target_phys_addr_t xhci_mask64(uint64_t addr) +{ +#if TARGET_PHYS_ADDR_BITS > 32 + return addr; +#else + return addr & 0xffffffff; +#endif +} + +static void xhci_irq_update(XHCIState *xhci) +{ + int level = 0; + + if (xhci->iman & IMAN_IP && xhci->iman & IMAN_IE && + xhci->usbcmd && USBCMD_INTE) { + level = 1; + } + + DPRINTF("xhci_irq_update(): %d\n", level); + + if (xhci->msi && msi_enabled(&xhci->pci_dev)) { + if (level) { + DPRINTF("xhci_irq_update(): MSI signal\n"); + msi_notify(&xhci->pci_dev, 0); + } + } else { + qemu_set_irq(xhci->irq, level); + } +} + +static inline int xhci_running(XHCIState *xhci) +{ + return !(xhci->usbsts & USBSTS_HCH) && !xhci->er_full; +} + +static void xhci_die(XHCIState *xhci) +{ + xhci->usbsts |= USBSTS_HCE; + fprintf(stderr, "xhci: asserted controller error\n"); +} + +static void xhci_write_event(XHCIState *xhci, XHCIEvent *event) +{ + XHCITRB ev_trb; + target_phys_addr_t addr; + + ev_trb.parameter = cpu_to_le64(event->ptr); + ev_trb.status = cpu_to_le32(event->length | (event->ccode << 24)); + ev_trb.control = (event->slotid << 24) | (event->epid << 16) | + event->flags | (event->type << TRB_TYPE_SHIFT); + if (xhci->er_pcs) { + ev_trb.control |= TRB_C; + } + ev_trb.control = cpu_to_le32(ev_trb.control); + + DPRINTF("xhci_write_event(): [%d] %016"PRIx64" %08x %08x %s\n", + xhci->er_ep_idx, ev_trb.parameter, ev_trb.status, ev_trb.control, + trb_name(&ev_trb)); + + addr = xhci->er_start + TRB_SIZE*xhci->er_ep_idx; + cpu_physical_memory_write(addr, (uint8_t *) &ev_trb, TRB_SIZE); + + xhci->er_ep_idx++; + if (xhci->er_ep_idx >= xhci->er_size) { + xhci->er_ep_idx = 0; + xhci->er_pcs = !xhci->er_pcs; + } +} + +static void xhci_events_update(XHCIState *xhci) +{ + target_phys_addr_t erdp; + unsigned int dp_idx; + bool do_irq = 0; + + if (xhci->usbsts & USBSTS_HCH) { + return; + } + + erdp = xhci_addr64(xhci->erdp_low, xhci->erdp_high); + if (erdp < xhci->er_start || + erdp >= (xhci->er_start + TRB_SIZE*xhci->er_size)) { + fprintf(stderr, "xhci: ERDP out of bounds: "TARGET_FMT_plx"\n", erdp); + fprintf(stderr, "xhci: ER at "TARGET_FMT_plx" len %d\n", + xhci->er_start, xhci->er_size); + xhci_die(xhci); + return; + } + dp_idx = (erdp - xhci->er_start) / TRB_SIZE; + assert(dp_idx < xhci->er_size); + + /* NEC didn't read section 4.9.4 of the spec (v1.0 p139 top Note) and thus + * deadlocks when the ER is full. Hack it by holding off events until + * the driver decides to free at least half of the ring */ + if (xhci->er_full) { + int er_free = dp_idx - xhci->er_ep_idx; + if (er_free <= 0) { + er_free += xhci->er_size; + } + if (er_free < (xhci->er_size/2)) { + DPRINTF("xhci_events_update(): event ring still " + "more than half full (hack)\n"); + return; + } + } + + while (xhci->ev_buffer_put != xhci->ev_buffer_get) { + assert(xhci->er_full); + if (((xhci->er_ep_idx+1) % xhci->er_size) == dp_idx) { + DPRINTF("xhci_events_update(): event ring full again\n"); +#ifndef ER_FULL_HACK + XHCIEvent full = {ER_HOST_CONTROLLER, CC_EVENT_RING_FULL_ERROR}; + xhci_write_event(xhci, &full); +#endif + do_irq = 1; + break; + } + XHCIEvent *event = &xhci->ev_buffer[xhci->ev_buffer_get]; + xhci_write_event(xhci, event); + xhci->ev_buffer_get++; + do_irq = 1; + if (xhci->ev_buffer_get == EV_QUEUE) { + xhci->ev_buffer_get = 0; + } + } + + if (do_irq) { + xhci->erdp_low |= ERDP_EHB; + xhci->iman |= IMAN_IP; + xhci->usbsts |= USBSTS_EINT; + xhci_irq_update(xhci); + } + + if (xhci->er_full && xhci->ev_buffer_put == xhci->ev_buffer_get) { + DPRINTF("xhci_events_update(): event ring no longer full\n"); + xhci->er_full = 0; + } + return; +} + +static void xhci_event(XHCIState *xhci, XHCIEvent *event) +{ + target_phys_addr_t erdp; + unsigned int dp_idx; + + if (xhci->er_full) { + DPRINTF("xhci_event(): ER full, queueing\n"); + if (((xhci->ev_buffer_put+1) % EV_QUEUE) == xhci->ev_buffer_get) { + fprintf(stderr, "xhci: event queue full, dropping event!\n"); + return; + } + xhci->ev_buffer[xhci->ev_buffer_put++] = *event; + if (xhci->ev_buffer_put == EV_QUEUE) { + xhci->ev_buffer_put = 0; + } + return; + } + + erdp = xhci_addr64(xhci->erdp_low, xhci->erdp_high); + if (erdp < xhci->er_start || + erdp >= (xhci->er_start + TRB_SIZE*xhci->er_size)) { + fprintf(stderr, "xhci: ERDP out of bounds: "TARGET_FMT_plx"\n", erdp); + fprintf(stderr, "xhci: ER at "TARGET_FMT_plx" len %d\n", + xhci->er_start, xhci->er_size); + xhci_die(xhci); + return; + } + + dp_idx = (erdp - xhci->er_start) / TRB_SIZE; + assert(dp_idx < xhci->er_size); + + if ((xhci->er_ep_idx+1) % xhci->er_size == dp_idx) { + DPRINTF("xhci_event(): ER full, queueing\n"); +#ifndef ER_FULL_HACK + XHCIEvent full = {ER_HOST_CONTROLLER, CC_EVENT_RING_FULL_ERROR}; + xhci_write_event(xhci, &full); +#endif + xhci->er_full = 1; + if (((xhci->ev_buffer_put+1) % EV_QUEUE) == xhci->ev_buffer_get) { + fprintf(stderr, "xhci: event queue full, dropping event!\n"); + return; + } + xhci->ev_buffer[xhci->ev_buffer_put++] = *event; + if (xhci->ev_buffer_put == EV_QUEUE) { + xhci->ev_buffer_put = 0; + } + } else { + xhci_write_event(xhci, event); + } + + xhci->erdp_low |= ERDP_EHB; + xhci->iman |= IMAN_IP; + xhci->usbsts |= USBSTS_EINT; + + xhci_irq_update(xhci); +} + +static void xhci_ring_init(XHCIState *xhci, XHCIRing *ring, + target_phys_addr_t base) +{ + ring->base = base; + ring->dequeue = base; + ring->ccs = 1; +} + +static TRBType xhci_ring_fetch(XHCIState *xhci, XHCIRing *ring, XHCITRB *trb, + target_phys_addr_t *addr) +{ + while (1) { + TRBType type; + cpu_physical_memory_read(ring->dequeue, (uint8_t *) trb, TRB_SIZE); + trb->addr = ring->dequeue; + trb->ccs = ring->ccs; + le64_to_cpus(&trb->parameter); + le32_to_cpus(&trb->status); + le32_to_cpus(&trb->control); + + DPRINTF("xhci: TRB fetched [" TARGET_FMT_plx "]: " + "%016" PRIx64 " %08x %08x %s\n", + ring->dequeue, trb->parameter, trb->status, trb->control, + trb_name(trb)); + + if ((trb->control & TRB_C) != ring->ccs) { + return 0; + } + + type = TRB_TYPE(*trb); + + if (type != TR_LINK) { + if (addr) { + *addr = ring->dequeue; + } + ring->dequeue += TRB_SIZE; + return type; + } else { + ring->dequeue = xhci_mask64(trb->parameter); + if (trb->control & TRB_LK_TC) { + ring->ccs = !ring->ccs; + } + } + } +} + +static int xhci_ring_chain_length(XHCIState *xhci, const XHCIRing *ring) +{ + XHCITRB trb; + int length = 0; + target_phys_addr_t dequeue = ring->dequeue; + bool ccs = ring->ccs; + /* hack to bundle together the two/three TDs that make a setup transfer */ + bool control_td_set = 0; + + while (1) { + TRBType type; + cpu_physical_memory_read(dequeue, (uint8_t *) &trb, TRB_SIZE); + le64_to_cpus(&trb.parameter); + le32_to_cpus(&trb.status); + le32_to_cpus(&trb.control); + + DPRINTF("xhci: TRB peeked [" TARGET_FMT_plx "]: " + "%016" PRIx64 " %08x %08x\n", + dequeue, trb.parameter, trb.status, trb.control); + + if ((trb.control & TRB_C) != ccs) { + return -length; + } + + type = TRB_TYPE(trb); + + if (type == TR_LINK) { + dequeue = xhci_mask64(trb.parameter); + if (trb.control & TRB_LK_TC) { + ccs = !ccs; + } + continue; + } + + length += 1; + dequeue += TRB_SIZE; + + if (type == TR_SETUP) { + control_td_set = 1; + } else if (type == TR_STATUS) { + control_td_set = 0; + } + + if (!control_td_set && !(trb.control & TRB_TR_CH)) { + return length; + } + } +} + +static void xhci_er_reset(XHCIState *xhci) +{ + XHCIEvRingSeg seg; + + /* cache the (sole) event ring segment location */ + if (xhci->erstsz != 1) { + fprintf(stderr, "xhci: invalid value for ERSTSZ: %d\n", xhci->erstsz); + xhci_die(xhci); + return; + } + target_phys_addr_t erstba = xhci_addr64(xhci->erstba_low, xhci->erstba_high); + cpu_physical_memory_read(erstba, (uint8_t *) &seg, sizeof(seg)); + le32_to_cpus(&seg.addr_low); + le32_to_cpus(&seg.addr_high); + le32_to_cpus(&seg.size); + if (seg.size < 16 || seg.size > 4096) { + fprintf(stderr, "xhci: invalid value for segment size: %d\n", seg.size); + xhci_die(xhci); + return; + } + xhci->er_start = xhci_addr64(seg.addr_low, seg.addr_high); + xhci->er_size = seg.size; + + xhci->er_ep_idx = 0; + xhci->er_pcs = 1; + xhci->er_full = 0; + + DPRINTF("xhci: event ring:" TARGET_FMT_plx " [%d]\n", + xhci->er_start, xhci->er_size); +} + +static void xhci_run(XHCIState *xhci) +{ + DPRINTF("xhci_run()\n"); + + xhci->usbsts &= ~USBSTS_HCH; +} + +static void xhci_stop(XHCIState *xhci) +{ + DPRINTF("xhci_stop()\n"); + xhci->usbsts |= USBSTS_HCH; + xhci->crcr_low &= ~CRCR_CRR; +} + +static void xhci_set_ep_state(XHCIState *xhci, XHCIEPContext *epctx, + uint32_t state) +{ + uint32_t ctx[5]; + if (epctx->state == state) { + return; + } + + cpu_physical_memory_read(epctx->pctx, (uint8_t *) ctx, sizeof(ctx)); + ctx[0] &= ~EP_STATE_MASK; + ctx[0] |= state; + ctx[2] = epctx->ring.dequeue | epctx->ring.ccs; + ctx[3] = (epctx->ring.dequeue >> 16) >> 16; + DPRINTF("xhci: set epctx: " TARGET_FMT_plx " state=%d dequeue=%08x%08x\n", + epctx->pctx, state, ctx[3], ctx[2]); + cpu_physical_memory_write(epctx->pctx, (uint8_t *) ctx, sizeof(ctx)); + epctx->state = state; +} + +static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid, target_phys_addr_t pctx, + uint32_t *ctx) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + target_phys_addr_t dequeue; + int i; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(epid >= 1 && epid <= 31); + + DPRINTF("xhci_enable_ep(%d, %d)\n", slotid, epid); + + slot = &xhci->slots[slotid-1]; + if (slot->eps[epid-1]) { + fprintf(stderr, "xhci: slot %d ep %d already enabled!\n", slotid, epid); + return CC_TRB_ERROR; + } + + epctx = g_malloc(sizeof(XHCIEPContext)); + memset(epctx, 0, sizeof(XHCIEPContext)); + + slot->eps[epid-1] = epctx; + + dequeue = xhci_addr64(ctx[2] & ~0xf, ctx[3]); + xhci_ring_init(xhci, &epctx->ring, dequeue); + epctx->ring.ccs = ctx[2] & 1; + + epctx->type = (ctx[1] >> EP_TYPE_SHIFT) & EP_TYPE_MASK; + DPRINTF("xhci: endpoint %d.%d type is %d\n", epid/2, epid%2, epctx->type); + epctx->pctx = pctx; + epctx->max_psize = ctx[1]>>16; + epctx->max_psize *= 1+((ctx[1]>>8)&0xff); + epctx->has_bg = false; + if (epctx->type == ET_ISO_IN) { + epctx->has_bg = true; + } + DPRINTF("xhci: endpoint %d.%d max transaction (burst) size is %d\n", + epid/2, epid%2, epctx->max_psize); + for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) { + usb_packet_init(&epctx->transfers[i].packet); + } + + epctx->state = EP_RUNNING; + ctx[0] &= ~EP_STATE_MASK; + ctx[0] |= EP_RUNNING; + + return CC_SUCCESS; +} + +static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid, + unsigned int epid) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + int i, xferi, killed = 0; + assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(epid >= 1 && epid <= 31); + + DPRINTF("xhci_ep_nuke_xfers(%d, %d)\n", slotid, epid); + + slot = &xhci->slots[slotid-1]; + + if (!slot->eps[epid-1]) { + return 0; + } + + epctx = slot->eps[epid-1]; + + xferi = epctx->next_xfer; + for (i = 0; i < TD_QUEUE; i++) { + XHCITransfer *t = &epctx->transfers[xferi]; + if (t->running_async) { + usb_cancel_packet(&t->packet); + t->running_async = 0; + t->cancelled = 1; + DPRINTF("xhci: cancelling transfer %d, waiting for it to complete...\n", i); + killed++; + } + if (t->running_retry) { + t->running_retry = 0; + epctx->retry = NULL; + } + if (t->backgrounded) { + t->backgrounded = 0; + } + if (t->trbs) { + g_free(t->trbs); + } + if (t->data) { + g_free(t->data); + } + + t->trbs = NULL; + t->data = NULL; + t->trb_count = t->trb_alloced = 0; + t->data_length = t->data_alloced = 0; + xferi = (xferi + 1) % TD_QUEUE; + } + if (epctx->has_bg) { + xferi = epctx->next_bg; + for (i = 0; i < BG_XFERS; i++) { + XHCITransfer *t = &epctx->bg_transfers[xferi]; + if (t->running_async) { + usb_cancel_packet(&t->packet); + t->running_async = 0; + t->cancelled = 1; + DPRINTF("xhci: cancelling bg transfer %d, waiting for it to complete...\n", i); + killed++; + } + if (t->data) { + g_free(t->data); + } + + t->data = NULL; + xferi = (xferi + 1) % BG_XFERS; + } + } + return killed; +} + +static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(epid >= 1 && epid <= 31); + + DPRINTF("xhci_disable_ep(%d, %d)\n", slotid, epid); + + slot = &xhci->slots[slotid-1]; + + if (!slot->eps[epid-1]) { + DPRINTF("xhci: slot %d ep %d already disabled\n", slotid, epid); + return CC_SUCCESS; + } + + xhci_ep_nuke_xfers(xhci, slotid, epid); + + epctx = slot->eps[epid-1]; + + xhci_set_ep_state(xhci, epctx, EP_DISABLED); + + g_free(epctx); + slot->eps[epid-1] = NULL; + + return CC_SUCCESS; +} + +static TRBCCode xhci_stop_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + + DPRINTF("xhci_stop_ep(%d, %d)\n", slotid, epid); + + assert(slotid >= 1 && slotid <= MAXSLOTS); + + if (epid < 1 || epid > 31) { + fprintf(stderr, "xhci: bad ep %d\n", epid); + return CC_TRB_ERROR; + } + + slot = &xhci->slots[slotid-1]; + + if (!slot->eps[epid-1]) { + DPRINTF("xhci: slot %d ep %d not enabled\n", slotid, epid); + return CC_EP_NOT_ENABLED_ERROR; + } + + if (xhci_ep_nuke_xfers(xhci, slotid, epid) > 0) { + fprintf(stderr, "xhci: FIXME: endpoint stopped w/ xfers running, " + "data might be lost\n"); + } + + epctx = slot->eps[epid-1]; + + xhci_set_ep_state(xhci, epctx, EP_STOPPED); + + return CC_SUCCESS; +} + +static TRBCCode xhci_reset_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + USBDevice *dev; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + + DPRINTF("xhci_reset_ep(%d, %d)\n", slotid, epid); + + if (epid < 1 || epid > 31) { + fprintf(stderr, "xhci: bad ep %d\n", epid); + return CC_TRB_ERROR; + } + + slot = &xhci->slots[slotid-1]; + + if (!slot->eps[epid-1]) { + DPRINTF("xhci: slot %d ep %d not enabled\n", slotid, epid); + return CC_EP_NOT_ENABLED_ERROR; + } + + epctx = slot->eps[epid-1]; + + if (epctx->state != EP_HALTED) { + fprintf(stderr, "xhci: reset EP while EP %d not halted (%d)\n", + epid, epctx->state); + return CC_CONTEXT_STATE_ERROR; + } + + if (xhci_ep_nuke_xfers(xhci, slotid, epid) > 0) { + fprintf(stderr, "xhci: FIXME: endpoint reset w/ xfers running, " + "data might be lost\n"); + } + + uint8_t ep = epid>>1; + + if (epid & 1) { + ep |= 0x80; + } + + dev = xhci->ports[xhci->slots[slotid-1].port-1].port.dev; + if (!dev) { + return CC_USB_TRANSACTION_ERROR; + } + + xhci_set_ep_state(xhci, epctx, EP_STOPPED); + + return CC_SUCCESS; +} + +static TRBCCode xhci_set_ep_dequeue(XHCIState *xhci, unsigned int slotid, + unsigned int epid, uint64_t pdequeue) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + target_phys_addr_t dequeue; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + + if (epid < 1 || epid > 31) { + fprintf(stderr, "xhci: bad ep %d\n", epid); + return CC_TRB_ERROR; + } + + DPRINTF("xhci_set_ep_dequeue(%d, %d, %016"PRIx64")\n", slotid, epid, pdequeue); + dequeue = xhci_mask64(pdequeue); + + slot = &xhci->slots[slotid-1]; + + if (!slot->eps[epid-1]) { + DPRINTF("xhci: slot %d ep %d not enabled\n", slotid, epid); + return CC_EP_NOT_ENABLED_ERROR; + } + + epctx = slot->eps[epid-1]; + + + if (epctx->state != EP_STOPPED) { + fprintf(stderr, "xhci: set EP dequeue pointer while EP %d not stopped\n", epid); + return CC_CONTEXT_STATE_ERROR; + } + + xhci_ring_init(xhci, &epctx->ring, dequeue & ~0xF); + epctx->ring.ccs = dequeue & 1; + + xhci_set_ep_state(xhci, epctx, EP_STOPPED); + + return CC_SUCCESS; +} + +static int xhci_xfer_data(XHCITransfer *xfer, uint8_t *data, + unsigned int length, bool in_xfer, bool out_xfer, + bool report) +{ + int i; + uint32_t edtla = 0; + unsigned int transferred = 0; + unsigned int left = length; + bool reported = 0; + bool shortpkt = 0; + XHCIEvent event = {ER_TRANSFER, CC_SUCCESS}; + XHCIState *xhci = xfer->xhci; + + DPRINTF("xhci_xfer_data(len=%d, in_xfer=%d, out_xfer=%d, report=%d)\n", + length, in_xfer, out_xfer, report); + + assert(!(in_xfer && out_xfer)); + + for (i = 0; i < xfer->trb_count; i++) { + XHCITRB *trb = &xfer->trbs[i]; + target_phys_addr_t addr; + unsigned int chunk = 0; + + switch (TRB_TYPE(*trb)) { + case TR_DATA: + if ((!(trb->control & TRB_TR_DIR)) != (!in_xfer)) { + fprintf(stderr, "xhci: data direction mismatch for TR_DATA\n"); + xhci_die(xhci); + return transferred; + } + /* fallthrough */ + case TR_NORMAL: + case TR_ISOCH: + addr = xhci_mask64(trb->parameter); + chunk = trb->status & 0x1ffff; + if (chunk > left) { + chunk = left; + shortpkt = 1; + } + if (in_xfer || out_xfer) { + if (trb->control & TRB_TR_IDT) { + uint64_t idata; + if (chunk > 8 || in_xfer) { + fprintf(stderr, "xhci: invalid immediate data TRB\n"); + xhci_die(xhci); + return transferred; + } + idata = le64_to_cpu(trb->parameter); + memcpy(data, &idata, chunk); + } else { + DPRINTF("xhci_xfer_data: r/w(%d) %d bytes at " + TARGET_FMT_plx "\n", in_xfer, chunk, addr); + if (in_xfer) { + cpu_physical_memory_write(addr, data, chunk); + } else { + cpu_physical_memory_read(addr, data, chunk); + } +#ifdef DEBUG_DATA + unsigned int count = chunk; + int i; + if (count > 16) { + count = 16; + } + DPRINTF(" ::"); + for (i = 0; i < count; i++) { + DPRINTF(" %02x", data[i]); + } + DPRINTF("\n"); +#endif + } + } + left -= chunk; + data += chunk; + edtla += chunk; + transferred += chunk; + break; + case TR_STATUS: + reported = 0; + shortpkt = 0; + break; + } + + if (report && !reported && (trb->control & TRB_TR_IOC || + (shortpkt && (trb->control & TRB_TR_ISP)))) { + event.slotid = xfer->slotid; + event.epid = xfer->epid; + event.length = (trb->status & 0x1ffff) - chunk; + event.flags = 0; + event.ptr = trb->addr; + if (xfer->status == CC_SUCCESS) { + event.ccode = shortpkt ? CC_SHORT_PACKET : CC_SUCCESS; + } else { + event.ccode = xfer->status; + } + if (TRB_TYPE(*trb) == TR_EVDATA) { + event.ptr = trb->parameter; + event.flags |= TRB_EV_ED; + event.length = edtla & 0xffffff; + DPRINTF("xhci_xfer_data: EDTLA=%d\n", event.length); + edtla = 0; + } + xhci_event(xhci, &event); + reported = 1; + } + } + return transferred; +} + +static void xhci_stall_ep(XHCITransfer *xfer) +{ + XHCIState *xhci = xfer->xhci; + XHCISlot *slot = &xhci->slots[xfer->slotid-1]; + XHCIEPContext *epctx = slot->eps[xfer->epid-1]; + + epctx->ring.dequeue = xfer->trbs[0].addr; + epctx->ring.ccs = xfer->trbs[0].ccs; + xhci_set_ep_state(xhci, epctx, EP_HALTED); + DPRINTF("xhci: stalled slot %d ep %d\n", xfer->slotid, xfer->epid); + DPRINTF("xhci: will continue at "TARGET_FMT_plx"\n", epctx->ring.dequeue); +} + +static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx); + +static void xhci_bg_update(XHCIState *xhci, XHCIEPContext *epctx) +{ + if (epctx->bg_updating) { + return; + } + DPRINTF("xhci_bg_update(%p, %p)\n", xhci, epctx); + assert(epctx->has_bg); + DPRINTF("xhci: fg=%d bg=%d\n", epctx->comp_xfer, epctx->next_bg); + epctx->bg_updating = 1; + while (epctx->transfers[epctx->comp_xfer].backgrounded && + epctx->bg_transfers[epctx->next_bg].complete) { + XHCITransfer *fg = &epctx->transfers[epctx->comp_xfer]; + XHCITransfer *bg = &epctx->bg_transfers[epctx->next_bg]; +#if 0 + DPRINTF("xhci: completing fg %d from bg %d.%d (stat: %d)\n", + epctx->comp_xfer, epctx->next_bg, bg->cur_pkt, + bg->usbxfer->iso_packet_desc[bg->cur_pkt].status + ); +#endif + assert(epctx->type == ET_ISO_IN); + assert(bg->iso_xfer); + assert(bg->in_xfer); + uint8_t *p = bg->data + bg->cur_pkt * bg->pktsize; +#if 0 + int len = bg->usbxfer->iso_packet_desc[bg->cur_pkt].actual_length; + fg->status = libusb_to_ccode(bg->usbxfer->iso_packet_desc[bg->cur_pkt].status); +#else + int len = 0; + FIXME(); +#endif + fg->complete = 1; + fg->backgrounded = 0; + + if (fg->status == CC_STALL_ERROR) { + xhci_stall_ep(fg); + } + + xhci_xfer_data(fg, p, len, 1, 0, 1); + + epctx->comp_xfer++; + if (epctx->comp_xfer == TD_QUEUE) { + epctx->comp_xfer = 0; + } + DPRINTF("next fg xfer: %d\n", epctx->comp_xfer); + bg->cur_pkt++; + if (bg->cur_pkt == bg->pkts) { + bg->complete = 0; + if (xhci_submit(xhci, bg, epctx) < 0) { + fprintf(stderr, "xhci: bg resubmit failed\n"); + } + epctx->next_bg++; + if (epctx->next_bg == BG_XFERS) { + epctx->next_bg = 0; + } + DPRINTF("next bg xfer: %d\n", epctx->next_bg); + + xhci_kick_ep(xhci, fg->slotid, fg->epid); + } + } + epctx->bg_updating = 0; +} + +#if 0 +static void xhci_xfer_cb(struct libusb_transfer *transfer) +{ + XHCIState *xhci; + XHCITransfer *xfer; + + xfer = (XHCITransfer *)transfer->user_data; + xhci = xfer->xhci; + + DPRINTF("xhci_xfer_cb(slot=%d, ep=%d, status=%d)\n", xfer->slotid, + xfer->epid, transfer->status); + + assert(xfer->slotid >= 1 && xfer->slotid <= MAXSLOTS); + assert(xfer->epid >= 1 && xfer->epid <= 31); + + if (xfer->cancelled) { + DPRINTF("xhci: transfer cancelled, not reporting anything\n"); + xfer->running = 0; + return; + } + + XHCIEPContext *epctx; + XHCISlot *slot; + slot = &xhci->slots[xfer->slotid-1]; + assert(slot->eps[xfer->epid-1]); + epctx = slot->eps[xfer->epid-1]; + + if (xfer->bg_xfer) { + DPRINTF("xhci: background transfer, updating\n"); + xfer->complete = 1; + xfer->running = 0; + xhci_bg_update(xhci, epctx); + return; + } + + if (xfer->iso_xfer) { + transfer->status = transfer->iso_packet_desc[0].status; + transfer->actual_length = transfer->iso_packet_desc[0].actual_length; + } + + xfer->status = libusb_to_ccode(transfer->status); + + xfer->complete = 1; + xfer->running = 0; + + if (transfer->status == LIBUSB_TRANSFER_STALL) + xhci_stall_ep(xhci, epctx, xfer); + + DPRINTF("xhci: transfer actual length = %d\n", transfer->actual_length); + + if (xfer->in_xfer) { + if (xfer->epid == 1) { + xhci_xfer_data(xhci, xfer, xfer->data + 8, + transfer->actual_length, 1, 0, 1); + } else { + xhci_xfer_data(xhci, xfer, xfer->data, + transfer->actual_length, 1, 0, 1); + } + } else { + xhci_xfer_data(xhci, xfer, NULL, transfer->actual_length, 0, 0, 1); + } + + xhci_kick_ep(xhci, xfer->slotid, xfer->epid); +} + +static int xhci_hle_control(XHCIState *xhci, XHCITransfer *xfer, + uint8_t bmRequestType, uint8_t bRequest, + uint16_t wValue, uint16_t wIndex, uint16_t wLength) +{ + uint16_t type_req = (bmRequestType << 8) | bRequest; + + switch (type_req) { + case 0x0000 | USB_REQ_SET_CONFIGURATION: + DPRINTF("xhci: HLE switch configuration\n"); + return xhci_switch_config(xhci, xfer->slotid, wValue) == 0; + case 0x0100 | USB_REQ_SET_INTERFACE: + DPRINTF("xhci: HLE set interface altsetting\n"); + return xhci_set_iface_alt(xhci, xfer->slotid, wIndex, wValue) == 0; + case 0x0200 | USB_REQ_CLEAR_FEATURE: + if (wValue == 0) { // endpoint halt + DPRINTF("xhci: HLE clear halt\n"); + return xhci_clear_halt(xhci, xfer->slotid, wIndex); + } + case 0x0000 | USB_REQ_SET_ADDRESS: + fprintf(stderr, "xhci: warn: illegal SET_ADDRESS request\n"); + return 0; + default: + return 0; + } +} +#endif + +static int xhci_setup_packet(XHCITransfer *xfer, USBDevice *dev) +{ + USBEndpoint *ep; + int dir; + + dir = xfer->in_xfer ? USB_TOKEN_IN : USB_TOKEN_OUT; + ep = usb_ep_get(dev, dir, xfer->epid >> 1); + usb_packet_setup(&xfer->packet, dir, ep); + usb_packet_addbuf(&xfer->packet, xfer->data, xfer->data_length); + DPRINTF("xhci: setup packet pid 0x%x addr %d ep %d\n", + xfer->packet.pid, dev->addr, ep->nr); + return 0; +} + +static int xhci_complete_packet(XHCITransfer *xfer, int ret) +{ + if (ret == USB_RET_ASYNC) { + xfer->running_async = 1; + xfer->running_retry = 0; + xfer->complete = 0; + xfer->cancelled = 0; + return 0; + } else if (ret == USB_RET_NAK) { + xfer->running_async = 0; + xfer->running_retry = 1; + xfer->complete = 0; + xfer->cancelled = 0; + return 0; + } else { + xfer->running_async = 0; + xfer->running_retry = 0; + xfer->complete = 1; + } + + if (ret >= 0) { + xfer->status = CC_SUCCESS; + xhci_xfer_data(xfer, xfer->data, ret, xfer->in_xfer, 0, 1); + return 0; + } + + /* error */ + switch (ret) { + case USB_RET_NODEV: + xfer->status = CC_USB_TRANSACTION_ERROR; + xhci_xfer_data(xfer, xfer->data, 0, xfer->in_xfer, 0, 1); + xhci_stall_ep(xfer); + break; + case USB_RET_STALL: + xfer->status = CC_STALL_ERROR; + xhci_xfer_data(xfer, xfer->data, 0, xfer->in_xfer, 0, 1); + xhci_stall_ep(xfer); + break; + default: + fprintf(stderr, "%s: FIXME: ret = %d\n", __FUNCTION__, ret); + FIXME(); + } + return 0; +} + +static USBDevice *xhci_find_device(XHCIPort *port, uint8_t addr) +{ + if (!(port->portsc & PORTSC_PED)) { + return NULL; + } + return usb_find_device(&port->port, addr); +} + +static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer) +{ + XHCITRB *trb_setup, *trb_status; + uint8_t bmRequestType; + uint16_t wLength; + XHCIPort *port; + USBDevice *dev; + int ret; + + DPRINTF("xhci_fire_ctl_transfer(slot=%d)\n", xfer->slotid); + + trb_setup = &xfer->trbs[0]; + trb_status = &xfer->trbs[xfer->trb_count-1]; + + /* at most one Event Data TRB allowed after STATUS */ + if (TRB_TYPE(*trb_status) == TR_EVDATA && xfer->trb_count > 2) { + trb_status--; + } + + /* do some sanity checks */ + if (TRB_TYPE(*trb_setup) != TR_SETUP) { + fprintf(stderr, "xhci: ep0 first TD not SETUP: %d\n", + TRB_TYPE(*trb_setup)); + return -1; + } + if (TRB_TYPE(*trb_status) != TR_STATUS) { + fprintf(stderr, "xhci: ep0 last TD not STATUS: %d\n", + TRB_TYPE(*trb_status)); + return -1; + } + if (!(trb_setup->control & TRB_TR_IDT)) { + fprintf(stderr, "xhci: Setup TRB doesn't have IDT set\n"); + return -1; + } + if ((trb_setup->status & 0x1ffff) != 8) { + fprintf(stderr, "xhci: Setup TRB has bad length (%d)\n", + (trb_setup->status & 0x1ffff)); + return -1; + } + + bmRequestType = trb_setup->parameter; + wLength = trb_setup->parameter >> 48; + + if (xfer->data && xfer->data_alloced < wLength) { + xfer->data_alloced = 0; + g_free(xfer->data); + xfer->data = NULL; + } + if (!xfer->data) { + DPRINTF("xhci: alloc %d bytes data\n", wLength); + xfer->data = g_malloc(wLength+1); + xfer->data_alloced = wLength; + } + xfer->data_length = wLength; + + port = &xhci->ports[xhci->slots[xfer->slotid-1].port-1]; + dev = xhci_find_device(port, xhci->slots[xfer->slotid-1].devaddr); + if (!dev) { + fprintf(stderr, "xhci: slot %d port %d has no device\n", xfer->slotid, + xhci->slots[xfer->slotid-1].port); + return -1; + } + + xfer->in_xfer = bmRequestType & USB_DIR_IN; + xfer->iso_xfer = false; + + xhci_setup_packet(xfer, dev); + xfer->packet.parameter = trb_setup->parameter; + if (!xfer->in_xfer) { + xhci_xfer_data(xfer, xfer->data, wLength, 0, 1, 0); + } + + ret = usb_handle_packet(dev, &xfer->packet); + + xhci_complete_packet(xfer, ret); + if (!xfer->running_async && !xfer->running_retry) { + xhci_kick_ep(xhci, xfer->slotid, xfer->epid); + } + return 0; +} + +static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx) +{ + XHCIPort *port; + USBDevice *dev; + int ret; + + DPRINTF("xhci_submit(slotid=%d,epid=%d)\n", xfer->slotid, xfer->epid); + + xfer->in_xfer = epctx->type>>2; + + if (xfer->data && xfer->data_alloced < xfer->data_length) { + xfer->data_alloced = 0; + g_free(xfer->data); + xfer->data = NULL; + } + if (!xfer->data && xfer->data_length) { + DPRINTF("xhci: alloc %d bytes data\n", xfer->data_length); + xfer->data = g_malloc(xfer->data_length); + xfer->data_alloced = xfer->data_length; + } + if (epctx->type == ET_ISO_IN || epctx->type == ET_ISO_OUT) { + if (!xfer->bg_xfer) { + xfer->pkts = 1; + } + } else { + xfer->pkts = 0; + } + + port = &xhci->ports[xhci->slots[xfer->slotid-1].port-1]; + dev = xhci_find_device(port, xhci->slots[xfer->slotid-1].devaddr); + if (!dev) { + fprintf(stderr, "xhci: slot %d port %d has no device\n", xfer->slotid, + xhci->slots[xfer->slotid-1].port); + return -1; + } + + xhci_setup_packet(xfer, dev); + + switch(epctx->type) { + case ET_INTR_OUT: + case ET_INTR_IN: + case ET_BULK_OUT: + case ET_BULK_IN: + break; + case ET_ISO_OUT: + case ET_ISO_IN: + FIXME(); + break; + default: + fprintf(stderr, "xhci: unknown or unhandled EP " + "(type %d, in %d, ep %02x)\n", + epctx->type, xfer->in_xfer, xfer->epid); + return -1; + } + + if (!xfer->in_xfer) { + xhci_xfer_data(xfer, xfer->data, xfer->data_length, 0, 1, 0); + } + ret = usb_handle_packet(dev, &xfer->packet); + + xhci_complete_packet(xfer, ret); + if (!xfer->running_async && !xfer->running_retry) { + xhci_kick_ep(xhci, xfer->slotid, xfer->epid); + } + return 0; +} + +static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx) +{ + int i; + unsigned int length = 0; + XHCITRB *trb; + + DPRINTF("xhci_fire_transfer(slotid=%d,epid=%d)\n", xfer->slotid, xfer->epid); + + for (i = 0; i < xfer->trb_count; i++) { + trb = &xfer->trbs[i]; + if (TRB_TYPE(*trb) == TR_NORMAL || TRB_TYPE(*trb) == TR_ISOCH) { + length += trb->status & 0x1ffff; + } + } + DPRINTF("xhci: total TD length=%d\n", length); + + if (!epctx->has_bg) { + xfer->data_length = length; + xfer->backgrounded = 0; + return xhci_submit(xhci, xfer, epctx); + } else { + if (!epctx->bg_running) { + for (i = 0; i < BG_XFERS; i++) { + XHCITransfer *t = &epctx->bg_transfers[i]; + t->xhci = xhci; + t->epid = xfer->epid; + t->slotid = xfer->slotid; + t->pkts = BG_PKTS; + t->pktsize = epctx->max_psize; + t->data_length = t->pkts * t->pktsize; + t->bg_xfer = 1; + if (xhci_submit(xhci, t, epctx) < 0) { + fprintf(stderr, "xhci: bg submit failed\n"); + return -1; + } + } + epctx->bg_running = 1; + } + xfer->backgrounded = 1; + xhci_bg_update(xhci, epctx); + return 0; + } +} + +static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, unsigned int epid) +{ + XHCIEPContext *epctx; + int length; + int i; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(epid >= 1 && epid <= 31); + DPRINTF("xhci_kick_ep(%d, %d)\n", slotid, epid); + + if (!xhci->slots[slotid-1].enabled) { + fprintf(stderr, "xhci: xhci_kick_ep for disabled slot %d\n", slotid); + return; + } + epctx = xhci->slots[slotid-1].eps[epid-1]; + if (!epctx) { + fprintf(stderr, "xhci: xhci_kick_ep for disabled endpoint %d,%d\n", + epid, slotid); + return; + } + + if (epctx->retry) { + /* retry nak'ed transfer */ + XHCITransfer *xfer = epctx->retry; + int result; + + DPRINTF("xhci: retry nack'ed transfer ...\n"); + assert(xfer->running_retry); + xhci_setup_packet(xfer, xfer->packet.ep->dev); + result = usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); + if (result == USB_RET_NAK) { + DPRINTF("xhci: ... xfer still nacked\n"); + return; + } + DPRINTF("xhci: ... result %d\n", result); + xhci_complete_packet(xfer, result); + assert(!xfer->running_retry); + epctx->retry = NULL; + } + + if (epctx->state == EP_HALTED) { + DPRINTF("xhci: ep halted, not running schedule\n"); + return; + } + + xhci_set_ep_state(xhci, epctx, EP_RUNNING); + + while (1) { + XHCITransfer *xfer = &epctx->transfers[epctx->next_xfer]; + if (xfer->running_async || xfer->running_retry || xfer->backgrounded) { + DPRINTF("xhci: ep is busy (#%d,%d,%d,%d)\n", + epctx->next_xfer, xfer->running_async, + xfer->running_retry, xfer->backgrounded); + break; + } else { + DPRINTF("xhci: ep: using #%d\n", epctx->next_xfer); + } + length = xhci_ring_chain_length(xhci, &epctx->ring); + if (length < 0) { + DPRINTF("xhci: incomplete TD (%d TRBs)\n", -length); + break; + } else if (length == 0) { + break; + } + DPRINTF("xhci: fetching %d-TRB TD\n", length); + if (xfer->trbs && xfer->trb_alloced < length) { + xfer->trb_count = 0; + xfer->trb_alloced = 0; + g_free(xfer->trbs); + xfer->trbs = NULL; + } + if (!xfer->trbs) { + xfer->trbs = g_malloc(sizeof(XHCITRB) * length); + xfer->trb_alloced = length; + } + xfer->trb_count = length; + + for (i = 0; i < length; i++) { + assert(xhci_ring_fetch(xhci, &epctx->ring, &xfer->trbs[i], NULL)); + } + xfer->xhci = xhci; + xfer->epid = epid; + xfer->slotid = slotid; + + if (epid == 1) { + if (xhci_fire_ctl_transfer(xhci, xfer) >= 0) { + epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; + } else { + fprintf(stderr, "xhci: error firing CTL transfer\n"); + } + } else { + if (xhci_fire_transfer(xhci, xfer, epctx) >= 0) { + epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; + } else { + fprintf(stderr, "xhci: error firing data transfer\n"); + } + } + + if (epctx->state == EP_HALTED) { + DPRINTF("xhci: ep halted, stopping schedule\n"); + break; + } + if (xfer->running_retry) { + DPRINTF("xhci: xfer nacked, stopping schedule\n"); + epctx->retry = xfer; + break; + } + } +} + +static TRBCCode xhci_enable_slot(XHCIState *xhci, unsigned int slotid) +{ + assert(slotid >= 1 && slotid <= MAXSLOTS); + DPRINTF("xhci_enable_slot(%d)\n", slotid); + xhci->slots[slotid-1].enabled = 1; + xhci->slots[slotid-1].port = 0; + memset(xhci->slots[slotid-1].eps, 0, sizeof(XHCIEPContext*)*31); + + return CC_SUCCESS; +} + +static TRBCCode xhci_disable_slot(XHCIState *xhci, unsigned int slotid) +{ + int i; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + DPRINTF("xhci_disable_slot(%d)\n", slotid); + + for (i = 1; i <= 31; i++) { + if (xhci->slots[slotid-1].eps[i-1]) { + xhci_disable_ep(xhci, slotid, i); + } + } + + xhci->slots[slotid-1].enabled = 0; + return CC_SUCCESS; +} + +static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid, + uint64_t pictx, bool bsr) +{ + XHCISlot *slot; + USBDevice *dev; + target_phys_addr_t ictx, octx, dcbaap; + uint64_t poctx; + uint32_t ictl_ctx[2]; + uint32_t slot_ctx[4]; + uint32_t ep0_ctx[5]; + unsigned int port; + int i; + TRBCCode res; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + DPRINTF("xhci_address_slot(%d)\n", slotid); + + dcbaap = xhci_addr64(xhci->dcbaap_low, xhci->dcbaap_high); + cpu_physical_memory_read(dcbaap + 8*slotid, + (uint8_t *) &poctx, sizeof(poctx)); + ictx = xhci_mask64(pictx); + octx = xhci_mask64(le64_to_cpu(poctx)); + + DPRINTF("xhci: input context at "TARGET_FMT_plx"\n", ictx); + DPRINTF("xhci: output context at "TARGET_FMT_plx"\n", octx); + + cpu_physical_memory_read(ictx, (uint8_t *) ictl_ctx, sizeof(ictl_ctx)); + + if (ictl_ctx[0] != 0x0 || ictl_ctx[1] != 0x3) { + fprintf(stderr, "xhci: invalid input context control %08x %08x\n", + ictl_ctx[0], ictl_ctx[1]); + return CC_TRB_ERROR; + } + + cpu_physical_memory_read(ictx+32, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + cpu_physical_memory_read(ictx+64, (uint8_t *) ep0_ctx, sizeof(ep0_ctx)); + + DPRINTF("xhci: input slot context: %08x %08x %08x %08x\n", + slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + + DPRINTF("xhci: input ep0 context: %08x %08x %08x %08x %08x\n", + ep0_ctx[0], ep0_ctx[1], ep0_ctx[2], ep0_ctx[3], ep0_ctx[4]); + + port = (slot_ctx[1]>>16) & 0xFF; + dev = xhci->ports[port-1].port.dev; + + if (port < 1 || port > MAXPORTS) { + fprintf(stderr, "xhci: bad port %d\n", port); + return CC_TRB_ERROR; + } else if (!dev) { + fprintf(stderr, "xhci: port %d not connected\n", port); + return CC_USB_TRANSACTION_ERROR; + } + + for (i = 0; i < MAXSLOTS; i++) { + if (xhci->slots[i].port == port) { + fprintf(stderr, "xhci: port %d already assigned to slot %d\n", + port, i+1); + return CC_TRB_ERROR; + } + } + + slot = &xhci->slots[slotid-1]; + slot->port = port; + slot->ctx = octx; + + if (bsr) { + slot_ctx[3] = SLOT_DEFAULT << SLOT_STATE_SHIFT; + } else { + slot->devaddr = xhci->devaddr++; + slot_ctx[3] = (SLOT_ADDRESSED << SLOT_STATE_SHIFT) | slot->devaddr; + DPRINTF("xhci: device address is %d\n", slot->devaddr); + usb_device_handle_control(dev, NULL, + DeviceOutRequest | USB_REQ_SET_ADDRESS, + slot->devaddr, 0, 0, NULL); + } + + res = xhci_enable_ep(xhci, slotid, 1, octx+32, ep0_ctx); + + DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", + slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + DPRINTF("xhci: output ep0 context: %08x %08x %08x %08x %08x\n", + ep0_ctx[0], ep0_ctx[1], ep0_ctx[2], ep0_ctx[3], ep0_ctx[4]); + + cpu_physical_memory_write(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + cpu_physical_memory_write(octx+32, (uint8_t *) ep0_ctx, sizeof(ep0_ctx)); + + return res; +} + + +static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, + uint64_t pictx, bool dc) +{ + target_phys_addr_t ictx, octx; + uint32_t ictl_ctx[2]; + uint32_t slot_ctx[4]; + uint32_t islot_ctx[4]; + uint32_t ep_ctx[5]; + int i; + TRBCCode res; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + DPRINTF("xhci_configure_slot(%d)\n", slotid); + + ictx = xhci_mask64(pictx); + octx = xhci->slots[slotid-1].ctx; + + DPRINTF("xhci: input context at "TARGET_FMT_plx"\n", ictx); + DPRINTF("xhci: output context at "TARGET_FMT_plx"\n", octx); + + if (dc) { + for (i = 2; i <= 31; i++) { + if (xhci->slots[slotid-1].eps[i-1]) { + xhci_disable_ep(xhci, slotid, i); + } + } + + cpu_physical_memory_read(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + slot_ctx[3] &= ~(SLOT_STATE_MASK << SLOT_STATE_SHIFT); + slot_ctx[3] |= SLOT_ADDRESSED << SLOT_STATE_SHIFT; + DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", + slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + cpu_physical_memory_write(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + + return CC_SUCCESS; + } + + cpu_physical_memory_read(ictx, (uint8_t *) ictl_ctx, sizeof(ictl_ctx)); + + if ((ictl_ctx[0] & 0x3) != 0x0 || (ictl_ctx[1] & 0x3) != 0x1) { + fprintf(stderr, "xhci: invalid input context control %08x %08x\n", + ictl_ctx[0], ictl_ctx[1]); + return CC_TRB_ERROR; + } + + cpu_physical_memory_read(ictx+32, (uint8_t *) islot_ctx, sizeof(islot_ctx)); + cpu_physical_memory_read(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + + if (SLOT_STATE(slot_ctx[3]) < SLOT_ADDRESSED) { + fprintf(stderr, "xhci: invalid slot state %08x\n", slot_ctx[3]); + return CC_CONTEXT_STATE_ERROR; + } + + for (i = 2; i <= 31; i++) { + if (ictl_ctx[0] & (1<<i)) { + xhci_disable_ep(xhci, slotid, i); + } + if (ictl_ctx[1] & (1<<i)) { + cpu_physical_memory_read(ictx+32+(32*i), + (uint8_t *) ep_ctx, sizeof(ep_ctx)); + DPRINTF("xhci: input ep%d.%d context: %08x %08x %08x %08x %08x\n", + i/2, i%2, ep_ctx[0], ep_ctx[1], ep_ctx[2], + ep_ctx[3], ep_ctx[4]); + xhci_disable_ep(xhci, slotid, i); + res = xhci_enable_ep(xhci, slotid, i, octx+(32*i), ep_ctx); + if (res != CC_SUCCESS) { + return res; + } + DPRINTF("xhci: output ep%d.%d context: %08x %08x %08x %08x %08x\n", + i/2, i%2, ep_ctx[0], ep_ctx[1], ep_ctx[2], + ep_ctx[3], ep_ctx[4]); + cpu_physical_memory_write(octx+(32*i), + (uint8_t *) ep_ctx, sizeof(ep_ctx)); + } + } + + slot_ctx[3] &= ~(SLOT_STATE_MASK << SLOT_STATE_SHIFT); + slot_ctx[3] |= SLOT_CONFIGURED << SLOT_STATE_SHIFT; + slot_ctx[0] &= ~(SLOT_CONTEXT_ENTRIES_MASK << SLOT_CONTEXT_ENTRIES_SHIFT); + slot_ctx[0] |= islot_ctx[0] & (SLOT_CONTEXT_ENTRIES_MASK << + SLOT_CONTEXT_ENTRIES_SHIFT); + DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", + slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + + cpu_physical_memory_write(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + + return CC_SUCCESS; +} + + +static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid, + uint64_t pictx) +{ + target_phys_addr_t ictx, octx; + uint32_t ictl_ctx[2]; + uint32_t iep0_ctx[5]; + uint32_t ep0_ctx[5]; + uint32_t islot_ctx[4]; + uint32_t slot_ctx[4]; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + DPRINTF("xhci_evaluate_slot(%d)\n", slotid); + + ictx = xhci_mask64(pictx); + octx = xhci->slots[slotid-1].ctx; + + DPRINTF("xhci: input context at "TARGET_FMT_plx"\n", ictx); + DPRINTF("xhci: output context at "TARGET_FMT_plx"\n", octx); + + cpu_physical_memory_read(ictx, (uint8_t *) ictl_ctx, sizeof(ictl_ctx)); + + if (ictl_ctx[0] != 0x0 || ictl_ctx[1] & ~0x3) { + fprintf(stderr, "xhci: invalid input context control %08x %08x\n", + ictl_ctx[0], ictl_ctx[1]); + return CC_TRB_ERROR; + } + + if (ictl_ctx[1] & 0x1) { + cpu_physical_memory_read(ictx+32, + (uint8_t *) islot_ctx, sizeof(islot_ctx)); + + DPRINTF("xhci: input slot context: %08x %08x %08x %08x\n", + islot_ctx[0], islot_ctx[1], islot_ctx[2], islot_ctx[3]); + + cpu_physical_memory_read(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + + slot_ctx[1] &= ~0xFFFF; /* max exit latency */ + slot_ctx[1] |= islot_ctx[1] & 0xFFFF; + slot_ctx[2] &= ~0xFF00000; /* interrupter target */ + slot_ctx[2] |= islot_ctx[2] & 0xFF000000; + + DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", + slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + + cpu_physical_memory_write(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + } + + if (ictl_ctx[1] & 0x2) { + cpu_physical_memory_read(ictx+64, + (uint8_t *) iep0_ctx, sizeof(iep0_ctx)); + + DPRINTF("xhci: input ep0 context: %08x %08x %08x %08x %08x\n", + iep0_ctx[0], iep0_ctx[1], iep0_ctx[2], + iep0_ctx[3], iep0_ctx[4]); + + cpu_physical_memory_read(octx+32, (uint8_t *) ep0_ctx, sizeof(ep0_ctx)); + + ep0_ctx[1] &= ~0xFFFF0000; /* max packet size*/ + ep0_ctx[1] |= iep0_ctx[1] & 0xFFFF0000; + + DPRINTF("xhci: output ep0 context: %08x %08x %08x %08x %08x\n", + ep0_ctx[0], ep0_ctx[1], ep0_ctx[2], ep0_ctx[3], ep0_ctx[4]); + + cpu_physical_memory_write(octx+32, + (uint8_t *) ep0_ctx, sizeof(ep0_ctx)); + } + + return CC_SUCCESS; +} + +static TRBCCode xhci_reset_slot(XHCIState *xhci, unsigned int slotid) +{ + uint32_t slot_ctx[4]; + target_phys_addr_t octx; + int i; + + assert(slotid >= 1 && slotid <= MAXSLOTS); + DPRINTF("xhci_reset_slot(%d)\n", slotid); + + octx = xhci->slots[slotid-1].ctx; + + DPRINTF("xhci: output context at "TARGET_FMT_plx"\n", octx); + + for (i = 2; i <= 31; i++) { + if (xhci->slots[slotid-1].eps[i-1]) { + xhci_disable_ep(xhci, slotid, i); + } + } + + cpu_physical_memory_read(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + slot_ctx[3] &= ~(SLOT_STATE_MASK << SLOT_STATE_SHIFT); + slot_ctx[3] |= SLOT_DEFAULT << SLOT_STATE_SHIFT; + DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", + slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + cpu_physical_memory_write(octx, (uint8_t *) slot_ctx, sizeof(slot_ctx)); + + return CC_SUCCESS; +} + +static unsigned int xhci_get_slot(XHCIState *xhci, XHCIEvent *event, XHCITRB *trb) +{ + unsigned int slotid; + slotid = (trb->control >> TRB_CR_SLOTID_SHIFT) & TRB_CR_SLOTID_MASK; + if (slotid < 1 || slotid > MAXSLOTS) { + fprintf(stderr, "xhci: bad slot id %d\n", slotid); + event->ccode = CC_TRB_ERROR; + return 0; + } else if (!xhci->slots[slotid-1].enabled) { + fprintf(stderr, "xhci: slot id %d not enabled\n", slotid); + event->ccode = CC_SLOT_NOT_ENABLED_ERROR; + return 0; + } + return slotid; +} + +static TRBCCode xhci_get_port_bandwidth(XHCIState *xhci, uint64_t pctx) +{ + target_phys_addr_t ctx; + uint8_t bw_ctx[MAXPORTS+1]; + + DPRINTF("xhci_get_port_bandwidth()\n"); + + ctx = xhci_mask64(pctx); + + DPRINTF("xhci: bandwidth context at "TARGET_FMT_plx"\n", ctx); + + /* TODO: actually implement real values here */ + bw_ctx[0] = 0; + memset(&bw_ctx[1], 80, MAXPORTS); /* 80% */ + cpu_physical_memory_write(ctx, bw_ctx, sizeof(bw_ctx)); + + return CC_SUCCESS; +} + +static uint32_t rotl(uint32_t v, unsigned count) +{ + count &= 31; + return (v << count) | (v >> (32 - count)); +} + + +static uint32_t xhci_nec_challenge(uint32_t hi, uint32_t lo) +{ + uint32_t val; + val = rotl(lo - 0x49434878, 32 - ((hi>>8) & 0x1F)); + val += rotl(lo + 0x49434878, hi & 0x1F); + val -= rotl(hi ^ 0x49434878, (lo >> 16) & 0x1F); + return ~val; +} + +static void xhci_via_challenge(uint64_t addr) +{ + uint32_t buf[8]; + uint32_t obuf[8]; + target_phys_addr_t paddr = xhci_mask64(addr); + + cpu_physical_memory_read(paddr, (uint8_t *) &buf, 32); + + memcpy(obuf, buf, sizeof(obuf)); + + if ((buf[0] & 0xff) == 2) { + obuf[0] = 0x49932000 + 0x54dc200 * buf[2] + 0x7429b578 * buf[3]; + obuf[0] |= (buf[2] * buf[3]) & 0xff; + obuf[1] = 0x0132bb37 + 0xe89 * buf[2] + 0xf09 * buf[3]; + obuf[2] = 0x0066c2e9 + 0x2091 * buf[2] + 0x19bd * buf[3]; + obuf[3] = 0xd5281342 + 0x2cc9691 * buf[2] + 0x2367662 * buf[3]; + obuf[4] = 0x0123c75c + 0x1595 * buf[2] + 0x19ec * buf[3]; + obuf[5] = 0x00f695de + 0x26fd * buf[2] + 0x3e9 * buf[3]; + obuf[6] = obuf[2] ^ obuf[3] ^ 0x29472956; + obuf[7] = obuf[2] ^ obuf[3] ^ 0x65866593; + } + + cpu_physical_memory_write(paddr, (uint8_t *) &obuf, 32); +} + +static void xhci_process_commands(XHCIState *xhci) +{ + XHCITRB trb; + TRBType type; + XHCIEvent event = {ER_COMMAND_COMPLETE, CC_SUCCESS}; + target_phys_addr_t addr; + unsigned int i, slotid = 0; + + DPRINTF("xhci_process_commands()\n"); + if (!xhci_running(xhci)) { + DPRINTF("xhci_process_commands() called while xHC stopped or paused\n"); + return; + } + + xhci->crcr_low |= CRCR_CRR; + + while ((type = xhci_ring_fetch(xhci, &xhci->cmd_ring, &trb, &addr))) { + event.ptr = addr; + switch (type) { + case CR_ENABLE_SLOT: + for (i = 0; i < MAXSLOTS; i++) { + if (!xhci->slots[i].enabled) { + break; + } + } + if (i >= MAXSLOTS) { + fprintf(stderr, "xhci: no device slots available\n"); + event.ccode = CC_NO_SLOTS_ERROR; + } else { + slotid = i+1; + event.ccode = xhci_enable_slot(xhci, slotid); + } + break; + case CR_DISABLE_SLOT: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + event.ccode = xhci_disable_slot(xhci, slotid); + } + break; + case CR_ADDRESS_DEVICE: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + event.ccode = xhci_address_slot(xhci, slotid, trb.parameter, + trb.control & TRB_CR_BSR); + } + break; + case CR_CONFIGURE_ENDPOINT: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + event.ccode = xhci_configure_slot(xhci, slotid, trb.parameter, + trb.control & TRB_CR_DC); + } + break; + case CR_EVALUATE_CONTEXT: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + event.ccode = xhci_evaluate_slot(xhci, slotid, trb.parameter); + } + break; + case CR_STOP_ENDPOINT: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT) + & TRB_CR_EPID_MASK; + event.ccode = xhci_stop_ep(xhci, slotid, epid); + } + break; + case CR_RESET_ENDPOINT: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT) + & TRB_CR_EPID_MASK; + event.ccode = xhci_reset_ep(xhci, slotid, epid); + } + break; + case CR_SET_TR_DEQUEUE: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT) + & TRB_CR_EPID_MASK; + event.ccode = xhci_set_ep_dequeue(xhci, slotid, epid, + trb.parameter); + } + break; + case CR_RESET_DEVICE: + slotid = xhci_get_slot(xhci, &event, &trb); + if (slotid) { + event.ccode = xhci_reset_slot(xhci, slotid); + } + break; + case CR_GET_PORT_BANDWIDTH: + event.ccode = xhci_get_port_bandwidth(xhci, trb.parameter); + break; + case CR_VENDOR_VIA_CHALLENGE_RESPONSE: + xhci_via_challenge(trb.parameter); + break; + case CR_VENDOR_NEC_FIRMWARE_REVISION: + event.type = 48; /* NEC reply */ + event.length = 0x3025; + break; + case CR_VENDOR_NEC_CHALLENGE_RESPONSE: + { + uint32_t chi = trb.parameter >> 32; + uint32_t clo = trb.parameter; + uint32_t val = xhci_nec_challenge(chi, clo); + event.length = val & 0xFFFF; + event.epid = val >> 16; + slotid = val >> 24; + event.type = 48; /* NEC reply */ + } + break; + default: + fprintf(stderr, "xhci: unimplemented command %d\n", type); + event.ccode = CC_TRB_ERROR; + break; + } + event.slotid = slotid; + xhci_event(xhci, &event); + } +} + +static void xhci_update_port(XHCIState *xhci, XHCIPort *port, int is_detach) +{ + int nr = port->port.index + 1; + + port->portsc = PORTSC_PP; + if (port->port.dev && port->port.dev->attached && !is_detach) { + port->portsc |= PORTSC_CCS; + switch (port->port.dev->speed) { + case USB_SPEED_LOW: + port->portsc |= PORTSC_SPEED_LOW; + break; + case USB_SPEED_FULL: + port->portsc |= PORTSC_SPEED_FULL; + break; + case USB_SPEED_HIGH: + port->portsc |= PORTSC_SPEED_HIGH; + break; + } + } + + if (xhci_running(xhci)) { + port->portsc |= PORTSC_CSC; + XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS, nr << 24}; + xhci_event(xhci, &ev); + DPRINTF("xhci: port change event for port %d\n", nr); + } +} + +static void xhci_reset(void *opaque) +{ + XHCIState *xhci = opaque; + int i; + + DPRINTF("xhci: full reset\n"); + if (!(xhci->usbsts & USBSTS_HCH)) { + fprintf(stderr, "xhci: reset while running!\n"); + } + + xhci->usbcmd = 0; + xhci->usbsts = USBSTS_HCH; + xhci->dnctrl = 0; + xhci->crcr_low = 0; + xhci->crcr_high = 0; + xhci->dcbaap_low = 0; + xhci->dcbaap_high = 0; + xhci->config = 0; + xhci->devaddr = 2; + + for (i = 0; i < MAXSLOTS; i++) { + xhci_disable_slot(xhci, i+1); + } + + for (i = 0; i < MAXPORTS; i++) { + xhci_update_port(xhci, xhci->ports + i, 0); + } + + xhci->mfindex = 0; + xhci->iman = 0; + xhci->imod = 0; + xhci->erstsz = 0; + xhci->erstba_low = 0; + xhci->erstba_high = 0; + xhci->erdp_low = 0; + xhci->erdp_high = 0; + + xhci->er_ep_idx = 0; + xhci->er_pcs = 1; + xhci->er_full = 0; + xhci->ev_buffer_put = 0; + xhci->ev_buffer_get = 0; +} + +static uint32_t xhci_cap_read(XHCIState *xhci, uint32_t reg) +{ + DPRINTF("xhci_cap_read(0x%x)\n", reg); + + switch (reg) { + case 0x00: /* HCIVERSION, CAPLENGTH */ + return 0x01000000 | LEN_CAP; + case 0x04: /* HCSPARAMS 1 */ + return (MAXPORTS<<24) | (MAXINTRS<<8) | MAXSLOTS; + case 0x08: /* HCSPARAMS 2 */ + return 0x0000000f; + case 0x0c: /* HCSPARAMS 3 */ + return 0x00000000; + case 0x10: /* HCCPARAMS */ +#if TARGET_PHYS_ADDR_BITS > 32 + return 0x00081001; +#else + return 0x00081000; +#endif + case 0x14: /* DBOFF */ + return OFF_DOORBELL; + case 0x18: /* RTSOFF */ + return OFF_RUNTIME; + + /* extended capabilities */ + case 0x20: /* Supported Protocol:00 */ +#if USB3_PORTS > 0 + return 0x02000402; /* USB 2.0 */ +#else + return 0x02000002; /* USB 2.0 */ +#endif + case 0x24: /* Supported Protocol:04 */ + return 0x20425455; /* "USB " */ + case 0x28: /* Supported Protocol:08 */ + return 0x00000001 | (USB2_PORTS<<8); + case 0x2c: /* Supported Protocol:0c */ + return 0x00000000; /* reserved */ +#if USB3_PORTS > 0 + case 0x30: /* Supported Protocol:00 */ + return 0x03000002; /* USB 3.0 */ + case 0x34: /* Supported Protocol:04 */ + return 0x20425455; /* "USB " */ + case 0x38: /* Supported Protocol:08 */ + return 0x00000000 | (USB2_PORTS+1) | (USB3_PORTS<<8); + case 0x3c: /* Supported Protocol:0c */ + return 0x00000000; /* reserved */ +#endif + default: + fprintf(stderr, "xhci_cap_read: reg %d unimplemented\n", reg); + } + return 0; +} + +static uint32_t xhci_port_read(XHCIState *xhci, uint32_t reg) +{ + uint32_t port = reg >> 4; + if (port >= MAXPORTS) { + fprintf(stderr, "xhci_port_read: port %d out of bounds\n", port); + return 0; + } + + switch (reg & 0xf) { + case 0x00: /* PORTSC */ + return xhci->ports[port].portsc; + case 0x04: /* PORTPMSC */ + case 0x08: /* PORTLI */ + return 0; + case 0x0c: /* reserved */ + default: + fprintf(stderr, "xhci_port_read (port %d): reg 0x%x unimplemented\n", + port, reg); + return 0; + } +} + +static void xhci_port_write(XHCIState *xhci, uint32_t reg, uint32_t val) +{ + uint32_t port = reg >> 4; + uint32_t portsc; + + if (port >= MAXPORTS) { + fprintf(stderr, "xhci_port_read: port %d out of bounds\n", port); + return; + } + + switch (reg & 0xf) { + case 0x00: /* PORTSC */ + portsc = xhci->ports[port].portsc; + /* write-1-to-clear bits*/ + portsc &= ~(val & (PORTSC_CSC|PORTSC_PEC|PORTSC_WRC|PORTSC_OCC| + PORTSC_PRC|PORTSC_PLC|PORTSC_CEC)); + if (val & PORTSC_LWS) { + /* overwrite PLS only when LWS=1 */ + portsc &= ~(PORTSC_PLS_MASK << PORTSC_PLS_SHIFT); + portsc |= val & (PORTSC_PLS_MASK << PORTSC_PLS_SHIFT); + } + /* read/write bits */ + portsc &= ~(PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE); + portsc |= (val & (PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE)); + /* write-1-to-start bits */ + if (val & PORTSC_PR) { + DPRINTF("xhci: port %d reset\n", port); + usb_device_reset(xhci->ports[port].port.dev); + portsc |= PORTSC_PRC | PORTSC_PED; + } + xhci->ports[port].portsc = portsc; + break; + case 0x04: /* PORTPMSC */ + case 0x08: /* PORTLI */ + default: + fprintf(stderr, "xhci_port_write (port %d): reg 0x%x unimplemented\n", + port, reg); + } +} + +static uint32_t xhci_oper_read(XHCIState *xhci, uint32_t reg) +{ + DPRINTF("xhci_oper_read(0x%x)\n", reg); + + if (reg >= 0x400) { + return xhci_port_read(xhci, reg - 0x400); + } + + switch (reg) { + case 0x00: /* USBCMD */ + return xhci->usbcmd; + case 0x04: /* USBSTS */ + return xhci->usbsts; + case 0x08: /* PAGESIZE */ + return 1; /* 4KiB */ + case 0x14: /* DNCTRL */ + return xhci->dnctrl; + case 0x18: /* CRCR low */ + return xhci->crcr_low & ~0xe; + case 0x1c: /* CRCR high */ + return xhci->crcr_high; + case 0x30: /* DCBAAP low */ + return xhci->dcbaap_low; + case 0x34: /* DCBAAP high */ + return xhci->dcbaap_high; + case 0x38: /* CONFIG */ + return xhci->config; + default: + fprintf(stderr, "xhci_oper_read: reg 0x%x unimplemented\n", reg); + } + return 0; +} + +static void xhci_oper_write(XHCIState *xhci, uint32_t reg, uint32_t val) +{ + DPRINTF("xhci_oper_write(0x%x, 0x%08x)\n", reg, val); + + if (reg >= 0x400) { + xhci_port_write(xhci, reg - 0x400, val); + return; + } + + switch (reg) { + case 0x00: /* USBCMD */ + if ((val & USBCMD_RS) && !(xhci->usbcmd & USBCMD_RS)) { + xhci_run(xhci); + } else if (!(val & USBCMD_RS) && (xhci->usbcmd & USBCMD_RS)) { + xhci_stop(xhci); + } + xhci->usbcmd = val & 0xc0f; + if (val & USBCMD_HCRST) { + xhci_reset(xhci); + } + xhci_irq_update(xhci); + break; + + case 0x04: /* USBSTS */ + /* these bits are write-1-to-clear */ + xhci->usbsts &= ~(val & (USBSTS_HSE|USBSTS_EINT|USBSTS_PCD|USBSTS_SRE)); + xhci_irq_update(xhci); + break; + + case 0x14: /* DNCTRL */ + xhci->dnctrl = val & 0xffff; + break; + case 0x18: /* CRCR low */ + xhci->crcr_low = (val & 0xffffffcf) | (xhci->crcr_low & CRCR_CRR); + break; + case 0x1c: /* CRCR high */ + xhci->crcr_high = val; + if (xhci->crcr_low & (CRCR_CA|CRCR_CS) && (xhci->crcr_low & CRCR_CRR)) { + XHCIEvent event = {ER_COMMAND_COMPLETE, CC_COMMAND_RING_STOPPED}; + xhci->crcr_low &= ~CRCR_CRR; + xhci_event(xhci, &event); + DPRINTF("xhci: command ring stopped (CRCR=%08x)\n", xhci->crcr_low); + } else { + target_phys_addr_t base = xhci_addr64(xhci->crcr_low & ~0x3f, val); + xhci_ring_init(xhci, &xhci->cmd_ring, base); + } + xhci->crcr_low &= ~(CRCR_CA | CRCR_CS); + break; + case 0x30: /* DCBAAP low */ + xhci->dcbaap_low = val & 0xffffffc0; + break; + case 0x34: /* DCBAAP high */ + xhci->dcbaap_high = val; + break; + case 0x38: /* CONFIG */ + xhci->config = val & 0xff; + break; + default: + fprintf(stderr, "xhci_oper_write: reg 0x%x unimplemented\n", reg); + } +} + +static uint32_t xhci_runtime_read(XHCIState *xhci, uint32_t reg) +{ + DPRINTF("xhci_runtime_read(0x%x)\n", reg); + + switch (reg) { + case 0x00: /* MFINDEX */ + fprintf(stderr, "xhci_runtime_read: MFINDEX not yet implemented\n"); + return xhci->mfindex; + case 0x20: /* IMAN */ + return xhci->iman; + case 0x24: /* IMOD */ + return xhci->imod; + case 0x28: /* ERSTSZ */ + return xhci->erstsz; + case 0x30: /* ERSTBA low */ + return xhci->erstba_low; + case 0x34: /* ERSTBA high */ + return xhci->erstba_high; + case 0x38: /* ERDP low */ + return xhci->erdp_low; + case 0x3c: /* ERDP high */ + return xhci->erdp_high; + default: + fprintf(stderr, "xhci_runtime_read: reg 0x%x unimplemented\n", reg); + } + return 0; +} + +static void xhci_runtime_write(XHCIState *xhci, uint32_t reg, uint32_t val) +{ + DPRINTF("xhci_runtime_write(0x%x, 0x%08x)\n", reg, val); + + switch (reg) { + case 0x20: /* IMAN */ + if (val & IMAN_IP) { + xhci->iman &= ~IMAN_IP; + } + xhci->iman &= ~IMAN_IE; + xhci->iman |= val & IMAN_IE; + xhci_irq_update(xhci); + break; + case 0x24: /* IMOD */ + xhci->imod = val; + break; + case 0x28: /* ERSTSZ */ + xhci->erstsz = val & 0xffff; + break; + case 0x30: /* ERSTBA low */ + /* XXX NEC driver bug: it doesn't align this to 64 bytes + xhci->erstba_low = val & 0xffffffc0; */ + xhci->erstba_low = val & 0xfffffff0; + break; + case 0x34: /* ERSTBA high */ + xhci->erstba_high = val; + xhci_er_reset(xhci); + break; + case 0x38: /* ERDP low */ + if (val & ERDP_EHB) { + xhci->erdp_low &= ~ERDP_EHB; + } + xhci->erdp_low = (val & ~ERDP_EHB) | (xhci->erdp_low & ERDP_EHB); + break; + case 0x3c: /* ERDP high */ + xhci->erdp_high = val; + xhci_events_update(xhci); + break; + default: + fprintf(stderr, "xhci_oper_write: reg 0x%x unimplemented\n", reg); + } +} + +static uint32_t xhci_doorbell_read(XHCIState *xhci, uint32_t reg) +{ + DPRINTF("xhci_doorbell_read(0x%x)\n", reg); + /* doorbells always read as 0 */ + return 0; +} + +static void xhci_doorbell_write(XHCIState *xhci, uint32_t reg, uint32_t val) +{ + DPRINTF("xhci_doorbell_write(0x%x, 0x%08x)\n", reg, val); + + if (!xhci_running(xhci)) { + fprintf(stderr, "xhci: wrote doorbell while xHC stopped or paused\n"); + return; + } + + reg >>= 2; + + if (reg == 0) { + if (val == 0) { + xhci_process_commands(xhci); + } else { + fprintf(stderr, "xhci: bad doorbell 0 write: 0x%x\n", val); + } + } else { + if (reg > MAXSLOTS) { + fprintf(stderr, "xhci: bad doorbell %d\n", reg); + } else if (val > 31) { + fprintf(stderr, "xhci: bad doorbell %d write: 0x%x\n", reg, val); + } else { + xhci_kick_ep(xhci, reg, val); + } + } +} + +static uint64_t xhci_mem_read(void *ptr, target_phys_addr_t addr, + unsigned size) +{ + XHCIState *xhci = ptr; + + /* Only aligned reads are allowed on xHCI */ + if (addr & 3) { + fprintf(stderr, "xhci_mem_read: Mis-aligned read\n"); + return 0; + } + + if (addr < LEN_CAP) { + return xhci_cap_read(xhci, addr); + } else if (addr >= OFF_OPER && addr < (OFF_OPER + LEN_OPER)) { + return xhci_oper_read(xhci, addr - OFF_OPER); + } else if (addr >= OFF_RUNTIME && addr < (OFF_RUNTIME + LEN_RUNTIME)) { + return xhci_runtime_read(xhci, addr - OFF_RUNTIME); + } else if (addr >= OFF_DOORBELL && addr < (OFF_DOORBELL + LEN_DOORBELL)) { + return xhci_doorbell_read(xhci, addr - OFF_DOORBELL); + } else { + fprintf(stderr, "xhci_mem_read: Bad offset %x\n", (int)addr); + return 0; + } +} + +static void xhci_mem_write(void *ptr, target_phys_addr_t addr, + uint64_t val, unsigned size) +{ + XHCIState *xhci = ptr; + + /* Only aligned writes are allowed on xHCI */ + if (addr & 3) { + fprintf(stderr, "xhci_mem_write: Mis-aligned write\n"); + return; + } + + if (addr >= OFF_OPER && addr < (OFF_OPER + LEN_OPER)) { + xhci_oper_write(xhci, addr - OFF_OPER, val); + } else if (addr >= OFF_RUNTIME && addr < (OFF_RUNTIME + LEN_RUNTIME)) { + xhci_runtime_write(xhci, addr - OFF_RUNTIME, val); + } else if (addr >= OFF_DOORBELL && addr < (OFF_DOORBELL + LEN_DOORBELL)) { + xhci_doorbell_write(xhci, addr - OFF_DOORBELL, val); + } else { + fprintf(stderr, "xhci_mem_write: Bad offset %x\n", (int)addr); + } +} + +static const MemoryRegionOps xhci_mem_ops = { + .read = xhci_mem_read, + .write = xhci_mem_write, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void xhci_attach(USBPort *usbport) +{ + XHCIState *xhci = usbport->opaque; + XHCIPort *port = &xhci->ports[usbport->index]; + + xhci_update_port(xhci, port, 0); +} + +static void xhci_detach(USBPort *usbport) +{ + XHCIState *xhci = usbport->opaque; + XHCIPort *port = &xhci->ports[usbport->index]; + + xhci_update_port(xhci, port, 1); +} + +static void xhci_wakeup(USBPort *usbport) +{ + XHCIState *xhci = usbport->opaque; + XHCIPort *port = &xhci->ports[usbport->index]; + int nr = port->port.index + 1; + XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS, nr << 24}; + uint32_t pls; + + pls = (port->portsc >> PORTSC_PLS_SHIFT) & PORTSC_PLS_MASK; + if (pls != 3) { + return; + } + port->portsc |= 0xf << PORTSC_PLS_SHIFT; + if (port->portsc & PORTSC_PLC) { + return; + } + port->portsc |= PORTSC_PLC; + xhci_event(xhci, &ev); +} + +static void xhci_complete(USBPort *port, USBPacket *packet) +{ + XHCITransfer *xfer = container_of(packet, XHCITransfer, packet); + + xhci_complete_packet(xfer, packet->result); + xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid); +} + +static void xhci_child_detach(USBPort *port, USBDevice *child) +{ + FIXME(); +} + +static USBPortOps xhci_port_ops = { + .attach = xhci_attach, + .detach = xhci_detach, + .wakeup = xhci_wakeup, + .complete = xhci_complete, + .child_detach = xhci_child_detach, +}; + +static int xhci_find_slotid(XHCIState *xhci, USBDevice *dev) +{ + XHCISlot *slot; + int slotid; + + for (slotid = 1; slotid <= MAXSLOTS; slotid++) { + slot = &xhci->slots[slotid-1]; + if (slot->devaddr == dev->addr) { + return slotid; + } + } + return 0; +} + +static int xhci_find_epid(USBEndpoint *ep) +{ + if (ep->nr == 0) { + return 1; + } + if (ep->pid == USB_TOKEN_IN) { + return ep->nr * 2 + 1; + } else { + return ep->nr * 2; + } +} + +static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep) +{ + XHCIState *xhci = container_of(bus, XHCIState, bus); + int slotid; + + DPRINTF("%s\n", __func__); + slotid = xhci_find_slotid(xhci, ep->dev); + if (slotid == 0 || !xhci->slots[slotid-1].enabled) { + DPRINTF("%s: oops, no slot for dev %d\n", __func__, ep->dev->addr); + return; + } + xhci_kick_ep(xhci, slotid, xhci_find_epid(ep)); +} + +static USBBusOps xhci_bus_ops = { + .wakeup_endpoint = xhci_wakeup_endpoint, +}; + +static void usb_xhci_init(XHCIState *xhci, DeviceState *dev) +{ + int i; + + xhci->usbsts = USBSTS_HCH; + + usb_bus_new(&xhci->bus, &xhci_bus_ops, &xhci->pci_dev.qdev); + + for (i = 0; i < MAXPORTS; i++) { + memset(&xhci->ports[i], 0, sizeof(xhci->ports[i])); + usb_register_port(&xhci->bus, &xhci->ports[i].port, xhci, i, + &xhci_port_ops, + USB_SPEED_MASK_LOW | + USB_SPEED_MASK_FULL | + USB_SPEED_MASK_HIGH); + } + for (i = 0; i < MAXSLOTS; i++) { + xhci->slots[i].enabled = 0; + } + + qemu_register_reset(xhci_reset, xhci); +} + +static int usb_xhci_initfn(struct PCIDevice *dev) +{ + int ret; + + XHCIState *xhci = DO_UPCAST(XHCIState, pci_dev, dev); + + xhci->pci_dev.config[PCI_CLASS_PROG] = 0x30; /* xHCI */ + xhci->pci_dev.config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin 1 */ + xhci->pci_dev.config[PCI_CACHE_LINE_SIZE] = 0x10; + xhci->pci_dev.config[0x60] = 0x30; /* release number */ + + usb_xhci_init(xhci, &dev->qdev); + + xhci->irq = xhci->pci_dev.irq[0]; + + memory_region_init_io(&xhci->mem, &xhci_mem_ops, xhci, + "xhci", LEN_REGS); + pci_register_bar(&xhci->pci_dev, 0, + PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64, + &xhci->mem); + + ret = pcie_cap_init(&xhci->pci_dev, 0xa0, PCI_EXP_TYPE_ENDPOINT, 0); + assert(ret >= 0); + + if (xhci->msi) { + ret = msi_init(&xhci->pci_dev, 0x70, 1, true, false); + assert(ret >= 0); + } + + return 0; +} + +static void xhci_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, + int len) +{ + XHCIState *xhci = DO_UPCAST(XHCIState, pci_dev, dev); + + pci_default_write_config(dev, addr, val, len); + if (xhci->msi) { + msi_write_config(dev, addr, val, len); + } +} + +static const VMStateDescription vmstate_xhci = { + .name = "xhci", + .unmigratable = 1, +}; + +static Property xhci_properties[] = { + DEFINE_PROP_UINT32("msi", XHCIState, msi, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xhci_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_xhci; + dc->props = xhci_properties; + k->init = usb_xhci_initfn; + k->vendor_id = PCI_VENDOR_ID_NEC; + k->device_id = PCI_DEVICE_ID_NEC_UPD720200; + k->class_id = PCI_CLASS_SERIAL_USB; + k->revision = 0x03; + k->is_express = 1; + k->config_write = xhci_write_config; +} + +static TypeInfo xhci_info = { + .name = "nec-usb-xhci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(XHCIState), + .class_init = xhci_class_init, +}; + +static void xhci_register_types(void) +{ + type_register_static(&xhci_info); +} + +type_init(xhci_register_types) diff --git a/hw/usb/host-bsd.c b/hw/usb/host-bsd.c new file mode 100644 index 0000000..ec26266 --- /dev/null +++ b/hw/usb/host-bsd.c @@ -0,0 +1,647 @@ +/* + * BSD host USB redirector + * + * Copyright (c) 2006 Lonnie Mendez + * Portions of code and concepts borrowed from + * usb-linux.c and libusb's bsd.c and are copyright their respective owners. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "monitor.h" +#include "hw/usb.h" + +/* usb.h declares these */ +#undef USB_SPEED_HIGH +#undef USB_SPEED_FULL +#undef USB_SPEED_LOW + +#include <sys/ioctl.h> +#ifndef __DragonFly__ +#include <dev/usb/usb.h> +#else +#include <bus/usb/usb.h> +#endif + +/* This value has maximum potential at 16. + * You should also set hw.usb.debug to gain + * more detailed view. + */ +//#define DEBUG +#define UGEN_DEBUG_LEVEL 0 + + +typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id, + int vendor_id, int product_id, + const char *product_name, int speed); +static int usb_host_find_device(int *pbus_num, int *paddr, + const char *devname); + +typedef struct USBHostDevice { + USBDevice dev; + int ep_fd[USB_MAX_ENDPOINTS]; + int devfd; + char devpath[32]; +} USBHostDevice; + + +static int ensure_ep_open(USBHostDevice *dev, int ep, int mode) +{ + char buf[32]; + int fd; + + /* Get the address for this endpoint */ + ep = UE_GET_ADDR(ep); + + if (dev->ep_fd[ep] < 0) { +#if defined(__FreeBSD__) || defined(__DragonFly__) + snprintf(buf, sizeof(buf) - 1, "%s.%d", dev->devpath, ep); +#else + snprintf(buf, sizeof(buf) - 1, "%s.%02d", dev->devpath, ep); +#endif + /* Try to open it O_RDWR first for those devices which have in and out + * endpoints with the same address (eg 0x02 and 0x82) + */ + fd = open(buf, O_RDWR); + if (fd < 0 && errno == ENXIO) + fd = open(buf, mode); + if (fd < 0) { +#ifdef DEBUG + printf("ensure_ep_open: failed to open device endpoint %s: %s\n", + buf, strerror(errno)); +#endif + } + dev->ep_fd[ep] = fd; + } + + return dev->ep_fd[ep]; +} + +static void ensure_eps_closed(USBHostDevice *dev) +{ + int epnum = 1; + + if (!dev) + return; + + while (epnum < USB_MAX_ENDPOINTS) { + if (dev->ep_fd[epnum] >= 0) { + close(dev->ep_fd[epnum]); + dev->ep_fd[epnum] = -1; + } + epnum++; + } +} + +static void usb_host_handle_reset(USBDevice *dev) +{ +#if 0 + USBHostDevice *s = (USBHostDevice *)dev; +#endif +} + +/* XXX: + * -check device states against transfer requests + * and return appropriate response + */ +static int usb_host_handle_control(USBDevice *dev, + USBPacket *p, + int request, + int value, + int index, + int length, + uint8_t *data) +{ + USBHostDevice *s = (USBHostDevice *)dev; + struct usb_ctl_request req; + struct usb_alt_interface aiface; + int ret, timeout = 50; + + if ((request >> 8) == UT_WRITE_DEVICE && + (request & 0xff) == UR_SET_ADDRESS) { + + /* specific SET_ADDRESS support */ + dev->addr = value; + return 0; + } else if ((request >> 8) == UT_WRITE_DEVICE && + (request & 0xff) == UR_SET_CONFIG) { + + ensure_eps_closed(s); /* can't do this without all eps closed */ + + ret = ioctl(s->devfd, USB_SET_CONFIG, &value); + if (ret < 0) { +#ifdef DEBUG + printf("handle_control: failed to set configuration - %s\n", + strerror(errno)); +#endif + return USB_RET_STALL; + } + + return 0; + } else if ((request >> 8) == UT_WRITE_INTERFACE && + (request & 0xff) == UR_SET_INTERFACE) { + + aiface.uai_interface_index = index; + aiface.uai_alt_no = value; + + ensure_eps_closed(s); /* can't do this without all eps closed */ + ret = ioctl(s->devfd, USB_SET_ALTINTERFACE, &aiface); + if (ret < 0) { +#ifdef DEBUG + printf("handle_control: failed to set alternate interface - %s\n", + strerror(errno)); +#endif + return USB_RET_STALL; + } + + return 0; + } else { + req.ucr_request.bmRequestType = request >> 8; + req.ucr_request.bRequest = request & 0xff; + USETW(req.ucr_request.wValue, value); + USETW(req.ucr_request.wIndex, index); + USETW(req.ucr_request.wLength, length); + req.ucr_data = data; + req.ucr_flags = USBD_SHORT_XFER_OK; + + ret = ioctl(s->devfd, USB_SET_TIMEOUT, &timeout); +#if defined(__NetBSD__) || defined(__OpenBSD__) + if (ret < 0 && errno != EINVAL) { +#else + if (ret < 0) { +#endif +#ifdef DEBUG + printf("handle_control: setting timeout failed - %s\n", + strerror(errno)); +#endif + } + + ret = ioctl(s->devfd, USB_DO_REQUEST, &req); + /* ugen returns EIO for usbd_do_request_ no matter what + * happens with the transfer */ + if (ret < 0) { +#ifdef DEBUG + printf("handle_control: error after request - %s\n", + strerror(errno)); +#endif + return USB_RET_NAK; // STALL + } else { + return req.ucr_actlen; + } + } +} + +static int usb_host_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHostDevice *s = (USBHostDevice *)dev; + int ret, fd, mode; + int one = 1, shortpacket = 0, timeout = 50; + sigset_t new_mask, old_mask; + uint8_t devep = p->ep->nr; + + /* protect data transfers from SIGALRM signal */ + sigemptyset(&new_mask); + sigaddset(&new_mask, SIGALRM); + sigprocmask(SIG_BLOCK, &new_mask, &old_mask); + + if (p->pid == USB_TOKEN_IN) { + devep |= 0x80; + mode = O_RDONLY; + shortpacket = 1; + } else { + mode = O_WRONLY; + } + + fd = ensure_ep_open(s, devep, mode); + if (fd < 0) { + sigprocmask(SIG_SETMASK, &old_mask, NULL); + return USB_RET_NODEV; + } + + if (ioctl(fd, USB_SET_TIMEOUT, &timeout) < 0) { +#ifdef DEBUG + printf("handle_data: failed to set timeout - %s\n", + strerror(errno)); +#endif + } + + if (shortpacket) { + if (ioctl(fd, USB_SET_SHORT_XFER, &one) < 0) { +#ifdef DEBUG + printf("handle_data: failed to set short xfer mode - %s\n", + strerror(errno)); +#endif + sigprocmask(SIG_SETMASK, &old_mask, NULL); + } + } + + if (p->pid == USB_TOKEN_IN) + ret = readv(fd, p->iov.iov, p->iov.niov); + else + ret = writev(fd, p->iov.iov, p->iov.niov); + + sigprocmask(SIG_SETMASK, &old_mask, NULL); + + if (ret < 0) { +#ifdef DEBUG + printf("handle_data: error after %s data - %s\n", + pid == USB_TOKEN_IN ? "reading" : "writing", strerror(errno)); +#endif + switch(errno) { + case ETIMEDOUT: + case EINTR: + return USB_RET_NAK; + default: + return USB_RET_STALL; + } + } else { + return ret; + } +} + +static void usb_host_handle_destroy(USBDevice *opaque) +{ + USBHostDevice *s = (USBHostDevice *)opaque; + int i; + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + if (s->ep_fd[i] >= 0) + close(s->ep_fd[i]); + + if (s->devfd < 0) + return; + + close(s->devfd); + + g_free(s); +} + +static int usb_host_initfn(USBDevice *dev) +{ + return 0; +} + +USBDevice *usb_host_device_open(USBBus *guest_bus, const char *devname) +{ + struct usb_device_info bus_info, dev_info; + USBDevice *d = NULL, *ret = NULL; + USBHostDevice *dev; + char ctlpath[PATH_MAX + 1]; + char buspath[PATH_MAX + 1]; + int bfd, dfd, bus, address, i; + int ugendebug = UGEN_DEBUG_LEVEL; + + if (usb_host_find_device(&bus, &address, devname) < 0) { + goto fail; + } + + snprintf(buspath, PATH_MAX, "/dev/usb%d", bus); + + bfd = open(buspath, O_RDWR); + if (bfd < 0) { +#ifdef DEBUG + printf("usb_host_device_open: failed to open usb bus - %s\n", + strerror(errno)); +#endif + goto fail; + } + + bus_info.udi_addr = address; + if (ioctl(bfd, USB_DEVICEINFO, &bus_info) < 0) { +#ifdef DEBUG + printf("usb_host_device_open: failed to grab bus information - %s\n", + strerror(errno)); +#endif + goto fail_bfd; + } + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + snprintf(ctlpath, PATH_MAX, "/dev/%s", bus_info.udi_devnames[0]); +#else + snprintf(ctlpath, PATH_MAX, "/dev/%s.00", bus_info.udi_devnames[0]); +#endif + + dfd = open(ctlpath, O_RDWR); + if (dfd < 0) { + dfd = open(ctlpath, O_RDONLY); + if (dfd < 0) { +#ifdef DEBUG + printf("usb_host_device_open: failed to open usb device %s - %s\n", + ctlpath, strerror(errno)); +#endif + } + goto fail_dfd; + } + + if (ioctl(dfd, USB_GET_DEVICEINFO, &dev_info) < 0) { +#ifdef DEBUG + printf("usb_host_device_open: failed to grab device info - %s\n", + strerror(errno)); +#endif + goto fail_dfd; + } + + d = usb_create(guest_bus, "usb-host"); + dev = DO_UPCAST(USBHostDevice, dev, d); + + if (dev_info.udi_speed == 1) { + dev->dev.speed = USB_SPEED_LOW - 1; + dev->dev.speedmask = USB_SPEED_MASK_LOW; + } else { + dev->dev.speed = USB_SPEED_FULL - 1; + dev->dev.speedmask = USB_SPEED_MASK_FULL; + } + + if (strncmp(dev_info.udi_product, "product", 7) != 0) { + pstrcpy(dev->dev.product_desc, sizeof(dev->dev.product_desc), + dev_info.udi_product); + } else { + snprintf(dev->dev.product_desc, sizeof(dev->dev.product_desc), + "host:%s", devname); + } + + pstrcpy(dev->devpath, sizeof(dev->devpath), "/dev/"); + pstrcat(dev->devpath, sizeof(dev->devpath), dev_info.udi_devnames[0]); + + /* Mark the endpoints as not yet open */ + for (i = 0; i < USB_MAX_ENDPOINTS; i++) { + dev->ep_fd[i] = -1; + } + + ioctl(dfd, USB_SETDEBUG, &ugendebug); + + ret = (USBDevice *)dev; + +fail_dfd: + close(dfd); +fail_bfd: + close(bfd); +fail: + return ret; +} + +static void usb_host_class_initfn(ObjectClass *klass, void *data) +{ + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "USB Host Device"; + uc->init = usb_host_initfn; + uc->handle_reset = usb_host_handle_reset; + uc->handle_control = usb_host_handle_control; + uc->handle_data = usb_host_handle_data; + uc->handle_destroy = usb_host_handle_destroy; +} + +static TypeInfo usb_host_dev_info = { + .name = "usb-host", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHostDevice), + .class_init = usb_host_class_initfn, +}; + +static void usb_host_register_types(void) +{ + type_register_static(&usb_host_dev_info); +} + +type_init(usb_host_register_types) + +static int usb_host_scan(void *opaque, USBScanFunc *func) +{ + struct usb_device_info bus_info; + struct usb_device_info dev_info; + uint16_t vendor_id, product_id, class_id, speed; + int bfd, dfd, bus, address; + char busbuf[20], devbuf[20], product_name[256]; + int ret = 0; + + for (bus = 0; bus < 10; bus++) { + + snprintf(busbuf, sizeof(busbuf) - 1, "/dev/usb%d", bus); + bfd = open(busbuf, O_RDWR); + if (bfd < 0) + continue; + + for (address = 1; address < 127; address++) { + + bus_info.udi_addr = address; + if (ioctl(bfd, USB_DEVICEINFO, &bus_info) < 0) + continue; + + /* only list devices that can be used by generic layer */ + if (strncmp(bus_info.udi_devnames[0], "ugen", 4) != 0) + continue; + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + snprintf(devbuf, sizeof(devbuf) - 1, "/dev/%s", bus_info.udi_devnames[0]); +#else + snprintf(devbuf, sizeof(devbuf) - 1, "/dev/%s.00", bus_info.udi_devnames[0]); +#endif + + dfd = open(devbuf, O_RDONLY); + if (dfd < 0) { +#ifdef DEBUG + printf("usb_host_scan: couldn't open device %s - %s\n", devbuf, + strerror(errno)); +#endif + continue; + } + + if (ioctl(dfd, USB_GET_DEVICEINFO, &dev_info) < 0) + printf("usb_host_scan: couldn't get device information for %s - %s\n", + devbuf, strerror(errno)); + + /* XXX: might need to fixup endianness of word values before copying over */ + + vendor_id = dev_info.udi_vendorNo; + product_id = dev_info.udi_productNo; + class_id = dev_info.udi_class; + speed = dev_info.udi_speed; + + if (strncmp(dev_info.udi_product, "product", 7) != 0) + pstrcpy(product_name, sizeof(product_name), + dev_info.udi_product); + else + product_name[0] = '\0'; + + ret = func(opaque, bus, address, class_id, vendor_id, + product_id, product_name, speed); + + close(dfd); + + if (ret) + goto the_end; + } + + close(bfd); + } + +the_end: + return ret; +} + +typedef struct FindDeviceState { + int vendor_id; + int product_id; + int bus_num; + int addr; +} FindDeviceState; + +static int usb_host_find_device_scan(void *opaque, int bus_num, int addr, + int class_id, + int vendor_id, int product_id, + const char *product_name, int speed) +{ + FindDeviceState *s = opaque; + if (vendor_id == s->vendor_id && + product_id == s->product_id) { + s->bus_num = bus_num; + s->addr = addr; + return 1; + } else { + return 0; + } +} + + +/* the syntax is : + 'bus.addr' (decimal numbers) or + 'vendor_id:product_id' (hexa numbers) */ +static int usb_host_find_device(int *pbus_num, int *paddr, + const char *devname) +{ + const char *p; + int ret; + FindDeviceState fs; + + p = strchr(devname, '.'); + if (p) { + *pbus_num = strtoul(devname, NULL, 0); + *paddr = strtoul(p + 1, NULL, 0); + return 0; + } + p = strchr(devname, ':'); + if (p) { + fs.vendor_id = strtoul(devname, NULL, 16); + fs.product_id = strtoul(p + 1, NULL, 16); + ret = usb_host_scan(&fs, usb_host_find_device_scan); + if (ret) { + *pbus_num = fs.bus_num; + *paddr = fs.addr; + return 0; + } + } + return -1; +} + +/**********************/ +/* USB host device info */ + +struct usb_class_info { + int class; + const char *class_name; +}; + +static const struct usb_class_info usb_class_info[] = { + { USB_CLASS_AUDIO, "Audio"}, + { USB_CLASS_COMM, "Communication"}, + { USB_CLASS_HID, "HID"}, + { USB_CLASS_HUB, "Hub" }, + { USB_CLASS_PHYSICAL, "Physical" }, + { USB_CLASS_PRINTER, "Printer" }, + { USB_CLASS_MASS_STORAGE, "Storage" }, + { USB_CLASS_CDC_DATA, "Data" }, + { USB_CLASS_APP_SPEC, "Application Specific" }, + { USB_CLASS_VENDOR_SPEC, "Vendor Specific" }, + { USB_CLASS_STILL_IMAGE, "Still Image" }, + { USB_CLASS_CSCID, "Smart Card" }, + { USB_CLASS_CONTENT_SEC, "Content Security" }, + { -1, NULL } +}; + +static const char *usb_class_str(uint8_t class) +{ + const struct usb_class_info *p; + for (p = usb_class_info; p->class != -1; p++) { + if (p->class == class) + break; + } + return p->class_name; +} + +static void usb_info_device(Monitor *mon, int bus_num, int addr, int class_id, + int vendor_id, int product_id, + const char *product_name, + int speed) +{ + const char *class_str, *speed_str; + + switch(speed) { + case USB_SPEED_LOW: + speed_str = "1.5"; + break; + case USB_SPEED_FULL: + speed_str = "12"; + break; + case USB_SPEED_HIGH: + speed_str = "480"; + break; + default: + speed_str = "?"; + break; + } + + monitor_printf(mon, " Device %d.%d, speed %s Mb/s\n", + bus_num, addr, speed_str); + class_str = usb_class_str(class_id); + if (class_str) + monitor_printf(mon, " %s:", class_str); + else + monitor_printf(mon, " Class %02x:", class_id); + monitor_printf(mon, " USB device %04x:%04x", vendor_id, product_id); + if (product_name[0] != '\0') + monitor_printf(mon, ", %s", product_name); + monitor_printf(mon, "\n"); +} + +static int usb_host_info_device(void *opaque, + int bus_num, int addr, + int class_id, + int vendor_id, int product_id, + const char *product_name, + int speed) +{ + Monitor *mon = opaque; + + usb_info_device(mon, bus_num, addr, class_id, vendor_id, product_id, + product_name, speed); + return 0; +} + +void usb_host_info(Monitor *mon) +{ + usb_host_scan(mon, usb_host_info_device); +} + +/* XXX add this */ +int usb_host_device_close(const char *devname) +{ + return 0; +} diff --git a/hw/usb/host-linux.c b/hw/usb/host-linux.c new file mode 100644 index 0000000..90919c2 --- /dev/null +++ b/hw/usb/host-linux.c @@ -0,0 +1,1913 @@ +/* + * Linux host USB redirector + * + * Copyright (c) 2005 Fabrice Bellard + * + * Copyright (c) 2008 Max Krasnyansky + * Support for host device auto connect & disconnect + * Major rewrite to support fully async operation + * + * Copyright 2008 TJ <linux@tjworld.net> + * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition + * to the legacy /proc/bus/usb USB device discovery and handling + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "monitor.h" +#include "sysemu.h" +#include "trace.h" + +#include <dirent.h> +#include <sys/ioctl.h> + +#include <linux/usbdevice_fs.h> +#include <linux/version.h> +#include "hw/usb.h" + +/* We redefine it to avoid version problems */ +struct usb_ctrltransfer { + uint8_t bRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint32_t timeout; + void *data; +}; + +typedef int USBScanFunc(void *opaque, int bus_num, int addr, const char *port, + int class_id, int vendor_id, int product_id, + const char *product_name, int speed); + +//#define DEBUG + +#ifdef DEBUG +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +#define PRODUCT_NAME_SZ 32 +#define MAX_PORTLEN 16 + +/* endpoint association data */ +#define ISO_FRAME_DESC_PER_URB 32 + +/* devio.c limits single requests to 16k */ +#define MAX_USBFS_BUFFER_SIZE 16384 + +typedef struct AsyncURB AsyncURB; + +struct endp_data { + uint8_t halted; + uint8_t iso_started; + AsyncURB *iso_urb; + int iso_urb_idx; + int iso_buffer_used; + int inflight; +}; + +struct USBAutoFilter { + uint32_t bus_num; + uint32_t addr; + char *port; + uint32_t vendor_id; + uint32_t product_id; +}; + +typedef struct USBHostDevice { + USBDevice dev; + int fd; + int hub_fd; + int hub_port; + + uint8_t descr[8192]; + int descr_len; + int closing; + uint32_t iso_urb_count; + Notifier exit; + + struct endp_data ep_in[USB_MAX_ENDPOINTS]; + struct endp_data ep_out[USB_MAX_ENDPOINTS]; + QLIST_HEAD(, AsyncURB) aurbs; + + /* Host side address */ + int bus_num; + int addr; + char port[MAX_PORTLEN]; + struct USBAutoFilter match; + int seen, errcount; + + QTAILQ_ENTRY(USBHostDevice) next; +} USBHostDevice; + +static QTAILQ_HEAD(, USBHostDevice) hostdevs = QTAILQ_HEAD_INITIALIZER(hostdevs); + +static int usb_host_close(USBHostDevice *dev); +static int parse_filter(const char *spec, struct USBAutoFilter *f); +static void usb_host_auto_check(void *unused); +static int usb_host_read_file(char *line, size_t line_size, + const char *device_file, const char *device_name); +static int usb_linux_update_endp_table(USBHostDevice *s); + +static int usb_host_usbfs_type(USBHostDevice *s, USBPacket *p) +{ + static const int usbfs[] = { + [USB_ENDPOINT_XFER_CONTROL] = USBDEVFS_URB_TYPE_CONTROL, + [USB_ENDPOINT_XFER_ISOC] = USBDEVFS_URB_TYPE_ISO, + [USB_ENDPOINT_XFER_BULK] = USBDEVFS_URB_TYPE_BULK, + [USB_ENDPOINT_XFER_INT] = USBDEVFS_URB_TYPE_INTERRUPT, + }; + uint8_t type = p->ep->type; + assert(type < ARRAY_SIZE(usbfs)); + return usbfs[type]; +} + +static int usb_host_do_reset(USBHostDevice *dev) +{ + struct timeval s, e; + uint32_t usecs; + int ret; + + gettimeofday(&s, NULL); + ret = ioctl(dev->fd, USBDEVFS_RESET); + gettimeofday(&e, NULL); + usecs = (e.tv_sec - s.tv_sec) * 1000000; + usecs += e.tv_usec - s.tv_usec; + if (usecs > 1000000) { + /* more than a second, something is fishy, broken usb device? */ + fprintf(stderr, "husb: device %d:%d reset took %d.%06d seconds\n", + dev->bus_num, dev->addr, usecs / 1000000, usecs % 1000000); + } + return ret; +} + +static struct endp_data *get_endp(USBHostDevice *s, int pid, int ep) +{ + struct endp_data *eps = pid == USB_TOKEN_IN ? s->ep_in : s->ep_out; + assert(pid == USB_TOKEN_IN || pid == USB_TOKEN_OUT); + assert(ep > 0 && ep <= USB_MAX_ENDPOINTS); + return eps + ep - 1; +} + +static int is_isoc(USBHostDevice *s, int pid, int ep) +{ + return usb_ep_get_type(&s->dev, pid, ep) == USB_ENDPOINT_XFER_ISOC; +} + +static int is_valid(USBHostDevice *s, int pid, int ep) +{ + return usb_ep_get_type(&s->dev, pid, ep) != USB_ENDPOINT_XFER_INVALID; +} + +static int is_halted(USBHostDevice *s, int pid, int ep) +{ + return get_endp(s, pid, ep)->halted; +} + +static void clear_halt(USBHostDevice *s, int pid, int ep) +{ + trace_usb_host_ep_clear_halt(s->bus_num, s->addr, ep); + get_endp(s, pid, ep)->halted = 0; +} + +static void set_halt(USBHostDevice *s, int pid, int ep) +{ + if (ep != 0) { + trace_usb_host_ep_set_halt(s->bus_num, s->addr, ep); + get_endp(s, pid, ep)->halted = 1; + } +} + +static int is_iso_started(USBHostDevice *s, int pid, int ep) +{ + return get_endp(s, pid, ep)->iso_started; +} + +static void clear_iso_started(USBHostDevice *s, int pid, int ep) +{ + trace_usb_host_ep_stop_iso(s->bus_num, s->addr, ep); + get_endp(s, pid, ep)->iso_started = 0; +} + +static void set_iso_started(USBHostDevice *s, int pid, int ep) +{ + struct endp_data *e = get_endp(s, pid, ep); + + trace_usb_host_ep_start_iso(s->bus_num, s->addr, ep); + if (!e->iso_started) { + e->iso_started = 1; + e->inflight = 0; + } +} + +static int change_iso_inflight(USBHostDevice *s, int pid, int ep, int value) +{ + struct endp_data *e = get_endp(s, pid, ep); + + e->inflight += value; + return e->inflight; +} + +static void set_iso_urb(USBHostDevice *s, int pid, int ep, AsyncURB *iso_urb) +{ + get_endp(s, pid, ep)->iso_urb = iso_urb; +} + +static AsyncURB *get_iso_urb(USBHostDevice *s, int pid, int ep) +{ + return get_endp(s, pid, ep)->iso_urb; +} + +static void set_iso_urb_idx(USBHostDevice *s, int pid, int ep, int i) +{ + get_endp(s, pid, ep)->iso_urb_idx = i; +} + +static int get_iso_urb_idx(USBHostDevice *s, int pid, int ep) +{ + return get_endp(s, pid, ep)->iso_urb_idx; +} + +static void set_iso_buffer_used(USBHostDevice *s, int pid, int ep, int i) +{ + get_endp(s, pid, ep)->iso_buffer_used = i; +} + +static int get_iso_buffer_used(USBHostDevice *s, int pid, int ep) +{ + return get_endp(s, pid, ep)->iso_buffer_used; +} + +/* + * Async URB state. + * We always allocate iso packet descriptors even for bulk transfers + * to simplify allocation and casts. + */ +struct AsyncURB +{ + struct usbdevfs_urb urb; + struct usbdevfs_iso_packet_desc isocpd[ISO_FRAME_DESC_PER_URB]; + USBHostDevice *hdev; + QLIST_ENTRY(AsyncURB) next; + + /* For regular async urbs */ + USBPacket *packet; + int more; /* large transfer, more urbs follow */ + + /* For buffered iso handling */ + int iso_frame_idx; /* -1 means in flight */ +}; + +static AsyncURB *async_alloc(USBHostDevice *s) +{ + AsyncURB *aurb = g_malloc0(sizeof(AsyncURB)); + aurb->hdev = s; + QLIST_INSERT_HEAD(&s->aurbs, aurb, next); + return aurb; +} + +static void async_free(AsyncURB *aurb) +{ + QLIST_REMOVE(aurb, next); + g_free(aurb); +} + +static void do_disconnect(USBHostDevice *s) +{ + usb_host_close(s); + usb_host_auto_check(NULL); +} + +static void async_complete(void *opaque) +{ + USBHostDevice *s = opaque; + AsyncURB *aurb; + int urbs = 0; + + while (1) { + USBPacket *p; + + int r = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb); + if (r < 0) { + if (errno == EAGAIN) { + if (urbs > 2) { + fprintf(stderr, "husb: %d iso urbs finished at once\n", urbs); + } + return; + } + if (errno == ENODEV) { + if (!s->closing) { + trace_usb_host_disconnect(s->bus_num, s->addr); + do_disconnect(s); + } + return; + } + + perror("USBDEVFS_REAPURBNDELAY"); + return; + } + + DPRINTF("husb: async completed. aurb %p status %d alen %d\n", + aurb, aurb->urb.status, aurb->urb.actual_length); + + /* If this is a buffered iso urb mark it as complete and don't do + anything else (it is handled further in usb_host_handle_iso_data) */ + if (aurb->iso_frame_idx == -1) { + int inflight; + int pid = (aurb->urb.endpoint & USB_DIR_IN) ? + USB_TOKEN_IN : USB_TOKEN_OUT; + int ep = aurb->urb.endpoint & 0xf; + if (aurb->urb.status == -EPIPE) { + set_halt(s, pid, ep); + } + aurb->iso_frame_idx = 0; + urbs++; + inflight = change_iso_inflight(s, pid, ep, -1); + if (inflight == 0 && is_iso_started(s, pid, ep)) { + fprintf(stderr, "husb: out of buffers for iso stream\n"); + } + continue; + } + + p = aurb->packet; + trace_usb_host_urb_complete(s->bus_num, s->addr, aurb, aurb->urb.status, + aurb->urb.actual_length, aurb->more); + + if (p) { + switch (aurb->urb.status) { + case 0: + p->result += aurb->urb.actual_length; + break; + + case -EPIPE: + set_halt(s, p->pid, p->ep->nr); + p->result = USB_RET_STALL; + break; + + case -EOVERFLOW: + p->result = USB_RET_BABBLE; + break; + + default: + p->result = USB_RET_IOERROR; + break; + } + + if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) { + trace_usb_host_req_complete(s->bus_num, s->addr, p->result); + usb_generic_async_ctrl_complete(&s->dev, p); + } else if (!aurb->more) { + trace_usb_host_req_complete(s->bus_num, s->addr, p->result); + usb_packet_complete(&s->dev, p); + } + } + + async_free(aurb); + } +} + +static void usb_host_async_cancel(USBDevice *dev, USBPacket *p) +{ + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + AsyncURB *aurb; + + QLIST_FOREACH(aurb, &s->aurbs, next) { + if (p != aurb->packet) { + continue; + } + + DPRINTF("husb: async cancel: packet %p, aurb %p\n", p, aurb); + + /* Mark it as dead (see async_complete above) */ + aurb->packet = NULL; + + int r = ioctl(s->fd, USBDEVFS_DISCARDURB, aurb); + if (r < 0) { + DPRINTF("husb: async. discard urb failed errno %d\n", errno); + } + } +} + +static int usb_host_open_device(int bus, int addr) +{ + const char *usbfs = NULL; + char filename[32]; + struct stat st; + int fd, rc; + + rc = stat("/dev/bus/usb", &st); + if (rc == 0 && S_ISDIR(st.st_mode)) { + /* udev-created device nodes available */ + usbfs = "/dev/bus/usb"; + } else { + /* fallback: usbfs mounted below /proc */ + usbfs = "/proc/bus/usb"; + } + + snprintf(filename, sizeof(filename), "%s/%03d/%03d", + usbfs, bus, addr); + fd = open(filename, O_RDWR | O_NONBLOCK); + if (fd < 0) { + fprintf(stderr, "husb: open %s: %s\n", filename, strerror(errno)); + } + return fd; +} + +static int usb_host_claim_port(USBHostDevice *s) +{ +#ifdef USBDEVFS_CLAIM_PORT + char *h, hub_name[64], line[1024]; + int hub_addr, ret; + + snprintf(hub_name, sizeof(hub_name), "%d-%s", + s->match.bus_num, s->match.port); + + /* try strip off last ".$portnr" to get hub */ + h = strrchr(hub_name, '.'); + if (h != NULL) { + s->hub_port = atoi(h+1); + *h = '\0'; + } else { + /* no dot in there -> it is the root hub */ + snprintf(hub_name, sizeof(hub_name), "usb%d", + s->match.bus_num); + s->hub_port = atoi(s->match.port); + } + + if (!usb_host_read_file(line, sizeof(line), "devnum", + hub_name)) { + return -1; + } + if (sscanf(line, "%d", &hub_addr) != 1) { + return -1; + } + + s->hub_fd = usb_host_open_device(s->match.bus_num, hub_addr); + if (s->hub_fd < 0) { + return -1; + } + + ret = ioctl(s->hub_fd, USBDEVFS_CLAIM_PORT, &s->hub_port); + if (ret < 0) { + close(s->hub_fd); + s->hub_fd = -1; + return -1; + } + + trace_usb_host_claim_port(s->match.bus_num, hub_addr, s->hub_port); + return 0; +#else + return -1; +#endif +} + +static void usb_host_release_port(USBHostDevice *s) +{ + if (s->hub_fd == -1) { + return; + } +#ifdef USBDEVFS_RELEASE_PORT + ioctl(s->hub_fd, USBDEVFS_RELEASE_PORT, &s->hub_port); +#endif + close(s->hub_fd); + s->hub_fd = -1; +} + +static int usb_host_disconnect_ifaces(USBHostDevice *dev, int nb_interfaces) +{ + /* earlier Linux 2.4 do not support that */ +#ifdef USBDEVFS_DISCONNECT + struct usbdevfs_ioctl ctrl; + int ret, interface; + + for (interface = 0; interface < nb_interfaces; interface++) { + ctrl.ioctl_code = USBDEVFS_DISCONNECT; + ctrl.ifno = interface; + ctrl.data = 0; + ret = ioctl(dev->fd, USBDEVFS_IOCTL, &ctrl); + if (ret < 0 && errno != ENODATA) { + perror("USBDEVFS_DISCONNECT"); + return -1; + } + } +#endif + return 0; +} + +static int usb_linux_get_num_interfaces(USBHostDevice *s) +{ + char device_name[64], line[1024]; + int num_interfaces = 0; + + sprintf(device_name, "%d-%s", s->bus_num, s->port); + if (!usb_host_read_file(line, sizeof(line), "bNumInterfaces", + device_name)) { + return -1; + } + if (sscanf(line, "%d", &num_interfaces) != 1) { + return -1; + } + return num_interfaces; +} + +static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration) +{ + const char *op = NULL; + int dev_descr_len, config_descr_len; + int interface, nb_interfaces; + int ret, i; + + for (i = 0; i < USB_MAX_INTERFACES; i++) { + dev->dev.altsetting[i] = 0; + } + + if (configuration == 0) { /* address state - ignore */ + dev->dev.ninterfaces = 0; + dev->dev.configuration = 0; + return 1; + } + + DPRINTF("husb: claiming interfaces. config %d\n", configuration); + + i = 0; + dev_descr_len = dev->descr[0]; + if (dev_descr_len > dev->descr_len) { + fprintf(stderr, "husb: update iface failed. descr too short\n"); + return 0; + } + + i += dev_descr_len; + while (i < dev->descr_len) { + DPRINTF("husb: i is %d, descr_len is %d, dl %d, dt %d\n", + i, dev->descr_len, + dev->descr[i], dev->descr[i+1]); + + if (dev->descr[i+1] != USB_DT_CONFIG) { + i += dev->descr[i]; + continue; + } + config_descr_len = dev->descr[i]; + + DPRINTF("husb: config #%d need %d\n", dev->descr[i + 5], configuration); + + if (configuration == dev->descr[i + 5]) { + configuration = dev->descr[i + 5]; + break; + } + + i += config_descr_len; + } + + if (i >= dev->descr_len) { + fprintf(stderr, + "husb: update iface failed. no matching configuration\n"); + return 0; + } + nb_interfaces = dev->descr[i + 4]; + + if (usb_host_disconnect_ifaces(dev, nb_interfaces) < 0) { + goto fail; + } + + /* XXX: only grab if all interfaces are free */ + for (interface = 0; interface < nb_interfaces; interface++) { + op = "USBDEVFS_CLAIMINTERFACE"; + ret = ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface); + if (ret < 0) { + goto fail; + } + } + + trace_usb_host_claim_interfaces(dev->bus_num, dev->addr, + nb_interfaces, configuration); + + dev->dev.ninterfaces = nb_interfaces; + dev->dev.configuration = configuration; + return 1; + +fail: + if (errno == ENODEV) { + do_disconnect(dev); + } + perror(op); + return 0; +} + +static int usb_host_release_interfaces(USBHostDevice *s) +{ + int ret, i; + + trace_usb_host_release_interfaces(s->bus_num, s->addr); + + for (i = 0; i < s->dev.ninterfaces; i++) { + ret = ioctl(s->fd, USBDEVFS_RELEASEINTERFACE, &i); + if (ret < 0) { + perror("USBDEVFS_RELEASEINTERFACE"); + return 0; + } + } + return 1; +} + +static void usb_host_handle_reset(USBDevice *dev) +{ + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + + trace_usb_host_reset(s->bus_num, s->addr); + + usb_host_do_reset(s);; + + usb_host_claim_interfaces(s, 0); + usb_linux_update_endp_table(s); +} + +static void usb_host_handle_destroy(USBDevice *dev) +{ + USBHostDevice *s = (USBHostDevice *)dev; + + usb_host_release_port(s); + usb_host_close(s); + QTAILQ_REMOVE(&hostdevs, s, next); + qemu_remove_exit_notifier(&s->exit); +} + +/* iso data is special, we need to keep enough urbs in flight to make sure + that the controller never runs out of them, otherwise the device will + likely suffer a buffer underrun / overrun. */ +static AsyncURB *usb_host_alloc_iso(USBHostDevice *s, int pid, uint8_t ep) +{ + AsyncURB *aurb; + int i, j, len = usb_ep_get_max_packet_size(&s->dev, pid, ep); + + aurb = g_malloc0(s->iso_urb_count * sizeof(*aurb)); + for (i = 0; i < s->iso_urb_count; i++) { + aurb[i].urb.endpoint = ep; + aurb[i].urb.buffer_length = ISO_FRAME_DESC_PER_URB * len; + aurb[i].urb.buffer = g_malloc(aurb[i].urb.buffer_length); + aurb[i].urb.type = USBDEVFS_URB_TYPE_ISO; + aurb[i].urb.flags = USBDEVFS_URB_ISO_ASAP; + aurb[i].urb.number_of_packets = ISO_FRAME_DESC_PER_URB; + for (j = 0 ; j < ISO_FRAME_DESC_PER_URB; j++) + aurb[i].urb.iso_frame_desc[j].length = len; + if (pid == USB_TOKEN_IN) { + aurb[i].urb.endpoint |= 0x80; + /* Mark as fully consumed (idle) */ + aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB; + } + } + set_iso_urb(s, pid, ep, aurb); + + return aurb; +} + +static void usb_host_stop_n_free_iso(USBHostDevice *s, int pid, uint8_t ep) +{ + AsyncURB *aurb; + int i, ret, killed = 0, free = 1; + + aurb = get_iso_urb(s, pid, ep); + if (!aurb) { + return; + } + + for (i = 0; i < s->iso_urb_count; i++) { + /* in flight? */ + if (aurb[i].iso_frame_idx == -1) { + ret = ioctl(s->fd, USBDEVFS_DISCARDURB, &aurb[i]); + if (ret < 0) { + perror("USBDEVFS_DISCARDURB"); + free = 0; + continue; + } + killed++; + } + } + + /* Make sure any urbs we've killed are reaped before we free them */ + if (killed) { + async_complete(s); + } + + for (i = 0; i < s->iso_urb_count; i++) { + g_free(aurb[i].urb.buffer); + } + + if (free) + g_free(aurb); + else + printf("husb: leaking iso urbs because of discard failure\n"); + set_iso_urb(s, pid, ep, NULL); + set_iso_urb_idx(s, pid, ep, 0); + clear_iso_started(s, pid, ep); +} + +static int urb_status_to_usb_ret(int status) +{ + switch (status) { + case -EPIPE: + return USB_RET_STALL; + case -EOVERFLOW: + return USB_RET_BABBLE; + default: + return USB_RET_IOERROR; + } +} + +static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) +{ + AsyncURB *aurb; + int i, j, ret, max_packet_size, offset, len = 0; + uint8_t *buf; + + max_packet_size = p->ep->max_packet_size; + if (max_packet_size == 0) + return USB_RET_NAK; + + aurb = get_iso_urb(s, p->pid, p->ep->nr); + if (!aurb) { + aurb = usb_host_alloc_iso(s, p->pid, p->ep->nr); + } + + i = get_iso_urb_idx(s, p->pid, p->ep->nr); + j = aurb[i].iso_frame_idx; + if (j >= 0 && j < ISO_FRAME_DESC_PER_URB) { + if (in) { + /* Check urb status */ + if (aurb[i].urb.status) { + len = urb_status_to_usb_ret(aurb[i].urb.status); + /* Move to the next urb */ + aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1; + /* Check frame status */ + } else if (aurb[i].urb.iso_frame_desc[j].status) { + len = urb_status_to_usb_ret( + aurb[i].urb.iso_frame_desc[j].status); + /* Check the frame fits */ + } else if (aurb[i].urb.iso_frame_desc[j].actual_length + > p->iov.size) { + printf("husb: received iso data is larger then packet\n"); + len = USB_RET_BABBLE; + /* All good copy data over */ + } else { + len = aurb[i].urb.iso_frame_desc[j].actual_length; + buf = aurb[i].urb.buffer + + j * aurb[i].urb.iso_frame_desc[0].length; + usb_packet_copy(p, buf, len); + } + } else { + len = p->iov.size; + offset = (j == 0) ? 0 : get_iso_buffer_used(s, p->pid, p->ep->nr); + + /* Check the frame fits */ + if (len > max_packet_size) { + printf("husb: send iso data is larger then max packet size\n"); + return USB_RET_NAK; + } + + /* All good copy data over */ + usb_packet_copy(p, aurb[i].urb.buffer + offset, len); + aurb[i].urb.iso_frame_desc[j].length = len; + offset += len; + set_iso_buffer_used(s, p->pid, p->ep->nr, offset); + + /* Start the stream once we have buffered enough data */ + if (!is_iso_started(s, p->pid, p->ep->nr) && i == 1 && j == 8) { + set_iso_started(s, p->pid, p->ep->nr); + } + } + aurb[i].iso_frame_idx++; + if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) { + i = (i + 1) % s->iso_urb_count; + set_iso_urb_idx(s, p->pid, p->ep->nr, i); + } + } else { + if (in) { + set_iso_started(s, p->pid, p->ep->nr); + } else { + DPRINTF("hubs: iso out error no free buffer, dropping packet\n"); + } + } + + if (is_iso_started(s, p->pid, p->ep->nr)) { + /* (Re)-submit all fully consumed / filled urbs */ + for (i = 0; i < s->iso_urb_count; i++) { + if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) { + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]); + if (ret < 0) { + perror("USBDEVFS_SUBMITURB"); + if (!in || len == 0) { + switch(errno) { + case ETIMEDOUT: + len = USB_RET_NAK; + break; + case EPIPE: + default: + len = USB_RET_STALL; + } + } + break; + } + aurb[i].iso_frame_idx = -1; + change_iso_inflight(s, p->pid, p->ep->nr, 1); + } + } + } + + return len; +} + +static int usb_host_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + struct usbdevfs_urb *urb; + AsyncURB *aurb; + int ret, rem, prem, v; + uint8_t *pbuf; + uint8_t ep; + + trace_usb_host_req_data(s->bus_num, s->addr, + p->pid == USB_TOKEN_IN, + p->ep->nr, p->iov.size); + + if (!is_valid(s, p->pid, p->ep->nr)) { + trace_usb_host_req_complete(s->bus_num, s->addr, USB_RET_NAK); + return USB_RET_NAK; + } + + if (p->pid == USB_TOKEN_IN) { + ep = p->ep->nr | 0x80; + } else { + ep = p->ep->nr; + } + + if (is_halted(s, p->pid, p->ep->nr)) { + unsigned int arg = ep; + ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &arg); + if (ret < 0) { + perror("USBDEVFS_CLEAR_HALT"); + trace_usb_host_req_complete(s->bus_num, s->addr, USB_RET_NAK); + return USB_RET_NAK; + } + clear_halt(s, p->pid, p->ep->nr); + } + + if (is_isoc(s, p->pid, p->ep->nr)) { + return usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN); + } + + v = 0; + prem = p->iov.iov[v].iov_len; + pbuf = p->iov.iov[v].iov_base; + rem = p->iov.size; + while (rem) { + if (prem == 0) { + v++; + assert(v < p->iov.niov); + prem = p->iov.iov[v].iov_len; + pbuf = p->iov.iov[v].iov_base; + assert(prem <= rem); + } + aurb = async_alloc(s); + aurb->packet = p; + + urb = &aurb->urb; + urb->endpoint = ep; + urb->type = usb_host_usbfs_type(s, p); + urb->usercontext = s; + urb->buffer = pbuf; + urb->buffer_length = prem; + + if (urb->buffer_length > MAX_USBFS_BUFFER_SIZE) { + urb->buffer_length = MAX_USBFS_BUFFER_SIZE; + } + pbuf += urb->buffer_length; + prem -= urb->buffer_length; + rem -= urb->buffer_length; + if (rem) { + aurb->more = 1; + } + + trace_usb_host_urb_submit(s->bus_num, s->addr, aurb, + urb->buffer_length, aurb->more); + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); + + DPRINTF("husb: data submit: ep 0x%x, len %u, more %d, packet %p, aurb %p\n", + urb->endpoint, urb->buffer_length, aurb->more, p, aurb); + + if (ret < 0) { + perror("USBDEVFS_SUBMITURB"); + async_free(aurb); + + switch(errno) { + case ETIMEDOUT: + trace_usb_host_req_complete(s->bus_num, s->addr, USB_RET_NAK); + return USB_RET_NAK; + case EPIPE: + default: + trace_usb_host_req_complete(s->bus_num, s->addr, USB_RET_STALL); + return USB_RET_STALL; + } + } + } + + return USB_RET_ASYNC; +} + +static int ctrl_error(void) +{ + if (errno == ETIMEDOUT) { + return USB_RET_NAK; + } else { + return USB_RET_STALL; + } +} + +static int usb_host_set_address(USBHostDevice *s, int addr) +{ + trace_usb_host_set_address(s->bus_num, s->addr, addr); + s->dev.addr = addr; + return 0; +} + +static int usb_host_set_config(USBHostDevice *s, int config) +{ + int ret, first = 1; + + trace_usb_host_set_config(s->bus_num, s->addr, config); + + usb_host_release_interfaces(s); + +again: + ret = ioctl(s->fd, USBDEVFS_SETCONFIGURATION, &config); + + DPRINTF("husb: ctrl set config %d ret %d errno %d\n", config, ret, errno); + + if (ret < 0 && errno == EBUSY && first) { + /* happens if usb device is in use by host drivers */ + int count = usb_linux_get_num_interfaces(s); + if (count > 0) { + DPRINTF("husb: busy -> disconnecting %d interfaces\n", count); + usb_host_disconnect_ifaces(s, count); + first = 0; + goto again; + } + } + + if (ret < 0) { + return ctrl_error(); + } + usb_host_claim_interfaces(s, config); + usb_linux_update_endp_table(s); + return 0; +} + +static int usb_host_set_interface(USBHostDevice *s, int iface, int alt) +{ + struct usbdevfs_setinterface si; + int i, ret; + + trace_usb_host_set_interface(s->bus_num, s->addr, iface, alt); + + for (i = 1; i <= USB_MAX_ENDPOINTS; i++) { + if (is_isoc(s, USB_TOKEN_IN, i)) { + usb_host_stop_n_free_iso(s, USB_TOKEN_IN, i); + } + if (is_isoc(s, USB_TOKEN_OUT, i)) { + usb_host_stop_n_free_iso(s, USB_TOKEN_OUT, i); + } + } + + if (iface >= USB_MAX_INTERFACES) { + return USB_RET_STALL; + } + + si.interface = iface; + si.altsetting = alt; + ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si); + + DPRINTF("husb: ctrl set iface %d altset %d ret %d errno %d\n", + iface, alt, ret, errno); + + if (ret < 0) { + return ctrl_error(); + } + + s->dev.altsetting[iface] = alt; + usb_linux_update_endp_table(s); + return 0; +} + +static int usb_host_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + struct usbdevfs_urb *urb; + AsyncURB *aurb; + int ret; + + /* + * Process certain standard device requests. + * These are infrequent and are processed synchronously. + */ + + /* Note request is (bRequestType << 8) | bRequest */ + trace_usb_host_req_control(s->bus_num, s->addr, request, value, index); + + switch (request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + return usb_host_set_address(s, value); + + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + return usb_host_set_config(s, value & 0xff); + + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + return usb_host_set_interface(s, index, value); + } + + /* The rest are asynchronous */ + + if (length > sizeof(dev->data_buf)) { + fprintf(stderr, "husb: ctrl buffer too small (%d > %zu)\n", + length, sizeof(dev->data_buf)); + return USB_RET_STALL; + } + + aurb = async_alloc(s); + aurb->packet = p; + + /* + * Setup ctrl transfer. + * + * s->ctrl is laid out such that data buffer immediately follows + * 'req' struct which is exactly what usbdevfs expects. + */ + urb = &aurb->urb; + + urb->type = USBDEVFS_URB_TYPE_CONTROL; + urb->endpoint = p->ep->nr; + + urb->buffer = &dev->setup_buf; + urb->buffer_length = length + 8; + + urb->usercontext = s; + + trace_usb_host_urb_submit(s->bus_num, s->addr, aurb, + urb->buffer_length, aurb->more); + ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); + + DPRINTF("husb: submit ctrl. len %u aurb %p\n", urb->buffer_length, aurb); + + if (ret < 0) { + DPRINTF("husb: submit failed. errno %d\n", errno); + async_free(aurb); + + switch(errno) { + case ETIMEDOUT: + return USB_RET_NAK; + case EPIPE: + default: + return USB_RET_STALL; + } + } + + return USB_RET_ASYNC; +} + +static uint8_t usb_linux_get_alt_setting(USBHostDevice *s, + uint8_t configuration, uint8_t interface) +{ + char device_name[64], line[1024]; + int alt_setting; + + sprintf(device_name, "%d-%s:%d.%d", s->bus_num, s->port, + (int)configuration, (int)interface); + + if (!usb_host_read_file(line, sizeof(line), "bAlternateSetting", + device_name)) { + /* Assume alt 0 on error */ + return 0; + } + if (sscanf(line, "%d", &alt_setting) != 1) { + /* Assume alt 0 on error */ + return 0; + } + return alt_setting; +} + +/* returns 1 on problem encountered or 0 for success */ +static int usb_linux_update_endp_table(USBHostDevice *s) +{ + uint8_t *descriptors; + uint8_t devep, type, alt_interface; + uint16_t raw; + int interface, length, i, ep, pid; + struct endp_data *epd; + + usb_ep_init(&s->dev); + + if (s->dev.configuration == 0) { + /* not configured yet -- leave all endpoints disabled */ + return 0; + } + + /* get the desired configuration, interface, and endpoint descriptors + * from device description */ + descriptors = &s->descr[18]; + length = s->descr_len - 18; + i = 0; + + while (i < length) { + if (descriptors[i + 1] != USB_DT_CONFIG) { + fprintf(stderr, "invalid descriptor data\n"); + return 1; + } else if (descriptors[i + 5] != s->dev.configuration) { + DPRINTF("not requested configuration %d\n", s->dev.configuration); + i += (descriptors[i + 3] << 8) + descriptors[i + 2]; + continue; + } + i += descriptors[i]; + + if (descriptors[i + 1] != USB_DT_INTERFACE || + (descriptors[i + 1] == USB_DT_INTERFACE && + descriptors[i + 4] == 0)) { + i += descriptors[i]; + continue; + } + + interface = descriptors[i + 2]; + alt_interface = usb_linux_get_alt_setting(s, s->dev.configuration, + interface); + + /* the current interface descriptor is the active interface + * and has endpoints */ + if (descriptors[i + 3] != alt_interface) { + i += descriptors[i]; + continue; + } + + /* advance to the endpoints */ + while (i < length && descriptors[i +1] != USB_DT_ENDPOINT) { + i += descriptors[i]; + } + + if (i >= length) + break; + + while (i < length) { + if (descriptors[i + 1] != USB_DT_ENDPOINT) { + break; + } + + devep = descriptors[i + 2]; + pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT; + ep = devep & 0xf; + if (ep == 0) { + fprintf(stderr, "usb-linux: invalid ep descriptor, ep == 0\n"); + return 1; + } + + type = descriptors[i + 3] & 0x3; + raw = descriptors[i + 4] + (descriptors[i + 5] << 8); + usb_ep_set_max_packet_size(&s->dev, pid, ep, raw); + assert(usb_ep_get_type(&s->dev, pid, ep) == + USB_ENDPOINT_XFER_INVALID); + usb_ep_set_type(&s->dev, pid, ep, type); + usb_ep_set_ifnum(&s->dev, pid, ep, interface); + if (type == USB_ENDPOINT_XFER_BULK) { + usb_ep_set_pipeline(&s->dev, pid, ep, true); + } + + epd = get_endp(s, pid, ep); + epd->halted = 0; + + i += descriptors[i]; + } + } +#ifdef DEBUG + usb_ep_dump(&s->dev); +#endif + return 0; +} + +/* + * Check if we can safely redirect a usb2 device to a usb1 virtual controller, + * this function assumes this is safe, if: + * 1) There are no isoc endpoints + * 2) There are no interrupt endpoints with a max_packet_size > 64 + * Note bulk endpoints with a max_packet_size > 64 in theory also are not + * usb1 compatible, but in practice this seems to work fine. + */ +static int usb_linux_full_speed_compat(USBHostDevice *dev) +{ + int i, packet_size; + + /* + * usb_linux_update_endp_table only registers info about ep in the current + * interface altsettings, so we need to parse the descriptors again. + */ + for (i = 0; (i + 5) < dev->descr_len; i += dev->descr[i]) { + if (dev->descr[i + 1] == USB_DT_ENDPOINT) { + switch (dev->descr[i + 3] & 0x3) { + case 0x00: /* CONTROL */ + break; + case 0x01: /* ISO */ + return 0; + case 0x02: /* BULK */ + break; + case 0x03: /* INTERRUPT */ + packet_size = dev->descr[i + 4] + (dev->descr[i + 5] << 8); + if (packet_size > 64) + return 0; + break; + } + } + } + return 1; +} + +static int usb_host_open(USBHostDevice *dev, int bus_num, + int addr, const char *port, + const char *prod_name, int speed) +{ + int fd = -1, ret; + + trace_usb_host_open_started(bus_num, addr); + + if (dev->fd != -1) { + goto fail; + } + + fd = usb_host_open_device(bus_num, addr); + if (fd < 0) { + goto fail; + } + DPRINTF("husb: opened %s\n", buf); + + dev->bus_num = bus_num; + dev->addr = addr; + strcpy(dev->port, port); + dev->fd = fd; + + /* read the device description */ + dev->descr_len = read(fd, dev->descr, sizeof(dev->descr)); + if (dev->descr_len <= 0) { + perror("husb: reading device data failed"); + goto fail; + } + +#ifdef DEBUG + { + int x; + printf("=== begin dumping device descriptor data ===\n"); + for (x = 0; x < dev->descr_len; x++) { + printf("%02x ", dev->descr[x]); + } + printf("\n=== end dumping device descriptor data ===\n"); + } +#endif + + + /* start unconfigured -- we'll wait for the guest to set a configuration */ + if (!usb_host_claim_interfaces(dev, 0)) { + goto fail; + } + + ret = usb_linux_update_endp_table(dev); + if (ret) { + goto fail; + } + + if (speed == -1) { + struct usbdevfs_connectinfo ci; + + ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); + if (ret < 0) { + perror("usb_host_device_open: USBDEVFS_CONNECTINFO"); + goto fail; + } + + if (ci.slow) { + speed = USB_SPEED_LOW; + } else { + speed = USB_SPEED_HIGH; + } + } + dev->dev.speed = speed; + dev->dev.speedmask = (1 << speed); + if (dev->dev.speed == USB_SPEED_HIGH && usb_linux_full_speed_compat(dev)) { + dev->dev.speedmask |= USB_SPEED_MASK_FULL; + } + + trace_usb_host_open_success(bus_num, addr); + + if (!prod_name || prod_name[0] == '\0') { + snprintf(dev->dev.product_desc, sizeof(dev->dev.product_desc), + "host:%d.%d", bus_num, addr); + } else { + pstrcpy(dev->dev.product_desc, sizeof(dev->dev.product_desc), + prod_name); + } + + ret = usb_device_attach(&dev->dev); + if (ret) { + goto fail; + } + + /* USB devio uses 'write' flag to check for async completions */ + qemu_set_fd_handler(dev->fd, NULL, async_complete, dev); + + return 0; + +fail: + trace_usb_host_open_failure(bus_num, addr); + if (dev->fd != -1) { + close(dev->fd); + dev->fd = -1; + } + return -1; +} + +static int usb_host_close(USBHostDevice *dev) +{ + int i; + + if (dev->fd == -1) { + return -1; + } + + trace_usb_host_close(dev->bus_num, dev->addr); + + qemu_set_fd_handler(dev->fd, NULL, NULL, NULL); + dev->closing = 1; + for (i = 1; i <= USB_MAX_ENDPOINTS; i++) { + if (is_isoc(dev, USB_TOKEN_IN, i)) { + usb_host_stop_n_free_iso(dev, USB_TOKEN_IN, i); + } + if (is_isoc(dev, USB_TOKEN_OUT, i)) { + usb_host_stop_n_free_iso(dev, USB_TOKEN_OUT, i); + } + } + async_complete(dev); + dev->closing = 0; + if (dev->dev.attached) { + usb_device_detach(&dev->dev); + } + usb_host_do_reset(dev); + close(dev->fd); + dev->fd = -1; + return 0; +} + +static void usb_host_exit_notifier(struct Notifier *n, void *data) +{ + USBHostDevice *s = container_of(n, USBHostDevice, exit); + + usb_host_release_port(s); + if (s->fd != -1) { + usb_host_do_reset(s);; + } +} + +static int usb_host_initfn(USBDevice *dev) +{ + USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + + dev->auto_attach = 0; + s->fd = -1; + s->hub_fd = -1; + + QTAILQ_INSERT_TAIL(&hostdevs, s, next); + s->exit.notify = usb_host_exit_notifier; + qemu_add_exit_notifier(&s->exit); + usb_host_auto_check(NULL); + + if (s->match.bus_num != 0 && s->match.port != NULL) { + usb_host_claim_port(s); + } + return 0; +} + +static const VMStateDescription vmstate_usb_host = { + .name = "usb-host", + .unmigratable = 1, +}; + +static Property usb_host_dev_properties[] = { + DEFINE_PROP_UINT32("hostbus", USBHostDevice, match.bus_num, 0), + DEFINE_PROP_UINT32("hostaddr", USBHostDevice, match.addr, 0), + DEFINE_PROP_STRING("hostport", USBHostDevice, match.port), + DEFINE_PROP_HEX32("vendorid", USBHostDevice, match.vendor_id, 0), + DEFINE_PROP_HEX32("productid", USBHostDevice, match.product_id, 0), + DEFINE_PROP_UINT32("isobufs", USBHostDevice, iso_urb_count, 4), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_host_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_host_initfn; + uc->product_desc = "USB Host Device"; + uc->cancel_packet = usb_host_async_cancel; + uc->handle_data = usb_host_handle_data; + uc->handle_control = usb_host_handle_control; + uc->handle_reset = usb_host_handle_reset; + uc->handle_destroy = usb_host_handle_destroy; + dc->vmsd = &vmstate_usb_host; + dc->props = usb_host_dev_properties; +} + +static TypeInfo usb_host_dev_info = { + .name = "usb-host", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHostDevice), + .class_init = usb_host_class_initfn, +}; + +static void usb_host_register_types(void) +{ + type_register_static(&usb_host_dev_info); + usb_legacy_register("usb-host", "host", usb_host_device_open); +} + +type_init(usb_host_register_types) + +USBDevice *usb_host_device_open(USBBus *bus, const char *devname) +{ + struct USBAutoFilter filter; + USBDevice *dev; + char *p; + + dev = usb_create(bus, "usb-host"); + + if (strstr(devname, "auto:")) { + if (parse_filter(devname, &filter) < 0) { + goto fail; + } + } else { + if ((p = strchr(devname, '.'))) { + filter.bus_num = strtoul(devname, NULL, 0); + filter.addr = strtoul(p + 1, NULL, 0); + filter.vendor_id = 0; + filter.product_id = 0; + } else if ((p = strchr(devname, ':'))) { + filter.bus_num = 0; + filter.addr = 0; + filter.vendor_id = strtoul(devname, NULL, 16); + filter.product_id = strtoul(p + 1, NULL, 16); + } else { + goto fail; + } + } + + qdev_prop_set_uint32(&dev->qdev, "hostbus", filter.bus_num); + qdev_prop_set_uint32(&dev->qdev, "hostaddr", filter.addr); + qdev_prop_set_uint32(&dev->qdev, "vendorid", filter.vendor_id); + qdev_prop_set_uint32(&dev->qdev, "productid", filter.product_id); + qdev_init_nofail(&dev->qdev); + return dev; + +fail: + qdev_free(&dev->qdev); + return NULL; +} + +int usb_host_device_close(const char *devname) +{ +#if 0 + char product_name[PRODUCT_NAME_SZ]; + int bus_num, addr; + USBHostDevice *s; + + if (strstr(devname, "auto:")) { + return usb_host_auto_del(devname); + } + if (usb_host_find_device(&bus_num, &addr, product_name, + sizeof(product_name), devname) < 0) { + return -1; + } + s = hostdev_find(bus_num, addr); + if (s) { + usb_device_delete_addr(s->bus_num, s->dev.addr); + return 0; + } +#endif + + return -1; +} + +/* + * Read sys file-system device file + * + * @line address of buffer to put file contents in + * @line_size size of line + * @device_file path to device file (printf format string) + * @device_name device being opened (inserted into device_file) + * + * @return 0 failed, 1 succeeded ('line' contains data) + */ +static int usb_host_read_file(char *line, size_t line_size, + const char *device_file, const char *device_name) +{ + FILE *f; + int ret = 0; + char filename[PATH_MAX]; + + snprintf(filename, PATH_MAX, "/sys/bus/usb/devices/%s/%s", device_name, + device_file); + f = fopen(filename, "r"); + if (f) { + ret = fgets(line, line_size, f) != NULL; + fclose(f); + } + + return ret; +} + +/* + * Use /sys/bus/usb/devices/ directory to determine host's USB + * devices. + * + * This code is based on Robert Schiele's original patches posted to + * the Novell bug-tracker https://bugzilla.novell.com/show_bug.cgi?id=241950 + */ +static int usb_host_scan(void *opaque, USBScanFunc *func) +{ + DIR *dir = NULL; + char line[1024]; + int bus_num, addr, speed, class_id, product_id, vendor_id; + int ret = 0; + char port[MAX_PORTLEN]; + char product_name[512]; + struct dirent *de; + + dir = opendir("/sys/bus/usb/devices"); + if (!dir) { + perror("husb: opendir /sys/bus/usb/devices"); + fprintf(stderr, "husb: please make sure sysfs is mounted at /sys\n"); + goto the_end; + } + + while ((de = readdir(dir))) { + if (de->d_name[0] != '.' && !strchr(de->d_name, ':')) { + if (sscanf(de->d_name, "%d-%7[0-9.]", &bus_num, port) < 2) { + continue; + } + + if (!usb_host_read_file(line, sizeof(line), "devnum", de->d_name)) { + goto the_end; + } + if (sscanf(line, "%d", &addr) != 1) { + goto the_end; + } + if (!usb_host_read_file(line, sizeof(line), "bDeviceClass", + de->d_name)) { + goto the_end; + } + if (sscanf(line, "%x", &class_id) != 1) { + goto the_end; + } + + if (!usb_host_read_file(line, sizeof(line), "idVendor", + de->d_name)) { + goto the_end; + } + if (sscanf(line, "%x", &vendor_id) != 1) { + goto the_end; + } + if (!usb_host_read_file(line, sizeof(line), "idProduct", + de->d_name)) { + goto the_end; + } + if (sscanf(line, "%x", &product_id) != 1) { + goto the_end; + } + if (!usb_host_read_file(line, sizeof(line), "product", + de->d_name)) { + *product_name = 0; + } else { + if (strlen(line) > 0) { + line[strlen(line) - 1] = '\0'; + } + pstrcpy(product_name, sizeof(product_name), line); + } + + if (!usb_host_read_file(line, sizeof(line), "speed", de->d_name)) { + goto the_end; + } + if (!strcmp(line, "5000\n")) { + speed = USB_SPEED_SUPER; + } else if (!strcmp(line, "480\n")) { + speed = USB_SPEED_HIGH; + } else if (!strcmp(line, "1.5\n")) { + speed = USB_SPEED_LOW; + } else { + speed = USB_SPEED_FULL; + } + + ret = func(opaque, bus_num, addr, port, class_id, vendor_id, + product_id, product_name, speed); + if (ret) { + goto the_end; + } + } + } + the_end: + if (dir) { + closedir(dir); + } + return ret; +} + +static QEMUTimer *usb_auto_timer; + +static int usb_host_auto_scan(void *opaque, int bus_num, + int addr, const char *port, + int class_id, int vendor_id, int product_id, + const char *product_name, int speed) +{ + struct USBAutoFilter *f; + struct USBHostDevice *s; + + /* Ignore hubs */ + if (class_id == 9) + return 0; + + QTAILQ_FOREACH(s, &hostdevs, next) { + f = &s->match; + + if (f->bus_num > 0 && f->bus_num != bus_num) { + continue; + } + if (f->addr > 0 && f->addr != addr) { + continue; + } + if (f->port != NULL && (port == NULL || strcmp(f->port, port) != 0)) { + continue; + } + + if (f->vendor_id > 0 && f->vendor_id != vendor_id) { + continue; + } + + if (f->product_id > 0 && f->product_id != product_id) { + continue; + } + /* We got a match */ + s->seen++; + if (s->errcount >= 3) { + return 0; + } + + /* Already attached ? */ + if (s->fd != -1) { + return 0; + } + DPRINTF("husb: auto open: bus_num %d addr %d\n", bus_num, addr); + + if (usb_host_open(s, bus_num, addr, port, product_name, speed) < 0) { + s->errcount++; + } + break; + } + + return 0; +} + +static void usb_host_auto_check(void *unused) +{ + struct USBHostDevice *s; + int unconnected = 0; + + usb_host_scan(NULL, usb_host_auto_scan); + + QTAILQ_FOREACH(s, &hostdevs, next) { + if (s->fd == -1) { + unconnected++; + } + if (s->seen == 0) { + s->errcount = 0; + } + s->seen = 0; + } + + if (unconnected == 0) { + /* nothing to watch */ + if (usb_auto_timer) { + qemu_del_timer(usb_auto_timer); + trace_usb_host_auto_scan_disabled(); + } + return; + } + + if (!usb_auto_timer) { + usb_auto_timer = qemu_new_timer_ms(rt_clock, usb_host_auto_check, NULL); + if (!usb_auto_timer) { + return; + } + trace_usb_host_auto_scan_enabled(); + } + qemu_mod_timer(usb_auto_timer, qemu_get_clock_ms(rt_clock) + 2000); +} + +/* + * Autoconnect filter + * Format: + * auto:bus:dev[:vid:pid] + * auto:bus.dev[:vid:pid] + * + * bus - bus number (dec, * means any) + * dev - device number (dec, * means any) + * vid - vendor id (hex, * means any) + * pid - product id (hex, * means any) + * + * See 'lsusb' output. + */ +static int parse_filter(const char *spec, struct USBAutoFilter *f) +{ + enum { BUS, DEV, VID, PID, DONE }; + const char *p = spec; + int i; + + f->bus_num = 0; + f->addr = 0; + f->vendor_id = 0; + f->product_id = 0; + + for (i = BUS; i < DONE; i++) { + p = strpbrk(p, ":."); + if (!p) { + break; + } + p++; + + if (*p == '*') { + continue; + } + switch(i) { + case BUS: f->bus_num = strtol(p, NULL, 10); break; + case DEV: f->addr = strtol(p, NULL, 10); break; + case VID: f->vendor_id = strtol(p, NULL, 16); break; + case PID: f->product_id = strtol(p, NULL, 16); break; + } + } + + if (i < DEV) { + fprintf(stderr, "husb: invalid auto filter spec %s\n", spec); + return -1; + } + + return 0; +} + +/**********************/ +/* USB host device info */ + +struct usb_class_info { + int class; + const char *class_name; +}; + +static const struct usb_class_info usb_class_info[] = { + { USB_CLASS_AUDIO, "Audio"}, + { USB_CLASS_COMM, "Communication"}, + { USB_CLASS_HID, "HID"}, + { USB_CLASS_HUB, "Hub" }, + { USB_CLASS_PHYSICAL, "Physical" }, + { USB_CLASS_PRINTER, "Printer" }, + { USB_CLASS_MASS_STORAGE, "Storage" }, + { USB_CLASS_CDC_DATA, "Data" }, + { USB_CLASS_APP_SPEC, "Application Specific" }, + { USB_CLASS_VENDOR_SPEC, "Vendor Specific" }, + { USB_CLASS_STILL_IMAGE, "Still Image" }, + { USB_CLASS_CSCID, "Smart Card" }, + { USB_CLASS_CONTENT_SEC, "Content Security" }, + { -1, NULL } +}; + +static const char *usb_class_str(uint8_t class) +{ + const struct usb_class_info *p; + for(p = usb_class_info; p->class != -1; p++) { + if (p->class == class) { + break; + } + } + return p->class_name; +} + +static void usb_info_device(Monitor *mon, int bus_num, + int addr, const char *port, + int class_id, int vendor_id, int product_id, + const char *product_name, + int speed) +{ + const char *class_str, *speed_str; + + switch(speed) { + case USB_SPEED_LOW: + speed_str = "1.5"; + break; + case USB_SPEED_FULL: + speed_str = "12"; + break; + case USB_SPEED_HIGH: + speed_str = "480"; + break; + case USB_SPEED_SUPER: + speed_str = "5000"; + break; + default: + speed_str = "?"; + break; + } + + monitor_printf(mon, " Bus %d, Addr %d, Port %s, Speed %s Mb/s\n", + bus_num, addr, port, speed_str); + class_str = usb_class_str(class_id); + if (class_str) { + monitor_printf(mon, " %s:", class_str); + } else { + monitor_printf(mon, " Class %02x:", class_id); + } + monitor_printf(mon, " USB device %04x:%04x", vendor_id, product_id); + if (product_name[0] != '\0') { + monitor_printf(mon, ", %s", product_name); + } + monitor_printf(mon, "\n"); +} + +static int usb_host_info_device(void *opaque, int bus_num, int addr, + const char *path, int class_id, + int vendor_id, int product_id, + const char *product_name, + int speed) +{ + Monitor *mon = opaque; + + usb_info_device(mon, bus_num, addr, path, class_id, vendor_id, product_id, + product_name, speed); + return 0; +} + +static void dec2str(int val, char *str, size_t size) +{ + if (val == 0) { + snprintf(str, size, "*"); + } else { + snprintf(str, size, "%d", val); + } +} + +static void hex2str(int val, char *str, size_t size) +{ + if (val == 0) { + snprintf(str, size, "*"); + } else { + snprintf(str, size, "%04x", val); + } +} + +void usb_host_info(Monitor *mon) +{ + struct USBAutoFilter *f; + struct USBHostDevice *s; + + usb_host_scan(mon, usb_host_info_device); + + if (QTAILQ_EMPTY(&hostdevs)) { + return; + } + + monitor_printf(mon, " Auto filters:\n"); + QTAILQ_FOREACH(s, &hostdevs, next) { + char bus[10], addr[10], vid[10], pid[10]; + f = &s->match; + dec2str(f->bus_num, bus, sizeof(bus)); + dec2str(f->addr, addr, sizeof(addr)); + hex2str(f->vendor_id, vid, sizeof(vid)); + hex2str(f->product_id, pid, sizeof(pid)); + monitor_printf(mon, " Bus %s, Addr %s, Port %s, ID %s:%s\n", + bus, addr, f->port ? f->port : "*", vid, pid); + } +} diff --git a/hw/usb/host-stub.c b/hw/usb/host-stub.c new file mode 100644 index 0000000..b4e10c1 --- /dev/null +++ b/hw/usb/host-stub.c @@ -0,0 +1,52 @@ +/* + * Stub host USB redirector + * + * Copyright (c) 2005 Fabrice Bellard + * + * Copyright (c) 2008 Max Krasnyansky + * Support for host device auto connect & disconnect + * Major rewrite to support fully async operation + * + * Copyright 2008 TJ <linux@tjworld.net> + * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition + * to the legacy /proc/bus/usb USB device discovery and handling + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "console.h" +#include "hw/usb.h" +#include "monitor.h" + +void usb_host_info(Monitor *mon) +{ + monitor_printf(mon, "USB host devices not supported\n"); +} + +/* XXX: modify configure to compile the right host driver */ +USBDevice *usb_host_device_open(USBBus *bus, const char *devname) +{ + return NULL; +} + +int usb_host_device_close(const char *devname) +{ + return 0; +} diff --git a/hw/usb/libhw.c b/hw/usb/libhw.c new file mode 100644 index 0000000..2462351 --- /dev/null +++ b/hw/usb/libhw.c @@ -0,0 +1,63 @@ +/* + * QEMU USB emulation, libhw bits. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "cpu-common.h" +#include "hw/usb.h" +#include "dma.h" + +int usb_packet_map(USBPacket *p, QEMUSGList *sgl) +{ + int is_write = (p->pid == USB_TOKEN_IN); + target_phys_addr_t len; + void *mem; + int i; + + for (i = 0; i < sgl->nsg; i++) { + len = sgl->sg[i].len; + mem = cpu_physical_memory_map(sgl->sg[i].base, &len, + is_write); + if (!mem) { + goto err; + } + qemu_iovec_add(&p->iov, mem, len); + if (len != sgl->sg[i].len) { + goto err; + } + } + return 0; + +err: + usb_packet_unmap(p); + return -1; +} + +void usb_packet_unmap(USBPacket *p) +{ + int is_write = (p->pid == USB_TOKEN_IN); + int i; + + for (i = 0; i < p->iov.niov; i++) { + cpu_physical_memory_unmap(p->iov.iov[i].iov_base, + p->iov.iov[i].iov_len, is_write, + p->iov.iov[i].iov_len); + } +} diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c new file mode 100644 index 0000000..8e9f175 --- /dev/null +++ b/hw/usb/redirect.c @@ -0,0 +1,1485 @@ +/* + * USB redirector usb-guest + * + * Copyright (c) 2011 Red Hat, Inc. + * + * Red Hat Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "monitor.h" +#include "sysemu.h" + +#include <dirent.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <usbredirparser.h> +#include <usbredirfilter.h> + +#include "hw/usb.h" + +#define MAX_ENDPOINTS 32 +#define EP2I(ep_address) (((ep_address & 0x80) >> 3) | (ep_address & 0x0f)) +#define I2EP(i) (((i & 0x10) << 3) | (i & 0x0f)) + +typedef struct AsyncURB AsyncURB; +typedef struct USBRedirDevice USBRedirDevice; + +/* Struct to hold buffered packets (iso or int input packets) */ +struct buf_packet { + uint8_t *data; + int len; + int status; + QTAILQ_ENTRY(buf_packet)next; +}; + +struct endp_data { + uint8_t type; + uint8_t interval; + uint8_t interface; /* bInterfaceNumber this ep belongs to */ + uint8_t iso_started; + uint8_t iso_error; /* For reporting iso errors to the HC */ + uint8_t interrupt_started; + uint8_t interrupt_error; + uint8_t bufpq_prefilled; + uint8_t bufpq_dropping_packets; + QTAILQ_HEAD(, buf_packet) bufpq; + int bufpq_size; + int bufpq_target_size; +}; + +struct USBRedirDevice { + USBDevice dev; + /* Properties */ + CharDriverState *cs; + uint8_t debug; + char *filter_str; + /* Data passed from chardev the fd_read cb to the usbredirparser read cb */ + const uint8_t *read_buf; + int read_buf_size; + /* For async handling of open/close */ + QEMUBH *open_close_bh; + /* To delay the usb attach in case of quick chardev close + open */ + QEMUTimer *attach_timer; + int64_t next_attach_time; + struct usbredirparser *parser; + struct endp_data endpoint[MAX_ENDPOINTS]; + uint32_t packet_id; + QTAILQ_HEAD(, AsyncURB) asyncq; + /* Data for device filtering */ + struct usb_redir_device_connect_header device_info; + struct usb_redir_interface_info_header interface_info; + struct usbredirfilter_rule *filter_rules; + int filter_rules_count; +}; + +struct AsyncURB { + USBRedirDevice *dev; + USBPacket *packet; + uint32_t packet_id; + int get; + union { + struct usb_redir_control_packet_header control_packet; + struct usb_redir_bulk_packet_header bulk_packet; + struct usb_redir_interrupt_packet_header interrupt_packet; + }; + QTAILQ_ENTRY(AsyncURB)next; +}; + +static void usbredir_hello(void *priv, struct usb_redir_hello_header *h); +static void usbredir_device_connect(void *priv, + struct usb_redir_device_connect_header *device_connect); +static void usbredir_device_disconnect(void *priv); +static void usbredir_interface_info(void *priv, + struct usb_redir_interface_info_header *interface_info); +static void usbredir_ep_info(void *priv, + struct usb_redir_ep_info_header *ep_info); +static void usbredir_configuration_status(void *priv, uint32_t id, + struct usb_redir_configuration_status_header *configuration_status); +static void usbredir_alt_setting_status(void *priv, uint32_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status); +static void usbredir_iso_stream_status(void *priv, uint32_t id, + struct usb_redir_iso_stream_status_header *iso_stream_status); +static void usbredir_interrupt_receiving_status(void *priv, uint32_t id, + struct usb_redir_interrupt_receiving_status_header + *interrupt_receiving_status); +static void usbredir_bulk_streams_status(void *priv, uint32_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status); +static void usbredir_control_packet(void *priv, uint32_t id, + struct usb_redir_control_packet_header *control_packet, + uint8_t *data, int data_len); +static void usbredir_bulk_packet(void *priv, uint32_t id, + struct usb_redir_bulk_packet_header *bulk_packet, + uint8_t *data, int data_len); +static void usbredir_iso_packet(void *priv, uint32_t id, + struct usb_redir_iso_packet_header *iso_packet, + uint8_t *data, int data_len); +static void usbredir_interrupt_packet(void *priv, uint32_t id, + struct usb_redir_interrupt_packet_header *interrupt_header, + uint8_t *data, int data_len); + +static int usbredir_handle_status(USBRedirDevice *dev, + int status, int actual_len); + +#define VERSION "qemu usb-redir guest " QEMU_VERSION + +/* + * Logging stuff + */ + +#define ERROR(...) \ + do { \ + if (dev->debug >= usbredirparser_error) { \ + error_report("usb-redir error: " __VA_ARGS__); \ + } \ + } while (0) +#define WARNING(...) \ + do { \ + if (dev->debug >= usbredirparser_warning) { \ + error_report("usb-redir warning: " __VA_ARGS__); \ + } \ + } while (0) +#define INFO(...) \ + do { \ + if (dev->debug >= usbredirparser_info) { \ + error_report("usb-redir: " __VA_ARGS__); \ + } \ + } while (0) +#define DPRINTF(...) \ + do { \ + if (dev->debug >= usbredirparser_debug) { \ + error_report("usb-redir: " __VA_ARGS__); \ + } \ + } while (0) +#define DPRINTF2(...) \ + do { \ + if (dev->debug >= usbredirparser_debug_data) { \ + error_report("usb-redir: " __VA_ARGS__); \ + } \ + } while (0) + +static void usbredir_log(void *priv, int level, const char *msg) +{ + USBRedirDevice *dev = priv; + + if (dev->debug < level) { + return; + } + + error_report("%s", msg); +} + +static void usbredir_log_data(USBRedirDevice *dev, const char *desc, + const uint8_t *data, int len) +{ + int i, j, n; + + if (dev->debug < usbredirparser_debug_data) { + return; + } + + for (i = 0; i < len; i += j) { + char buf[128]; + + n = sprintf(buf, "%s", desc); + for (j = 0; j < 8 && i + j < len; j++) { + n += sprintf(buf + n, " %02X", data[i + j]); + } + error_report("%s", buf); + } +} + +/* + * usbredirparser io functions + */ + +static int usbredir_read(void *priv, uint8_t *data, int count) +{ + USBRedirDevice *dev = priv; + + if (dev->read_buf_size < count) { + count = dev->read_buf_size; + } + + memcpy(data, dev->read_buf, count); + + dev->read_buf_size -= count; + if (dev->read_buf_size) { + dev->read_buf += count; + } else { + dev->read_buf = NULL; + } + + return count; +} + +static int usbredir_write(void *priv, uint8_t *data, int count) +{ + USBRedirDevice *dev = priv; + + if (!dev->cs->opened) { + return 0; + } + + return qemu_chr_fe_write(dev->cs, data, count); +} + +/* + * Async and buffered packets helpers + */ + +static AsyncURB *async_alloc(USBRedirDevice *dev, USBPacket *p) +{ + AsyncURB *aurb = (AsyncURB *) g_malloc0(sizeof(AsyncURB)); + aurb->dev = dev; + aurb->packet = p; + aurb->packet_id = dev->packet_id; + QTAILQ_INSERT_TAIL(&dev->asyncq, aurb, next); + dev->packet_id++; + + return aurb; +} + +static void async_free(USBRedirDevice *dev, AsyncURB *aurb) +{ + QTAILQ_REMOVE(&dev->asyncq, aurb, next); + g_free(aurb); +} + +static AsyncURB *async_find(USBRedirDevice *dev, uint32_t packet_id) +{ + AsyncURB *aurb; + + QTAILQ_FOREACH(aurb, &dev->asyncq, next) { + if (aurb->packet_id == packet_id) { + return aurb; + } + } + ERROR("could not find async urb for packet_id %u\n", packet_id); + return NULL; +} + +static void usbredir_cancel_packet(USBDevice *udev, USBPacket *p) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + AsyncURB *aurb; + + QTAILQ_FOREACH(aurb, &dev->asyncq, next) { + if (p != aurb->packet) { + continue; + } + + DPRINTF("async cancel id %u\n", aurb->packet_id); + usbredirparser_send_cancel_data_packet(dev->parser, aurb->packet_id); + usbredirparser_do_write(dev->parser); + + /* Mark it as dead */ + aurb->packet = NULL; + break; + } +} + +static void bufp_alloc(USBRedirDevice *dev, + uint8_t *data, int len, int status, uint8_t ep) +{ + struct buf_packet *bufp; + + if (!dev->endpoint[EP2I(ep)].bufpq_dropping_packets && + dev->endpoint[EP2I(ep)].bufpq_size > + 2 * dev->endpoint[EP2I(ep)].bufpq_target_size) { + DPRINTF("bufpq overflow, dropping packets ep %02X\n", ep); + dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 1; + } + /* Since we're interupting the stream anyways, drop enough packets to get + back to our target buffer size */ + if (dev->endpoint[EP2I(ep)].bufpq_dropping_packets) { + if (dev->endpoint[EP2I(ep)].bufpq_size > + dev->endpoint[EP2I(ep)].bufpq_target_size) { + free(data); + return; + } + dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; + } + + bufp = g_malloc(sizeof(struct buf_packet)); + bufp->data = data; + bufp->len = len; + bufp->status = status; + QTAILQ_INSERT_TAIL(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); + dev->endpoint[EP2I(ep)].bufpq_size++; +} + +static void bufp_free(USBRedirDevice *dev, struct buf_packet *bufp, + uint8_t ep) +{ + QTAILQ_REMOVE(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); + dev->endpoint[EP2I(ep)].bufpq_size--; + free(bufp->data); + g_free(bufp); +} + +static void usbredir_free_bufpq(USBRedirDevice *dev, uint8_t ep) +{ + struct buf_packet *buf, *buf_next; + + QTAILQ_FOREACH_SAFE(buf, &dev->endpoint[EP2I(ep)].bufpq, next, buf_next) { + bufp_free(dev, buf, ep); + } +} + +/* + * USBDevice callbacks + */ + +static void usbredir_handle_reset(USBDevice *udev) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + + DPRINTF("reset device\n"); + usbredirparser_send_reset(dev->parser); + usbredirparser_do_write(dev->parser); +} + +static int usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p, + uint8_t ep) +{ + int status, len; + if (!dev->endpoint[EP2I(ep)].iso_started && + !dev->endpoint[EP2I(ep)].iso_error) { + struct usb_redir_start_iso_stream_header start_iso = { + .endpoint = ep, + }; + int pkts_per_sec; + + if (dev->dev.speed == USB_SPEED_HIGH) { + pkts_per_sec = 8000 / dev->endpoint[EP2I(ep)].interval; + } else { + pkts_per_sec = 1000 / dev->endpoint[EP2I(ep)].interval; + } + /* Testing has shown that we need circa 60 ms buffer */ + dev->endpoint[EP2I(ep)].bufpq_target_size = (pkts_per_sec * 60) / 1000; + + /* Aim for approx 100 interrupts / second on the client to + balance latency and interrupt load */ + start_iso.pkts_per_urb = pkts_per_sec / 100; + if (start_iso.pkts_per_urb < 1) { + start_iso.pkts_per_urb = 1; + } else if (start_iso.pkts_per_urb > 32) { + start_iso.pkts_per_urb = 32; + } + + start_iso.no_urbs = (dev->endpoint[EP2I(ep)].bufpq_target_size + + start_iso.pkts_per_urb - 1) / + start_iso.pkts_per_urb; + /* Output endpoints pre-fill only 1/2 of the packets, keeping the rest + as overflow buffer. Also see the usbredir protocol documentation */ + if (!(ep & USB_DIR_IN)) { + start_iso.no_urbs *= 2; + } + if (start_iso.no_urbs > 16) { + start_iso.no_urbs = 16; + } + + /* No id, we look at the ep when receiving a status back */ + usbredirparser_send_start_iso_stream(dev->parser, 0, &start_iso); + usbredirparser_do_write(dev->parser); + DPRINTF("iso stream started pkts/sec %d pkts/urb %d urbs %d ep %02X\n", + pkts_per_sec, start_iso.pkts_per_urb, start_iso.no_urbs, ep); + dev->endpoint[EP2I(ep)].iso_started = 1; + dev->endpoint[EP2I(ep)].bufpq_prefilled = 0; + dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; + } + + if (ep & USB_DIR_IN) { + struct buf_packet *isop; + + if (dev->endpoint[EP2I(ep)].iso_started && + !dev->endpoint[EP2I(ep)].bufpq_prefilled) { + if (dev->endpoint[EP2I(ep)].bufpq_size < + dev->endpoint[EP2I(ep)].bufpq_target_size) { + return usbredir_handle_status(dev, 0, 0); + } + dev->endpoint[EP2I(ep)].bufpq_prefilled = 1; + } + + isop = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq); + if (isop == NULL) { + DPRINTF("iso-token-in ep %02X, no isop, iso_error: %d\n", + ep, dev->endpoint[EP2I(ep)].iso_error); + /* Re-fill the buffer */ + dev->endpoint[EP2I(ep)].bufpq_prefilled = 0; + /* Check iso_error for stream errors, otherwise its an underrun */ + status = dev->endpoint[EP2I(ep)].iso_error; + dev->endpoint[EP2I(ep)].iso_error = 0; + return status ? USB_RET_IOERROR : 0; + } + DPRINTF2("iso-token-in ep %02X status %d len %d queue-size: %d\n", ep, + isop->status, isop->len, dev->endpoint[EP2I(ep)].bufpq_size); + + status = isop->status; + if (status != usb_redir_success) { + bufp_free(dev, isop, ep); + return USB_RET_IOERROR; + } + + len = isop->len; + if (len > p->iov.size) { + ERROR("received iso data is larger then packet ep %02X (%d > %d)\n", + ep, len, (int)p->iov.size); + bufp_free(dev, isop, ep); + return USB_RET_BABBLE; + } + usb_packet_copy(p, isop->data, len); + bufp_free(dev, isop, ep); + return len; + } else { + /* If the stream was not started because of a pending error don't + send the packet to the usb-host */ + if (dev->endpoint[EP2I(ep)].iso_started) { + struct usb_redir_iso_packet_header iso_packet = { + .endpoint = ep, + .length = p->iov.size + }; + uint8_t buf[p->iov.size]; + /* No id, we look at the ep when receiving a status back */ + usb_packet_copy(p, buf, p->iov.size); + usbredirparser_send_iso_packet(dev->parser, 0, &iso_packet, + buf, p->iov.size); + usbredirparser_do_write(dev->parser); + } + status = dev->endpoint[EP2I(ep)].iso_error; + dev->endpoint[EP2I(ep)].iso_error = 0; + DPRINTF2("iso-token-out ep %02X status %d len %zd\n", ep, status, + p->iov.size); + return usbredir_handle_status(dev, status, p->iov.size); + } +} + +static void usbredir_stop_iso_stream(USBRedirDevice *dev, uint8_t ep) +{ + struct usb_redir_stop_iso_stream_header stop_iso_stream = { + .endpoint = ep + }; + if (dev->endpoint[EP2I(ep)].iso_started) { + usbredirparser_send_stop_iso_stream(dev->parser, 0, &stop_iso_stream); + DPRINTF("iso stream stopped ep %02X\n", ep); + dev->endpoint[EP2I(ep)].iso_started = 0; + } + dev->endpoint[EP2I(ep)].iso_error = 0; + usbredir_free_bufpq(dev, ep); +} + +static int usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p, + uint8_t ep) +{ + AsyncURB *aurb = async_alloc(dev, p); + struct usb_redir_bulk_packet_header bulk_packet; + + DPRINTF("bulk-out ep %02X len %zd id %u\n", ep, + p->iov.size, aurb->packet_id); + + bulk_packet.endpoint = ep; + bulk_packet.length = p->iov.size; + bulk_packet.stream_id = 0; + aurb->bulk_packet = bulk_packet; + + if (ep & USB_DIR_IN) { + usbredirparser_send_bulk_packet(dev->parser, aurb->packet_id, + &bulk_packet, NULL, 0); + } else { + uint8_t buf[p->iov.size]; + usb_packet_copy(p, buf, p->iov.size); + usbredir_log_data(dev, "bulk data out:", buf, p->iov.size); + usbredirparser_send_bulk_packet(dev->parser, aurb->packet_id, + &bulk_packet, buf, p->iov.size); + } + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_handle_interrupt_data(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + if (ep & USB_DIR_IN) { + /* Input interrupt endpoint, buffered packet input */ + struct buf_packet *intp; + int status, len; + + if (!dev->endpoint[EP2I(ep)].interrupt_started && + !dev->endpoint[EP2I(ep)].interrupt_error) { + struct usb_redir_start_interrupt_receiving_header start_int = { + .endpoint = ep, + }; + /* No id, we look at the ep when receiving a status back */ + usbredirparser_send_start_interrupt_receiving(dev->parser, 0, + &start_int); + usbredirparser_do_write(dev->parser); + DPRINTF("interrupt recv started ep %02X\n", ep); + dev->endpoint[EP2I(ep)].interrupt_started = 1; + /* We don't really want to drop interrupt packets ever, but + having some upper limit to how much we buffer is good. */ + dev->endpoint[EP2I(ep)].bufpq_target_size = 1000; + dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; + } + + intp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq); + if (intp == NULL) { + DPRINTF2("interrupt-token-in ep %02X, no intp\n", ep); + /* Check interrupt_error for stream errors */ + status = dev->endpoint[EP2I(ep)].interrupt_error; + dev->endpoint[EP2I(ep)].interrupt_error = 0; + if (status) { + return usbredir_handle_status(dev, status, 0); + } + return USB_RET_NAK; + } + DPRINTF("interrupt-token-in ep %02X status %d len %d\n", ep, + intp->status, intp->len); + + status = intp->status; + if (status != usb_redir_success) { + bufp_free(dev, intp, ep); + return usbredir_handle_status(dev, status, 0); + } + + len = intp->len; + if (len > p->iov.size) { + ERROR("received int data is larger then packet ep %02X\n", ep); + bufp_free(dev, intp, ep); + return USB_RET_BABBLE; + } + usb_packet_copy(p, intp->data, len); + bufp_free(dev, intp, ep); + return len; + } else { + /* Output interrupt endpoint, normal async operation */ + AsyncURB *aurb = async_alloc(dev, p); + struct usb_redir_interrupt_packet_header interrupt_packet; + uint8_t buf[p->iov.size]; + + DPRINTF("interrupt-out ep %02X len %zd id %u\n", ep, p->iov.size, + aurb->packet_id); + + interrupt_packet.endpoint = ep; + interrupt_packet.length = p->iov.size; + aurb->interrupt_packet = interrupt_packet; + + usb_packet_copy(p, buf, p->iov.size); + usbredir_log_data(dev, "interrupt data out:", buf, p->iov.size); + usbredirparser_send_interrupt_packet(dev->parser, aurb->packet_id, + &interrupt_packet, buf, p->iov.size); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; + } +} + +static void usbredir_stop_interrupt_receiving(USBRedirDevice *dev, + uint8_t ep) +{ + struct usb_redir_stop_interrupt_receiving_header stop_interrupt_recv = { + .endpoint = ep + }; + if (dev->endpoint[EP2I(ep)].interrupt_started) { + usbredirparser_send_stop_interrupt_receiving(dev->parser, 0, + &stop_interrupt_recv); + DPRINTF("interrupt recv stopped ep %02X\n", ep); + dev->endpoint[EP2I(ep)].interrupt_started = 0; + } + dev->endpoint[EP2I(ep)].interrupt_error = 0; + usbredir_free_bufpq(dev, ep); +} + +static int usbredir_handle_data(USBDevice *udev, USBPacket *p) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + uint8_t ep; + + ep = p->ep->nr; + if (p->pid == USB_TOKEN_IN) { + ep |= USB_DIR_IN; + } + + switch (dev->endpoint[EP2I(ep)].type) { + case USB_ENDPOINT_XFER_CONTROL: + ERROR("handle_data called for control transfer on ep %02X\n", ep); + return USB_RET_NAK; + case USB_ENDPOINT_XFER_ISOC: + return usbredir_handle_iso_data(dev, p, ep); + case USB_ENDPOINT_XFER_BULK: + return usbredir_handle_bulk_data(dev, p, ep); + case USB_ENDPOINT_XFER_INT: + return usbredir_handle_interrupt_data(dev, p, ep); + default: + ERROR("handle_data ep %02X has unknown type %d\n", ep, + dev->endpoint[EP2I(ep)].type); + return USB_RET_NAK; + } +} + +static int usbredir_set_config(USBRedirDevice *dev, USBPacket *p, + int config) +{ + struct usb_redir_set_configuration_header set_config; + AsyncURB *aurb = async_alloc(dev, p); + int i; + + DPRINTF("set config %d id %u\n", config, aurb->packet_id); + + for (i = 0; i < MAX_ENDPOINTS; i++) { + switch (dev->endpoint[i].type) { + case USB_ENDPOINT_XFER_ISOC: + usbredir_stop_iso_stream(dev, I2EP(i)); + break; + case USB_ENDPOINT_XFER_INT: + if (i & 0x10) { + usbredir_stop_interrupt_receiving(dev, I2EP(i)); + } + break; + } + usbredir_free_bufpq(dev, I2EP(i)); + } + + set_config.configuration = config; + usbredirparser_send_set_configuration(dev->parser, aurb->packet_id, + &set_config); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_get_config(USBRedirDevice *dev, USBPacket *p) +{ + AsyncURB *aurb = async_alloc(dev, p); + + DPRINTF("get config id %u\n", aurb->packet_id); + + aurb->get = 1; + usbredirparser_send_get_configuration(dev->parser, aurb->packet_id); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_set_interface(USBRedirDevice *dev, USBPacket *p, + int interface, int alt) +{ + struct usb_redir_set_alt_setting_header set_alt; + AsyncURB *aurb = async_alloc(dev, p); + int i; + + DPRINTF("set interface %d alt %d id %u\n", interface, alt, + aurb->packet_id); + + for (i = 0; i < MAX_ENDPOINTS; i++) { + if (dev->endpoint[i].interface == interface) { + switch (dev->endpoint[i].type) { + case USB_ENDPOINT_XFER_ISOC: + usbredir_stop_iso_stream(dev, I2EP(i)); + break; + case USB_ENDPOINT_XFER_INT: + if (i & 0x10) { + usbredir_stop_interrupt_receiving(dev, I2EP(i)); + } + break; + } + usbredir_free_bufpq(dev, I2EP(i)); + } + } + + set_alt.interface = interface; + set_alt.alt = alt; + usbredirparser_send_set_alt_setting(dev->parser, aurb->packet_id, + &set_alt); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_get_interface(USBRedirDevice *dev, USBPacket *p, + int interface) +{ + struct usb_redir_get_alt_setting_header get_alt; + AsyncURB *aurb = async_alloc(dev, p); + + DPRINTF("get interface %d id %u\n", interface, aurb->packet_id); + + get_alt.interface = interface; + aurb->get = 1; + usbredirparser_send_get_alt_setting(dev->parser, aurb->packet_id, + &get_alt); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_handle_control(USBDevice *udev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + struct usb_redir_control_packet_header control_packet; + AsyncURB *aurb; + + /* Special cases for certain standard device requests */ + switch (request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + DPRINTF("set address %d\n", value); + dev->dev.addr = value; + return 0; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + return usbredir_set_config(dev, p, value & 0xff); + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + return usbredir_get_config(dev, p); + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + return usbredir_set_interface(dev, p, index, value); + case InterfaceRequest | USB_REQ_GET_INTERFACE: + return usbredir_get_interface(dev, p, index); + } + + /* "Normal" ctrl requests */ + aurb = async_alloc(dev, p); + + /* Note request is (bRequestType << 8) | bRequest */ + DPRINTF("ctrl-out type 0x%x req 0x%x val 0x%x index %d len %d id %u\n", + request >> 8, request & 0xff, value, index, length, + aurb->packet_id); + + control_packet.request = request & 0xFF; + control_packet.requesttype = request >> 8; + control_packet.endpoint = control_packet.requesttype & USB_DIR_IN; + control_packet.value = value; + control_packet.index = index; + control_packet.length = length; + aurb->control_packet = control_packet; + + if (control_packet.requesttype & USB_DIR_IN) { + usbredirparser_send_control_packet(dev->parser, aurb->packet_id, + &control_packet, NULL, 0); + } else { + usbredir_log_data(dev, "ctrl data out:", data, length); + usbredirparser_send_control_packet(dev->parser, aurb->packet_id, + &control_packet, data, length); + } + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +/* + * Close events can be triggered by usbredirparser_do_write which gets called + * from within the USBDevice data / control packet callbacks and doing a + * usb_detach from within these callbacks is not a good idea. + * + * So we use a bh handler to take care of close events. We also handle + * open events from this callback to make sure that a close directly followed + * by an open gets handled in the right order. + */ +static void usbredir_open_close_bh(void *opaque) +{ + USBRedirDevice *dev = opaque; + uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; + + usbredir_device_disconnect(dev); + + if (dev->parser) { + usbredirparser_destroy(dev->parser); + dev->parser = NULL; + } + + if (dev->cs->opened) { + dev->parser = qemu_oom_check(usbredirparser_create()); + dev->parser->priv = dev; + dev->parser->log_func = usbredir_log; + dev->parser->read_func = usbredir_read; + dev->parser->write_func = usbredir_write; + dev->parser->hello_func = usbredir_hello; + dev->parser->device_connect_func = usbredir_device_connect; + dev->parser->device_disconnect_func = usbredir_device_disconnect; + dev->parser->interface_info_func = usbredir_interface_info; + dev->parser->ep_info_func = usbredir_ep_info; + dev->parser->configuration_status_func = usbredir_configuration_status; + dev->parser->alt_setting_status_func = usbredir_alt_setting_status; + dev->parser->iso_stream_status_func = usbredir_iso_stream_status; + dev->parser->interrupt_receiving_status_func = + usbredir_interrupt_receiving_status; + dev->parser->bulk_streams_status_func = usbredir_bulk_streams_status; + dev->parser->control_packet_func = usbredir_control_packet; + dev->parser->bulk_packet_func = usbredir_bulk_packet; + dev->parser->iso_packet_func = usbredir_iso_packet; + dev->parser->interrupt_packet_func = usbredir_interrupt_packet; + dev->read_buf = NULL; + dev->read_buf_size = 0; + + usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); + usbredirparser_caps_set_cap(caps, usb_redir_cap_filter); + usbredirparser_init(dev->parser, VERSION, caps, USB_REDIR_CAPS_SIZE, 0); + usbredirparser_do_write(dev->parser); + } +} + +static void usbredir_do_attach(void *opaque) +{ + USBRedirDevice *dev = opaque; + + usb_device_attach(&dev->dev); +} + +/* + * chardev callbacks + */ + +static int usbredir_chardev_can_read(void *opaque) +{ + USBRedirDevice *dev = opaque; + + if (dev->parser) { + /* usbredir_parser_do_read will consume *all* data we give it */ + return 1024 * 1024; + } else { + /* usbredir_open_close_bh hasn't handled the open event yet */ + return 0; + } +} + +static void usbredir_chardev_read(void *opaque, const uint8_t *buf, int size) +{ + USBRedirDevice *dev = opaque; + + /* No recursion allowed! */ + assert(dev->read_buf == NULL); + + dev->read_buf = buf; + dev->read_buf_size = size; + + usbredirparser_do_read(dev->parser); + /* Send any acks, etc. which may be queued now */ + usbredirparser_do_write(dev->parser); +} + +static void usbredir_chardev_event(void *opaque, int event) +{ + USBRedirDevice *dev = opaque; + + switch (event) { + case CHR_EVENT_OPENED: + case CHR_EVENT_CLOSED: + qemu_bh_schedule(dev->open_close_bh); + break; + } +} + +/* + * init + destroy + */ + +static int usbredir_initfn(USBDevice *udev) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + int i; + + if (dev->cs == NULL) { + qerror_report(QERR_MISSING_PARAMETER, "chardev"); + return -1; + } + + if (dev->filter_str) { + i = usbredirfilter_string_to_rules(dev->filter_str, ":", "|", + &dev->filter_rules, + &dev->filter_rules_count); + if (i) { + qerror_report(QERR_INVALID_PARAMETER_VALUE, "filter", + "a usb device filter string"); + return -1; + } + } + + dev->open_close_bh = qemu_bh_new(usbredir_open_close_bh, dev); + dev->attach_timer = qemu_new_timer_ms(vm_clock, usbredir_do_attach, dev); + + QTAILQ_INIT(&dev->asyncq); + for (i = 0; i < MAX_ENDPOINTS; i++) { + QTAILQ_INIT(&dev->endpoint[i].bufpq); + } + + /* We'll do the attach once we receive the speed from the usb-host */ + udev->auto_attach = 0; + + /* Let the backend know we are ready */ + qemu_chr_fe_open(dev->cs); + qemu_chr_add_handlers(dev->cs, usbredir_chardev_can_read, + usbredir_chardev_read, usbredir_chardev_event, dev); + + return 0; +} + +static void usbredir_cleanup_device_queues(USBRedirDevice *dev) +{ + AsyncURB *aurb, *next_aurb; + int i; + + QTAILQ_FOREACH_SAFE(aurb, &dev->asyncq, next, next_aurb) { + async_free(dev, aurb); + } + for (i = 0; i < MAX_ENDPOINTS; i++) { + usbredir_free_bufpq(dev, I2EP(i)); + } +} + +static void usbredir_handle_destroy(USBDevice *udev) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + + qemu_chr_fe_close(dev->cs); + qemu_chr_delete(dev->cs); + /* Note must be done after qemu_chr_close, as that causes a close event */ + qemu_bh_delete(dev->open_close_bh); + + qemu_del_timer(dev->attach_timer); + qemu_free_timer(dev->attach_timer); + + usbredir_cleanup_device_queues(dev); + + if (dev->parser) { + usbredirparser_destroy(dev->parser); + } + + free(dev->filter_rules); +} + +static int usbredir_check_filter(USBRedirDevice *dev) +{ + if (dev->interface_info.interface_count == 0) { + ERROR("No interface info for device\n"); + goto error; + } + + if (dev->filter_rules) { + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_connect_device_version)) { + ERROR("Device filter specified and peer does not have the " + "connect_device_version capability\n"); + goto error; + } + + if (usbredirfilter_check( + dev->filter_rules, + dev->filter_rules_count, + dev->device_info.device_class, + dev->device_info.device_subclass, + dev->device_info.device_protocol, + dev->interface_info.interface_class, + dev->interface_info.interface_subclass, + dev->interface_info.interface_protocol, + dev->interface_info.interface_count, + dev->device_info.vendor_id, + dev->device_info.product_id, + dev->device_info.device_version_bcd, + 0) != 0) { + goto error; + } + } + + return 0; + +error: + usbredir_device_disconnect(dev); + if (usbredirparser_peer_has_cap(dev->parser, usb_redir_cap_filter)) { + usbredirparser_send_filter_reject(dev->parser); + usbredirparser_do_write(dev->parser); + } + return -1; +} + +/* + * usbredirparser packet complete callbacks + */ + +static int usbredir_handle_status(USBRedirDevice *dev, + int status, int actual_len) +{ + switch (status) { + case usb_redir_success: + return actual_len; + case usb_redir_stall: + return USB_RET_STALL; + case usb_redir_cancelled: + WARNING("returning cancelled packet to HC?\n"); + return USB_RET_NAK; + case usb_redir_inval: + WARNING("got invalid param error from usb-host?\n"); + return USB_RET_NAK; + case usb_redir_ioerror: + case usb_redir_timeout: + default: + return USB_RET_IOERROR; + } +} + +static void usbredir_hello(void *priv, struct usb_redir_hello_header *h) +{ + USBRedirDevice *dev = priv; + + /* Try to send the filter info now that we've the usb-host's caps */ + if (usbredirparser_peer_has_cap(dev->parser, usb_redir_cap_filter) && + dev->filter_rules) { + usbredirparser_send_filter_filter(dev->parser, dev->filter_rules, + dev->filter_rules_count); + usbredirparser_do_write(dev->parser); + } +} + +static void usbredir_device_connect(void *priv, + struct usb_redir_device_connect_header *device_connect) +{ + USBRedirDevice *dev = priv; + const char *speed; + + if (qemu_timer_pending(dev->attach_timer) || dev->dev.attached) { + ERROR("Received device connect while already connected\n"); + return; + } + + switch (device_connect->speed) { + case usb_redir_speed_low: + speed = "low speed"; + dev->dev.speed = USB_SPEED_LOW; + break; + case usb_redir_speed_full: + speed = "full speed"; + dev->dev.speed = USB_SPEED_FULL; + break; + case usb_redir_speed_high: + speed = "high speed"; + dev->dev.speed = USB_SPEED_HIGH; + break; + case usb_redir_speed_super: + speed = "super speed"; + dev->dev.speed = USB_SPEED_SUPER; + break; + default: + speed = "unknown speed"; + dev->dev.speed = USB_SPEED_FULL; + } + + if (usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_connect_device_version)) { + INFO("attaching %s device %04x:%04x version %d.%d class %02x\n", + speed, device_connect->vendor_id, device_connect->product_id, + ((device_connect->device_version_bcd & 0xf000) >> 12) * 10 + + ((device_connect->device_version_bcd & 0x0f00) >> 8), + ((device_connect->device_version_bcd & 0x00f0) >> 4) * 10 + + ((device_connect->device_version_bcd & 0x000f) >> 0), + device_connect->device_class); + } else { + INFO("attaching %s device %04x:%04x class %02x\n", speed, + device_connect->vendor_id, device_connect->product_id, + device_connect->device_class); + } + + dev->dev.speedmask = (1 << dev->dev.speed); + dev->device_info = *device_connect; + + if (usbredir_check_filter(dev)) { + WARNING("Device %04x:%04x rejected by device filter, not attaching\n", + device_connect->vendor_id, device_connect->product_id); + return; + } + + qemu_mod_timer(dev->attach_timer, dev->next_attach_time); +} + +static void usbredir_device_disconnect(void *priv) +{ + USBRedirDevice *dev = priv; + int i; + + /* Stop any pending attaches */ + qemu_del_timer(dev->attach_timer); + + if (dev->dev.attached) { + usb_device_detach(&dev->dev); + /* + * Delay next usb device attach to give the guest a chance to see + * see the detach / attach in case of quick close / open succession + */ + dev->next_attach_time = qemu_get_clock_ms(vm_clock) + 200; + } + + /* Reset state so that the next dev connected starts with a clean slate */ + usbredir_cleanup_device_queues(dev); + memset(dev->endpoint, 0, sizeof(dev->endpoint)); + for (i = 0; i < MAX_ENDPOINTS; i++) { + QTAILQ_INIT(&dev->endpoint[i].bufpq); + } + usb_ep_init(&dev->dev); + dev->interface_info.interface_count = 0; +} + +static void usbredir_interface_info(void *priv, + struct usb_redir_interface_info_header *interface_info) +{ + USBRedirDevice *dev = priv; + + dev->interface_info = *interface_info; + + /* + * If we receive interface info after the device has already been + * connected (ie on a set_config), re-check the filter. + */ + if (qemu_timer_pending(dev->attach_timer) || dev->dev.attached) { + if (usbredir_check_filter(dev)) { + ERROR("Device no longer matches filter after interface info " + "change, disconnecting!\n"); + } + } +} + +static void usbredir_ep_info(void *priv, + struct usb_redir_ep_info_header *ep_info) +{ + USBRedirDevice *dev = priv; + struct USBEndpoint *usb_ep; + int i; + + for (i = 0; i < MAX_ENDPOINTS; i++) { + dev->endpoint[i].type = ep_info->type[i]; + dev->endpoint[i].interval = ep_info->interval[i]; + dev->endpoint[i].interface = ep_info->interface[i]; + switch (dev->endpoint[i].type) { + case usb_redir_type_invalid: + break; + case usb_redir_type_iso: + case usb_redir_type_interrupt: + if (dev->endpoint[i].interval == 0) { + ERROR("Received 0 interval for isoc or irq endpoint\n"); + usbredir_device_disconnect(dev); + } + /* Fall through */ + case usb_redir_type_control: + case usb_redir_type_bulk: + DPRINTF("ep: %02X type: %d interface: %d\n", I2EP(i), + dev->endpoint[i].type, dev->endpoint[i].interface); + break; + default: + ERROR("Received invalid endpoint type\n"); + usbredir_device_disconnect(dev); + return; + } + usb_ep = usb_ep_get(&dev->dev, + (i & 0x10) ? USB_TOKEN_IN : USB_TOKEN_OUT, + i & 0x0f); + usb_ep->type = dev->endpoint[i].type; + usb_ep->ifnum = dev->endpoint[i].interface; + } +} + +static void usbredir_configuration_status(void *priv, uint32_t id, + struct usb_redir_configuration_status_header *config_status) +{ + USBRedirDevice *dev = priv; + AsyncURB *aurb; + int len = 0; + + DPRINTF("set config status %d config %d id %u\n", config_status->status, + config_status->configuration, id); + + aurb = async_find(dev, id); + if (!aurb) { + return; + } + if (aurb->packet) { + if (aurb->get) { + dev->dev.data_buf[0] = config_status->configuration; + len = 1; + } + aurb->packet->result = + usbredir_handle_status(dev, config_status->status, len); + usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); +} + +static void usbredir_alt_setting_status(void *priv, uint32_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status) +{ + USBRedirDevice *dev = priv; + AsyncURB *aurb; + int len = 0; + + DPRINTF("alt status %d intf %d alt %d id: %u\n", + alt_setting_status->status, + alt_setting_status->interface, + alt_setting_status->alt, id); + + aurb = async_find(dev, id); + if (!aurb) { + return; + } + if (aurb->packet) { + if (aurb->get) { + dev->dev.data_buf[0] = alt_setting_status->alt; + len = 1; + } + aurb->packet->result = + usbredir_handle_status(dev, alt_setting_status->status, len); + usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); +} + +static void usbredir_iso_stream_status(void *priv, uint32_t id, + struct usb_redir_iso_stream_status_header *iso_stream_status) +{ + USBRedirDevice *dev = priv; + uint8_t ep = iso_stream_status->endpoint; + + DPRINTF("iso status %d ep %02X id %u\n", iso_stream_status->status, + ep, id); + + if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].iso_started) { + return; + } + + dev->endpoint[EP2I(ep)].iso_error = iso_stream_status->status; + if (iso_stream_status->status == usb_redir_stall) { + DPRINTF("iso stream stopped by peer ep %02X\n", ep); + dev->endpoint[EP2I(ep)].iso_started = 0; + } +} + +static void usbredir_interrupt_receiving_status(void *priv, uint32_t id, + struct usb_redir_interrupt_receiving_status_header + *interrupt_receiving_status) +{ + USBRedirDevice *dev = priv; + uint8_t ep = interrupt_receiving_status->endpoint; + + DPRINTF("interrupt recv status %d ep %02X id %u\n", + interrupt_receiving_status->status, ep, id); + + if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].interrupt_started) { + return; + } + + dev->endpoint[EP2I(ep)].interrupt_error = + interrupt_receiving_status->status; + if (interrupt_receiving_status->status == usb_redir_stall) { + DPRINTF("interrupt receiving stopped by peer ep %02X\n", ep); + dev->endpoint[EP2I(ep)].interrupt_started = 0; + } +} + +static void usbredir_bulk_streams_status(void *priv, uint32_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status) +{ +} + +static void usbredir_control_packet(void *priv, uint32_t id, + struct usb_redir_control_packet_header *control_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + int len = control_packet->length; + AsyncURB *aurb; + + DPRINTF("ctrl-in status %d len %d id %u\n", control_packet->status, + len, id); + + aurb = async_find(dev, id); + if (!aurb) { + free(data); + return; + } + + aurb->control_packet.status = control_packet->status; + aurb->control_packet.length = control_packet->length; + if (memcmp(&aurb->control_packet, control_packet, + sizeof(*control_packet))) { + ERROR("return control packet mismatch, please report this!\n"); + len = USB_RET_NAK; + } + + if (aurb->packet) { + len = usbredir_handle_status(dev, control_packet->status, len); + if (len > 0) { + usbredir_log_data(dev, "ctrl data in:", data, data_len); + if (data_len <= sizeof(dev->dev.data_buf)) { + memcpy(dev->dev.data_buf, data, data_len); + } else { + ERROR("ctrl buffer too small (%d > %zu)\n", + data_len, sizeof(dev->dev.data_buf)); + len = USB_RET_STALL; + } + } + aurb->packet->result = len; + usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); + free(data); +} + +static void usbredir_bulk_packet(void *priv, uint32_t id, + struct usb_redir_bulk_packet_header *bulk_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t ep = bulk_packet->endpoint; + int len = bulk_packet->length; + AsyncURB *aurb; + + DPRINTF("bulk-in status %d ep %02X len %d id %u\n", bulk_packet->status, + ep, len, id); + + aurb = async_find(dev, id); + if (!aurb) { + free(data); + return; + } + + if (aurb->bulk_packet.endpoint != bulk_packet->endpoint || + aurb->bulk_packet.stream_id != bulk_packet->stream_id) { + ERROR("return bulk packet mismatch, please report this!\n"); + len = USB_RET_NAK; + } + + if (aurb->packet) { + len = usbredir_handle_status(dev, bulk_packet->status, len); + if (len > 0) { + usbredir_log_data(dev, "bulk data in:", data, data_len); + if (data_len <= aurb->packet->iov.size) { + usb_packet_copy(aurb->packet, data, data_len); + } else { + ERROR("bulk buffer too small (%d > %zd)\n", data_len, + aurb->packet->iov.size); + len = USB_RET_STALL; + } + } + aurb->packet->result = len; + usb_packet_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); + free(data); +} + +static void usbredir_iso_packet(void *priv, uint32_t id, + struct usb_redir_iso_packet_header *iso_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t ep = iso_packet->endpoint; + + DPRINTF2("iso-in status %d ep %02X len %d id %u\n", iso_packet->status, ep, + data_len, id); + + if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_ISOC) { + ERROR("received iso packet for non iso endpoint %02X\n", ep); + free(data); + return; + } + + if (dev->endpoint[EP2I(ep)].iso_started == 0) { + DPRINTF("received iso packet for non started stream ep %02X\n", ep); + free(data); + return; + } + + /* bufp_alloc also adds the packet to the ep queue */ + bufp_alloc(dev, data, data_len, iso_packet->status, ep); +} + +static void usbredir_interrupt_packet(void *priv, uint32_t id, + struct usb_redir_interrupt_packet_header *interrupt_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t ep = interrupt_packet->endpoint; + + DPRINTF("interrupt-in status %d ep %02X len %d id %u\n", + interrupt_packet->status, ep, data_len, id); + + if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_INT) { + ERROR("received int packet for non interrupt endpoint %02X\n", ep); + free(data); + return; + } + + if (ep & USB_DIR_IN) { + if (dev->endpoint[EP2I(ep)].interrupt_started == 0) { + DPRINTF("received int packet while not started ep %02X\n", ep); + free(data); + return; + } + + /* bufp_alloc also adds the packet to the ep queue */ + bufp_alloc(dev, data, data_len, interrupt_packet->status, ep); + } else { + int len = interrupt_packet->length; + + AsyncURB *aurb = async_find(dev, id); + if (!aurb) { + return; + } + + if (aurb->interrupt_packet.endpoint != interrupt_packet->endpoint) { + ERROR("return int packet mismatch, please report this!\n"); + len = USB_RET_NAK; + } + + if (aurb->packet) { + aurb->packet->result = usbredir_handle_status(dev, + interrupt_packet->status, len); + usb_packet_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); + } +} + +static Property usbredir_properties[] = { + DEFINE_PROP_CHR("chardev", USBRedirDevice, cs), + DEFINE_PROP_UINT8("debug", USBRedirDevice, debug, 0), + DEFINE_PROP_STRING("filter", USBRedirDevice, filter_str), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usbredir_class_initfn(ObjectClass *klass, void *data) +{ + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + uc->init = usbredir_initfn; + uc->product_desc = "USB Redirection Device"; + uc->handle_destroy = usbredir_handle_destroy; + uc->cancel_packet = usbredir_cancel_packet; + uc->handle_reset = usbredir_handle_reset; + uc->handle_data = usbredir_handle_data; + uc->handle_control = usbredir_handle_control; + dc->props = usbredir_properties; +} + +static TypeInfo usbredir_dev_info = { + .name = "usb-redir", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBRedirDevice), + .class_init = usbredir_class_initfn, +}; + +static void usbredir_register_types(void) +{ + type_register_static(&usbredir_dev_info); +} + +type_init(usbredir_register_types) |