diff options
author | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
---|---|---|
committer | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/usb | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff) |
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback
design to work with QEMU and rust-vmm vhost-user backend without require any
changes.
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'hw/usb')
65 files changed, 42354 insertions, 0 deletions
diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig new file mode 100644 index 000000000..53f8283ff --- /dev/null +++ b/hw/usb/Kconfig @@ -0,0 +1,135 @@ +config USB + bool + +config USB_UHCI + bool + default y if PCI_DEVICES + depends on PCI + select USB + +config USB_OHCI + bool + select USB + +config USB_OHCI_PCI + bool + default y if PCI_DEVICES + depends on PCI + select USB_OHCI + +config USB_EHCI + bool + select USB + +config USB_EHCI_PCI + bool + default y if PCI_DEVICES + select USB_EHCI + +config USB_EHCI_SYSBUS + bool + select USB_EHCI + +config USB_XHCI + bool + select USB + +config USB_XHCI_PCI + bool + default y if PCI_DEVICES + depends on PCI + select USB_XHCI + +config USB_XHCI_NEC + bool + default y if PCI_DEVICES + select USB_XHCI_PCI + +config USB_XHCI_SYSBUS + bool + select USB_XHCI + +config USB_MUSB + bool + select USB + +config USB_DWC2 + bool + select USB + +config TUSB6010 + bool + select USB_MUSB + +config USB_TABLET_WACOM + bool + default y + depends on USB + +config USB_STORAGE_CORE + bool + depends on USB + select SCSI + +config USB_STORAGE_CLASSIC + bool + default y + depends on USB + select USB_STORAGE_CORE + +config USB_STORAGE_BOT + bool + default y + depends on USB + select USB_STORAGE_CORE + +config USB_STORAGE_UAS + bool + default y + depends on USB + select SCSI + +config USB_AUDIO + bool + default y + depends on USB + +config USB_SERIAL + bool + default y + depends on USB + +config USB_NETWORK + bool + default y + depends on USB + +config USB_SMARTCARD + bool + default y + depends on USB + +config USB_STORAGE_MTP + bool + default y + depends on USB + +config USB_U2F + bool + default y + depends on USB + +config IMX_USBPHY + bool + default y + depends on USB + +config USB_DWC3 + bool + select USB_XHCI_SYSBUS + select REGISTER + +config XLNX_USB_SUBSYS + bool + default y if XLNX_VERSAL + select USB_DWC3 diff --git a/hw/usb/bus.c b/hw/usb/bus.c new file mode 100644 index 000000000..92d6ed562 --- /dev/null +++ b/hw/usb/bus.c @@ -0,0 +1,776 @@ +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-machine.h" +#include "qapi/type-helpers.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "sysemu/sysemu.h" +#include "migration/vmstate.h" +#include "monitor/monitor.h" +#include "trace.h" +#include "qemu/cutils.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 void usb_qdev_unrealize(DeviceState *qdev); + +static Property usb_props[] = { + DEFINE_PROP_STRING("port", USBDevice, port_path), + DEFINE_PROP_STRING("serial", USBDevice, serial), + DEFINE_PROP_BIT("msos-desc", USBDevice, flags, + USB_DEV_FLAG_MSOS_DESC_ENABLE, true), + DEFINE_PROP_STRING("pcap", USBDevice, pcap_filename), + DEFINE_PROP_END_OF_LIST() +}; + +static void usb_bus_class_init(ObjectClass *klass, void *data) +{ + BusClass *k = BUS_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); + + k->print_dev = usb_bus_dev_print; + k->get_dev_path = usb_get_dev_path; + k->get_fw_dev_path = usb_get_fw_dev_path; + hc->unplug = qdev_simple_device_unplug_cb; +} + +static const TypeInfo usb_bus_info = { + .name = TYPE_USB_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(USBBus), + .class_init = usb_bus_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } +}; + +static int next_usb_bus = 0; +static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses); + +static int usb_device_post_load(void *opaque, int version_id) +{ + USBDevice *dev = opaque; + + if (dev->state == USB_STATE_NOTATTACHED) { + dev->attached = false; + } else { + dev->attached = true; + } + return 0; +} + +const VMStateDescription vmstate_usb_device = { + .name = "USBDevice", + .version_id = 1, + .minimum_version_id = 1, + .post_load = usb_device_post_load, + .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, size_t bus_size, + USBBusOps *ops, DeviceState *host) +{ + qbus_init(bus, bus_size, TYPE_USB_BUS, host, NULL); + qbus_set_bus_hotplug_handler(BUS(bus)); + bus->ops = ops; + bus->busnr = next_usb_bus++; + QTAILQ_INIT(&bus->free); + QTAILQ_INIT(&bus->used); + QTAILQ_INSERT_TAIL(&busses, bus, next); +} + +void usb_bus_release(USBBus *bus) +{ + assert(next_usb_bus > 0); + + QTAILQ_REMOVE(&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 void usb_device_realize(USBDevice *dev, Error **errp) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + + if (klass->realize) { + klass->realize(dev, errp); + } +} + +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_unrealize(USBDevice *dev) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + + if (klass->unrealize) { + klass->unrealize(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); + } +} + +void 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) { + klass->handle_control(dev, p, request, value, index, length, data); + } +} + +void usb_device_handle_data(USBDevice *dev, USBPacket *p) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->handle_data) { + klass->handle_data(dev, p); + } +} + +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); + if (dev->usb_desc) { + return dev->usb_desc; + } + 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); + } +} + +void usb_device_flush_ep_queue(USBDevice *dev, USBEndpoint *ep) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->flush_ep_queue) { + klass->flush_ep_queue(dev, ep); + } +} + +void usb_device_ep_stopped(USBDevice *dev, USBEndpoint *ep) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->ep_stopped) { + klass->ep_stopped(dev, ep); + } +} + +int usb_device_alloc_streams(USBDevice *dev, USBEndpoint **eps, int nr_eps, + int streams) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->alloc_streams) { + return klass->alloc_streams(dev, eps, nr_eps, streams); + } + return 0; +} + +void usb_device_free_streams(USBDevice *dev, USBEndpoint **eps, int nr_eps) +{ + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + if (klass->free_streams) { + klass->free_streams(dev, eps, nr_eps); + } +} + +static void usb_qdev_realize(DeviceState *qdev, Error **errp) +{ + USBDevice *dev = USB_DEVICE(qdev); + Error *local_err = NULL; + + 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); + + usb_claim_port(dev, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + usb_device_realize(dev, &local_err); + if (local_err) { + usb_release_port(dev); + error_propagate(errp, local_err); + return; + } + + if (dev->auto_attach) { + usb_device_attach(dev, &local_err); + if (local_err) { + usb_qdev_unrealize(qdev); + error_propagate(errp, local_err); + return; + } + } + + if (dev->pcap_filename) { + int fd = qemu_open_old(dev->pcap_filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (fd < 0) { + error_setg(errp, "open %s failed", dev->pcap_filename); + usb_qdev_unrealize(qdev); + return; + } + dev->pcap = fdopen(fd, "w"); + usb_pcap_init(dev->pcap); + } +} + +static void usb_qdev_unrealize(DeviceState *qdev) +{ + USBDevice *dev = USB_DEVICE(qdev); + USBDescString *s, *next; + + QLIST_FOREACH_SAFE(s, &dev->strings, next, next) { + QLIST_REMOVE(s, next); + g_free(s->str); + g_free(s); + } + + if (dev->pcap) { + fclose(dev->pcap); + } + + if (dev->attached) { + usb_device_detach(dev); + } + usb_device_unrealize(dev); + if (dev->port) { + usb_release_port(dev); + } +} + +typedef struct LegacyUSBFactory +{ + const char *name; + const char *usbdevice_name; + USBDevice *(*usbdevice_init)(void); +} LegacyUSBFactory; + +static GSList *legacy_usb_factory; + +void usb_legacy_register(const char *typename, const char *usbdevice_name, + USBDevice *(*usbdevice_init)(void)) +{ + 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_new(const char *name) +{ + return USB_DEVICE(qdev_new(name)); +} + +static USBDevice *usb_try_new(const char *name) +{ + return USB_DEVICE(qdev_try_new(name)); +} + +bool usb_realize_and_unref(USBDevice *dev, USBBus *bus, Error **errp) +{ + return qdev_realize_and_unref(&dev->qdev, &bus->qbus, errp); +} + +USBDevice *usb_create_simple(USBBus *bus, const char *name) +{ + USBDevice *dev = usb_new(name); + + usb_realize_and_unref(dev, bus, &error_abort); + 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++; +} + +void usb_register_companion(const char *masterbus, USBPort *ports[], + uint32_t portcount, uint32_t firstport, + void *opaque, USBPortOps *ops, int speedmask, + Error **errp) +{ + USBBus *bus; + int i; + + QTAILQ_FOREACH(bus, &busses, next) { + if (strcmp(bus->qbus.name, masterbus) == 0) { + break; + } + } + + if (!bus) { + error_setg(errp, "USB bus '%s' not found", masterbus); + return; + } + if (!bus->ops->register_companion) { + error_setg(errp, "Can't use USB bus '%s' as masterbus," + " it doesn't support companion controllers", + masterbus); + return; + } + + for (i = 0; i < portcount; i++) { + usb_fill_port(ports[i], opaque, i, ops, speedmask); + } + + bus->ops->register_companion(bus, ports, portcount, firstport, errp); +} + +void usb_port_location(USBPort *downstream, USBPort *upstream, int portnr) +{ + if (upstream) { + int l = snprintf(downstream->path, sizeof(downstream->path), "%s.%d", + upstream->path, portnr); + /* Max string is nn.nn.nn.nn.nn, which fits in 16 bytes */ + assert(l < sizeof(downstream->path)); + downstream->hubcount = upstream->hubcount + 1; + } else { + snprintf(downstream->path, sizeof(downstream->path), "%d", portnr); + downstream->hubcount = 0; + } +} + +void usb_unregister_port(USBBus *bus, USBPort *port) +{ + if (port->dev) { + object_unparent(OBJECT(port->dev)); + } + QTAILQ_REMOVE(&bus->free, port, next); + bus->nfree--; +} + +void usb_claim_port(USBDevice *dev, Error **errp) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port; + USBDevice *hub; + + 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_setg(errp, "usb port %s (bus %s) not found (in use?)", + dev->port_path, bus->qbus.name); + return; + } + } else { + if (bus->nfree == 1 && strcmp(object_get_typename(OBJECT(dev)), "usb-hub") != 0) { + /* Create a new hub and chain it on */ + hub = usb_try_new("usb-hub"); + if (hub) { + usb_realize_and_unref(hub, bus, NULL); + } + } + if (bus->nfree == 0) { + error_setg(errp, "tried to attach usb device %s to a bus " + "with no free ports", dev->product_desc); + return; + } + 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++; +} + +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++; +} + +static void usb_mask_to_str(char *dest, size_t size, + unsigned int speedmask) +{ + static const struct { + unsigned int mask; + const char *name; + } speeds[] = { + { .mask = USB_SPEED_MASK_FULL, .name = "full" }, + { .mask = USB_SPEED_MASK_HIGH, .name = "high" }, + { .mask = USB_SPEED_MASK_SUPER, .name = "super" }, + }; + int i, pos = 0; + + for (i = 0; i < ARRAY_SIZE(speeds); i++) { + if (speeds[i].mask & speedmask) { + pos += snprintf(dest + pos, size - pos, "%s%s", + pos ? "+" : "", + speeds[i].name); + } + } + + if (pos == 0) { + snprintf(dest, size, "unknown"); + } +} + +void usb_check_attach(USBDevice *dev, Error **errp) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port = dev->port; + char devspeed[32], portspeed[32]; + + assert(port != NULL); + assert(!dev->attached); + usb_mask_to_str(devspeed, sizeof(devspeed), dev->speedmask); + usb_mask_to_str(portspeed, sizeof(portspeed), port->speedmask); + trace_usb_port_attach(bus->busnr, port->path, + devspeed, portspeed); + + if (!(port->speedmask & dev->speedmask)) { + error_setg(errp, "Warning: speed mismatch trying to attach" + " usb device \"%s\" (%s speed)" + " to bus \"%s\", port \"%s\" (%s speed)", + dev->product_desc, devspeed, + bus->qbus.name, port->path, portspeed); + return; + } +} + +void usb_device_attach(USBDevice *dev, Error **errp) +{ + USBPort *port = dev->port; + Error *local_err = NULL; + + usb_check_attach(dev, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + dev->attached = true; + usb_attach(port); +} + +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 = false; + 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); + DeviceState *hcd = qdev->parent_bus->parent; + char *id = qdev_get_dev_path(hcd); + + if (id) { + char *ret = g_strdup_printf("%s/%s", id, dev->port->path); + g_free(id); + return ret; + } else { + 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@%lx/", nr); + in++; + } else { + /* the device itself */ + snprintf(fw_path + pos, fw_len - pos, "%s@%lx", + qdev_fw_name(qdev), nr); + break; + } + } + return fw_path; +} + +HumanReadableText *qmp_x_query_usb(Error **errp) +{ + g_autoptr(GString) buf = g_string_new(""); + USBBus *bus; + USBDevice *dev; + USBPort *port; + + if (QTAILQ_EMPTY(&busses)) { + error_setg(errp, "USB support not enabled"); + return NULL; + } + + QTAILQ_FOREACH(bus, &busses, next) { + QTAILQ_FOREACH(port, &bus->used, next) { + dev = port->dev; + if (!dev) + continue; + g_string_append_printf(buf, + " Device %d.%d, Port %s, Speed %s Mb/s, " + "Product %s%s%s\n", + bus->busnr, dev->addr, port->path, + usb_speed(dev->speed), dev->product_desc, + dev->qdev.id ? ", ID: " : "", + dev->qdev.id ?: ""); + } + } + + return human_readable_text_from_str(buf); +} + +/* handle legacy -usbdevice cmd line option */ +USBDevice *usbdevice_create(const char *driver) +{ + USBBus *bus = usb_bus_find(-1 /* any */); + LegacyUSBFactory *f = NULL; + Error *err = NULL; + GSList *i; + USBDevice *dev; + + if (strchr(driver, ':')) { + error_report("usbdevice parameters are not supported anymore"); + return NULL; + } + + 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 (!bus) { + error_report("Error: no usb bus to attach usbdevice %s, " + "please try -machine usb=on and check that " + "the machine model supports USB", driver); + return NULL; + } + + dev = f->usbdevice_init ? f->usbdevice_init() : usb_new(f->name); + if (!dev) { + error_report("Failed to create USB device '%s'", f->name); + return NULL; + } + if (!usb_realize_and_unref(dev, bus, &err)) { + error_reportf_err(err, "Failed to initialize USB device '%s': ", + f->name); + object_unparent(OBJECT(dev)); + return NULL; + } + return dev; +} + +static bool usb_get_attached(Object *obj, Error **errp) +{ + USBDevice *dev = USB_DEVICE(obj); + + return dev->attached; +} + +static void usb_set_attached(Object *obj, bool value, Error **errp) +{ + USBDevice *dev = USB_DEVICE(obj); + + if (dev->attached == value) { + return; + } + + if (value) { + usb_device_attach(dev, errp); + } else { + usb_device_detach(dev); + } +} + +static void usb_device_instance_init(Object *obj) +{ + USBDevice *dev = USB_DEVICE(obj); + USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); + + if (klass->attached_settable) { + object_property_add_bool(obj, "attached", + usb_get_attached, usb_set_attached); + } else { + object_property_add_bool(obj, "attached", + usb_get_attached, NULL); + } +} + +static void usb_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->bus_type = TYPE_USB_BUS; + k->realize = usb_qdev_realize; + k->unrealize = usb_qdev_unrealize; + device_class_set_props(k, usb_props); +} + +static const TypeInfo usb_device_type_info = { + .name = TYPE_USB_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(USBDevice), + .instance_init = usb_device_instance_init, + .abstract = true, + .class_size = sizeof(USBDeviceClass), + .class_init = usb_device_class_init, +}; + +static void usb_register_types(void) +{ + type_register_static(&usb_bus_info); + type_register_static(&usb_device_type_info); +} + +type_init(usb_register_types) diff --git a/hw/usb/ccid-card-emulated.c b/hw/usb/ccid-card-emulated.c new file mode 100644 index 000000000..6c8c0355e --- /dev/null +++ b/hw/usb/ccid-card-emulated.c @@ -0,0 +1,622 @@ +/* + * CCID Card Device. Emulated card. + * + * Copyright (c) 2011 Red Hat. + * Written by Alon Levy. + * + * This code is licensed under the GNU LGPL, version 2 or later. + */ + +/* + * It can be used to provide access to the local hardware in a non exclusive + * way, or it can use certificates. It requires the usb-ccid bus. + * + * Usage 1: standard, mirror hardware reader+card: + * qemu .. -usb -device usb-ccid -device ccid-card-emulated + * + * Usage 2: use certificates, no hardware required + * one time: create the certificates: + * for i in 1 2 3; do + * certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i + * done + * qemu .. -usb -device usb-ccid \ + * -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 + * + * If you use a non default db for the certificates you can specify it using + * the db parameter. + */ + +#include "qemu/osdep.h" +#include <libcacard.h> + +#include "qemu/thread.h" +#include "qemu/lockable.h" +#include "qemu/main-loop.h" +#include "qemu/module.h" +#include "ccid.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qom/object.h" + +#define DPRINTF(card, lvl, fmt, ...) \ +do {\ + if (lvl <= card->debug) {\ + printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__);\ + } \ +} while (0) + + +#define TYPE_EMULATED_CCID "ccid-card-emulated" +typedef struct EmulatedState EmulatedState; +DECLARE_INSTANCE_CHECKER(EmulatedState, EMULATED_CCID_CARD, + TYPE_EMULATED_CCID) + +#define BACKEND_NSS_EMULATED_NAME "nss-emulated" +#define BACKEND_CERTIFICATES_NAME "certificates" + +enum { + BACKEND_NSS_EMULATED = 1, + BACKEND_CERTIFICATES +}; + +#define DEFAULT_BACKEND BACKEND_NSS_EMULATED + + +enum { + EMUL_READER_INSERT = 0, + EMUL_READER_REMOVE, + EMUL_CARD_INSERT, + EMUL_CARD_REMOVE, + EMUL_GUEST_APDU, + EMUL_RESPONSE_APDU, + EMUL_ERROR, +}; + +static const char *emul_event_to_string(uint32_t emul_event) +{ + switch (emul_event) { + case EMUL_READER_INSERT: + return "EMUL_READER_INSERT"; + case EMUL_READER_REMOVE: + return "EMUL_READER_REMOVE"; + case EMUL_CARD_INSERT: + return "EMUL_CARD_INSERT"; + case EMUL_CARD_REMOVE: + return "EMUL_CARD_REMOVE"; + case EMUL_GUEST_APDU: + return "EMUL_GUEST_APDU"; + case EMUL_RESPONSE_APDU: + return "EMUL_RESPONSE_APDU"; + case EMUL_ERROR: + return "EMUL_ERROR"; + } + return "UNKNOWN"; +} + +typedef struct EmulEvent { + QSIMPLEQ_ENTRY(EmulEvent) entry; + union { + struct { + uint32_t type; + } gen; + struct { + uint32_t type; + uint64_t code; + } error; + struct { + uint32_t type; + uint32_t len; + uint8_t data[]; + } data; + } p; +} EmulEvent; + +#define MAX_ATR_SIZE 40 +struct EmulatedState { + CCIDCardState base; + uint8_t debug; + char *backend_str; + uint32_t backend; + char *cert1; + char *cert2; + char *cert3; + char *db; + uint8_t atr[MAX_ATR_SIZE]; + uint8_t atr_length; + QSIMPLEQ_HEAD(, EmulEvent) event_list; + QemuMutex event_list_mutex; + QemuThread event_thread_id; + VReader *reader; + QSIMPLEQ_HEAD(, EmulEvent) guest_apdu_list; + QemuMutex vreader_mutex; /* and guest_apdu_list mutex */ + QemuMutex handle_apdu_mutex; + QemuCond handle_apdu_cond; + EventNotifier notifier; + int quit_apdu_thread; + QemuThread apdu_thread_id; +}; + +static void emulated_apdu_from_guest(CCIDCardState *base, + const uint8_t *apdu, uint32_t len) +{ + EmulatedState *card = EMULATED_CCID_CARD(base); + EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len); + + assert(event); + event->p.data.type = EMUL_GUEST_APDU; + event->p.data.len = len; + memcpy(event->p.data.data, apdu, len); + qemu_mutex_lock(&card->vreader_mutex); + QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); + qemu_mutex_unlock(&card->vreader_mutex); + qemu_mutex_lock(&card->handle_apdu_mutex); + qemu_cond_signal(&card->handle_apdu_cond); + qemu_mutex_unlock(&card->handle_apdu_mutex); +} + +static const uint8_t *emulated_get_atr(CCIDCardState *base, uint32_t *len) +{ + EmulatedState *card = EMULATED_CCID_CARD(base); + + *len = card->atr_length; + return card->atr; +} + +static void emulated_push_event(EmulatedState *card, EmulEvent *event) +{ + qemu_mutex_lock(&card->event_list_mutex); + QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); + qemu_mutex_unlock(&card->event_list_mutex); + event_notifier_set(&card->notifier); +} + +static void emulated_push_type(EmulatedState *card, uint32_t type) +{ + EmulEvent *event = g_new(EmulEvent, 1); + + assert(event); + event->p.gen.type = type; + emulated_push_event(card, event); +} + +static void emulated_push_error(EmulatedState *card, uint64_t code) +{ + EmulEvent *event = g_new(EmulEvent, 1); + + assert(event); + event->p.error.type = EMUL_ERROR; + event->p.error.code = code; + emulated_push_event(card, event); +} + +static void emulated_push_data_type(EmulatedState *card, uint32_t type, + const uint8_t *data, uint32_t len) +{ + EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len); + + assert(event); + event->p.data.type = type; + event->p.data.len = len; + memcpy(event->p.data.data, data, len); + emulated_push_event(card, event); +} + +static void emulated_push_reader_insert(EmulatedState *card) +{ + emulated_push_type(card, EMUL_READER_INSERT); +} + +static void emulated_push_reader_remove(EmulatedState *card) +{ + emulated_push_type(card, EMUL_READER_REMOVE); +} + +static void emulated_push_card_insert(EmulatedState *card, + const uint8_t *atr, uint32_t len) +{ + emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); +} + +static void emulated_push_card_remove(EmulatedState *card) +{ + emulated_push_type(card, EMUL_CARD_REMOVE); +} + +static void emulated_push_response_apdu(EmulatedState *card, + const uint8_t *apdu, uint32_t len) +{ + emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); +} + +#define APDU_BUF_SIZE 270 +static void *handle_apdu_thread(void* arg) +{ + EmulatedState *card = arg; + uint8_t recv_data[APDU_BUF_SIZE]; + int recv_len; + VReaderStatus reader_status; + EmulEvent *event; + + while (1) { + qemu_mutex_lock(&card->handle_apdu_mutex); + qemu_cond_wait(&card->handle_apdu_cond, &card->handle_apdu_mutex); + qemu_mutex_unlock(&card->handle_apdu_mutex); + if (card->quit_apdu_thread) { + card->quit_apdu_thread = 0; /* debugging */ + break; + } + WITH_QEMU_LOCK_GUARD(&card->vreader_mutex) { + while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { + event = QSIMPLEQ_FIRST(&card->guest_apdu_list); + assert((unsigned long)event > 1000); + QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); + if (event->p.data.type != EMUL_GUEST_APDU) { + DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); + g_free(event); + continue; + } + if (card->reader == NULL) { + DPRINTF(card, 1, "reader is NULL\n"); + g_free(event); + continue; + } + recv_len = sizeof(recv_data); + reader_status = vreader_xfr_bytes(card->reader, + event->p.data.data, event->p.data.len, + recv_data, &recv_len); + DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); + if (reader_status == VREADER_OK) { + emulated_push_response_apdu(card, recv_data, recv_len); + } else { + emulated_push_error(card, reader_status); + } + g_free(event); + } + } + } + return NULL; +} + +static void *event_thread(void *arg) +{ + int atr_len = MAX_ATR_SIZE; + uint8_t atr[MAX_ATR_SIZE]; + VEvent *event = NULL; + EmulatedState *card = arg; + + while (1) { + const char *reader_name; + + event = vevent_wait_next_vevent(); + if (event == NULL || event->type == VEVENT_LAST) { + break; + } + if (event->type != VEVENT_READER_INSERT) { + if (card->reader == NULL && event->reader != NULL) { + /* Happens after device_add followed by card remove or insert. + * XXX: create synthetic add_reader events if vcard_emul_init + * already called, which happens if device_del and device_add + * are called */ + card->reader = vreader_reference(event->reader); + } else { + if (event->reader != card->reader) { + fprintf(stderr, + "ERROR: wrong reader: quitting event_thread\n"); + break; + } + } + } + switch (event->type) { + case VEVENT_READER_INSERT: + /* TODO: take a specific reader. i.e. track which reader + * we are seeing here, check it is the one we want (the first, + * or by a particular name), and ignore if we don't want it. + */ + reader_name = vreader_get_name(event->reader); + if (card->reader != NULL) { + DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", + vreader_get_name(card->reader), reader_name); + qemu_mutex_lock(&card->vreader_mutex); + vreader_free(card->reader); + qemu_mutex_unlock(&card->vreader_mutex); + emulated_push_reader_remove(card); + } + qemu_mutex_lock(&card->vreader_mutex); + DPRINTF(card, 2, "READER INSERT %s\n", reader_name); + card->reader = vreader_reference(event->reader); + qemu_mutex_unlock(&card->vreader_mutex); + emulated_push_reader_insert(card); + break; + case VEVENT_READER_REMOVE: + DPRINTF(card, 2, " READER REMOVE: %s\n", + vreader_get_name(event->reader)); + qemu_mutex_lock(&card->vreader_mutex); + vreader_free(card->reader); + card->reader = NULL; + qemu_mutex_unlock(&card->vreader_mutex); + emulated_push_reader_remove(card); + break; + case VEVENT_CARD_INSERT: + /* get the ATR (intended as a response to a power on from the + * reader */ + atr_len = MAX_ATR_SIZE; + vreader_power_on(event->reader, atr, &atr_len); + card->atr_length = (uint8_t)atr_len; + DPRINTF(card, 2, " CARD INSERT\n"); + emulated_push_card_insert(card, atr, atr_len); + break; + case VEVENT_CARD_REMOVE: + DPRINTF(card, 2, " CARD REMOVE\n"); + emulated_push_card_remove(card); + break; + case VEVENT_LAST: /* quit */ + vevent_delete(event); + return NULL; + default: + break; + } + vevent_delete(event); + } + return NULL; +} + +static void card_event_handler(EventNotifier *notifier) +{ + EmulatedState *card = container_of(notifier, EmulatedState, notifier); + EmulEvent *event, *next; + + event_notifier_test_and_clear(&card->notifier); + QEMU_LOCK_GUARD(&card->event_list_mutex); + QSIMPLEQ_FOREACH_SAFE(event, &card->event_list, entry, next) { + DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); + switch (event->p.gen.type) { + case EMUL_RESPONSE_APDU: + ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, + event->p.data.len); + break; + case EMUL_READER_INSERT: + ccid_card_ccid_attach(&card->base); + break; + case EMUL_READER_REMOVE: + ccid_card_ccid_detach(&card->base); + break; + case EMUL_CARD_INSERT: + assert(event->p.data.len <= MAX_ATR_SIZE); + card->atr_length = event->p.data.len; + memcpy(card->atr, event->p.data.data, card->atr_length); + ccid_card_card_inserted(&card->base); + break; + case EMUL_CARD_REMOVE: + ccid_card_card_removed(&card->base); + break; + case EMUL_ERROR: + ccid_card_card_error(&card->base, event->p.error.code); + break; + default: + DPRINTF(card, 2, "unexpected event\n"); + break; + } + g_free(event); + } + QSIMPLEQ_INIT(&card->event_list); +} + +static int init_event_notifier(EmulatedState *card, Error **errp) +{ + if (event_notifier_init(&card->notifier, false) < 0) { + error_setg(errp, "ccid-card-emul: event notifier creation failed"); + return -1; + } + event_notifier_set_handler(&card->notifier, card_event_handler); + return 0; +} + +static void clean_event_notifier(EmulatedState *card) +{ + event_notifier_set_handler(&card->notifier, NULL); + event_notifier_cleanup(&card->notifier); +} + +#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" +#define CERTIFICATES_ARGS_TEMPLATE\ + "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" + +static int wrap_vcard_emul_init(VCardEmulOptions *options) +{ + static int called; + static int options_was_null; + + if (called) { + if ((options == NULL) != options_was_null) { + printf("%s: warning: running emulated with certificates" + " and emulated side by side is not supported\n", + __func__); + return VCARD_EMUL_FAIL; + } + vcard_emul_replay_insertion_events(); + return VCARD_EMUL_OK; + } + options_was_null = (options == NULL); + called = 1; + return vcard_emul_init(options); +} + +static int emulated_initialize_vcard_from_certificates(EmulatedState *card) +{ + char emul_args[200]; + VCardEmulOptions *options = NULL; + + snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, + card->db ? card->db : CERTIFICATES_DEFAULT_DB, + card->cert1, card->cert2, card->cert3); + options = vcard_emul_options(emul_args); + if (options == NULL) { + printf("%s: warning: not using certificates due to" + " initialization error\n", __func__); + } + return wrap_vcard_emul_init(options); +} + +typedef struct EnumTable { + const char *name; + uint32_t value; +} EnumTable; + +static const EnumTable backend_enum_table[] = { + {BACKEND_NSS_EMULATED_NAME, BACKEND_NSS_EMULATED}, + {BACKEND_CERTIFICATES_NAME, BACKEND_CERTIFICATES}, + {NULL, 0}, +}; + +static uint32_t parse_enumeration(char *str, + const EnumTable *table, uint32_t not_found_value) +{ + uint32_t ret = not_found_value; + + if (str == NULL) + return 0; + + while (table->name != NULL) { + if (strcmp(table->name, str) == 0) { + ret = table->value; + break; + } + table++; + } + return ret; +} + +static void emulated_realize(CCIDCardState *base, Error **errp) +{ + EmulatedState *card = EMULATED_CCID_CARD(base); + VCardEmulError ret; + const EnumTable *ptable; + + QSIMPLEQ_INIT(&card->event_list); + QSIMPLEQ_INIT(&card->guest_apdu_list); + qemu_mutex_init(&card->event_list_mutex); + qemu_mutex_init(&card->vreader_mutex); + qemu_mutex_init(&card->handle_apdu_mutex); + qemu_cond_init(&card->handle_apdu_cond); + card->reader = NULL; + card->quit_apdu_thread = 0; + if (init_event_notifier(card, errp) < 0) { + goto out1; + } + + card->backend = 0; + if (card->backend_str) { + card->backend = parse_enumeration(card->backend_str, + backend_enum_table, 0); + } + + if (card->backend == 0) { + error_setg(errp, "backend must be one of:"); + for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) { + error_append_hint(errp, "%s\n", ptable->name); + } + goto out2; + } + + /* TODO: a passthru backened that works on local machine. third card type?*/ + if (card->backend == BACKEND_CERTIFICATES) { + if (card->cert1 != NULL && card->cert2 != NULL && card->cert3 != NULL) { + ret = emulated_initialize_vcard_from_certificates(card); + } else { + error_setg(errp, "%s: you must provide all three certs for" + " certificates backend", TYPE_EMULATED_CCID); + goto out2; + } + } else { + if (card->backend != BACKEND_NSS_EMULATED) { + error_setg(errp, "%s: bad backend specified. The options are:%s" + " (default), %s.", TYPE_EMULATED_CCID, + BACKEND_NSS_EMULATED_NAME, BACKEND_CERTIFICATES_NAME); + goto out2; + } + if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) { + error_setg(errp, "%s: unexpected cert parameters to nss emulated " + "backend", TYPE_EMULATED_CCID); + goto out2; + } + /* default to mirroring the local hardware readers */ + ret = wrap_vcard_emul_init(NULL); + } + if (ret != VCARD_EMUL_OK) { + error_setg(errp, "%s: failed to initialize vcard", TYPE_EMULATED_CCID); + goto out2; + } + qemu_thread_create(&card->event_thread_id, "ccid/event", event_thread, + card, QEMU_THREAD_JOINABLE); + qemu_thread_create(&card->apdu_thread_id, "ccid/apdu", handle_apdu_thread, + card, QEMU_THREAD_JOINABLE); + + return; + +out2: + clean_event_notifier(card); +out1: + qemu_cond_destroy(&card->handle_apdu_cond); + qemu_mutex_destroy(&card->handle_apdu_mutex); + qemu_mutex_destroy(&card->vreader_mutex); + qemu_mutex_destroy(&card->event_list_mutex); +} + +static void emulated_unrealize(CCIDCardState *base) +{ + EmulatedState *card = EMULATED_CCID_CARD(base); + VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); + + vevent_queue_vevent(vevent); /* stop vevent thread */ + qemu_thread_join(&card->event_thread_id); + + card->quit_apdu_thread = 1; /* stop handle_apdu thread */ + qemu_cond_signal(&card->handle_apdu_cond); + qemu_thread_join(&card->apdu_thread_id); + + clean_event_notifier(card); + /* threads exited, can destroy all condvars/mutexes */ + qemu_cond_destroy(&card->handle_apdu_cond); + qemu_mutex_destroy(&card->handle_apdu_mutex); + qemu_mutex_destroy(&card->vreader_mutex); + qemu_mutex_destroy(&card->event_list_mutex); +} + +static Property emulated_card_properties[] = { + DEFINE_PROP_STRING("backend", EmulatedState, backend_str), + DEFINE_PROP_STRING("cert1", EmulatedState, cert1), + DEFINE_PROP_STRING("cert2", EmulatedState, cert2), + DEFINE_PROP_STRING("cert3", EmulatedState, cert3), + DEFINE_PROP_STRING("db", EmulatedState, db), + DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void emulated_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + CCIDCardClass *cc = CCID_CARD_CLASS(klass); + + cc->realize = emulated_realize; + cc->unrealize = emulated_unrealize; + cc->get_atr = emulated_get_atr; + cc->apdu_from_guest = emulated_apdu_from_guest; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + dc->desc = "emulated smartcard"; + device_class_set_props(dc, emulated_card_properties); +} + +static const TypeInfo emulated_card_info = { + .name = TYPE_EMULATED_CCID, + .parent = TYPE_CCID_CARD, + .instance_size = sizeof(EmulatedState), + .class_init = emulated_class_initfn, +}; +module_obj(TYPE_EMULATED_CCID); + +static void ccid_card_emulated_register_types(void) +{ + type_register_static(&emulated_card_info); +} + +type_init(ccid_card_emulated_register_types) diff --git a/hw/usb/ccid-card-passthru.c b/hw/usb/ccid-card-passthru.c new file mode 100644 index 000000000..fa3040fb7 --- /dev/null +++ b/hw/usb/ccid-card-passthru.c @@ -0,0 +1,424 @@ +/* + * CCID Passthru Card Device emulation + * + * Copyright (c) 2011 Red Hat. + * Written by Alon Levy. + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "qemu/units.h" +#include <libcacard.h> +#include "chardev/char-fe.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "migration/vmstate.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "qemu/sockets.h" +#include "ccid.h" +#include "qapi/error.h" +#include "qom/object.h" + +#define DPRINTF(card, lvl, fmt, ...) \ +do { \ + if (lvl <= card->debug) { \ + printf("ccid-card-passthru: " fmt , ## __VA_ARGS__); \ + } \ +} while (0) + +#define D_WARN 1 +#define D_INFO 2 +#define D_MORE_INFO 3 +#define D_VERBOSE 4 + +/* TODO: do we still need this? */ +static const uint8_t DEFAULT_ATR[] = { +/* + * From some example somewhere + * 0x3B, 0xB0, 0x18, 0x00, 0xD1, 0x81, 0x05, 0xB1, 0x40, 0x38, 0x1F, 0x03, 0x28 + */ + +/* From an Athena smart card */ + 0x3B, 0xD5, 0x18, 0xFF, 0x80, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x73, 0xC8, 0x21, + 0x13, 0x08 +}; + +#define VSCARD_IN_SIZE (64 * KiB) + +/* maximum size of ATR - from 7816-3 */ +#define MAX_ATR_SIZE 40 + +typedef struct PassthruState PassthruState; + +struct PassthruState { + CCIDCardState base; + CharBackend cs; + uint8_t vscard_in_data[VSCARD_IN_SIZE]; + uint32_t vscard_in_pos; + uint32_t vscard_in_hdr; + uint8_t atr[MAX_ATR_SIZE]; + uint8_t atr_length; + uint8_t debug; +}; + +#define TYPE_CCID_PASSTHRU "ccid-card-passthru" +DECLARE_INSTANCE_CHECKER(PassthruState, PASSTHRU_CCID_CARD, + TYPE_CCID_PASSTHRU) + +/* + * VSCard protocol over chardev + * This code should not depend on the card type. + */ + +static void ccid_card_vscard_send_msg(PassthruState *s, + VSCMsgType type, uint32_t reader_id, + const uint8_t *payload, uint32_t length) +{ + VSCMsgHeader scr_msg_header; + + scr_msg_header.type = htonl(type); + scr_msg_header.reader_id = htonl(reader_id); + scr_msg_header.length = htonl(length); + /* XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks */ + qemu_chr_fe_write_all(&s->cs, (uint8_t *)&scr_msg_header, + sizeof(VSCMsgHeader)); + qemu_chr_fe_write_all(&s->cs, payload, length); +} + +static void ccid_card_vscard_send_apdu(PassthruState *s, + const uint8_t *apdu, uint32_t length) +{ + ccid_card_vscard_send_msg( + s, VSC_APDU, VSCARD_MINIMAL_READER_ID, apdu, length); +} + +static void ccid_card_vscard_send_error(PassthruState *s, + uint32_t reader_id, VSCErrorCode code) +{ + VSCMsgError msg = {.code = htonl(code)}; + + ccid_card_vscard_send_msg( + s, VSC_Error, reader_id, (uint8_t *)&msg, sizeof(msg)); +} + +static void ccid_card_vscard_send_init(PassthruState *s) +{ + VSCMsgInit msg = { + .version = htonl(VSCARD_VERSION), + .magic = VSCARD_MAGIC, + .capabilities = {0} + }; + + ccid_card_vscard_send_msg(s, VSC_Init, VSCARD_UNDEFINED_READER_ID, + (uint8_t *)&msg, sizeof(msg)); +} + +static int ccid_card_vscard_can_read(void *opaque) +{ + PassthruState *card = opaque; + + return VSCARD_IN_SIZE >= card->vscard_in_pos ? + VSCARD_IN_SIZE - card->vscard_in_pos : 0; +} + +static void ccid_card_vscard_handle_init( + PassthruState *card, VSCMsgHeader *hdr, VSCMsgInit *init) +{ + uint32_t *capabilities; + int num_capabilities; + int i; + + capabilities = init->capabilities; + num_capabilities = + 1 + ((hdr->length - sizeof(VSCMsgInit)) / sizeof(uint32_t)); + init->version = ntohl(init->version); + for (i = 0 ; i < num_capabilities; ++i) { + capabilities[i] = ntohl(capabilities[i]); + } + if (init->magic != VSCARD_MAGIC) { + error_report("wrong magic"); + /* we can't disconnect the chardev */ + } + if (init->version != VSCARD_VERSION) { + DPRINTF(card, D_WARN, + "got version %d, have %d", init->version, VSCARD_VERSION); + } + /* future handling of capabilities, none exist atm */ + ccid_card_vscard_send_init(card); +} + +static int check_atr(PassthruState *card, uint8_t *data, int len) +{ + int historical_length, opt_bytes; + int td_count = 0; + int td; + + if (len < 2) { + return 0; + } + historical_length = data[1] & 0xf; + opt_bytes = 0; + if (data[0] != 0x3b && data[0] != 0x3f) { + DPRINTF(card, D_WARN, "atr's T0 is 0x%X, not in {0x3b, 0x3f}\n", + data[0]); + return 0; + } + td_count = 0; + td = data[1] >> 4; + while (td && td_count < 2 && opt_bytes + historical_length + 2 < len) { + td_count++; + if (td & 0x1) { + opt_bytes++; + } + if (td & 0x2) { + opt_bytes++; + } + if (td & 0x4) { + opt_bytes++; + } + if (td & 0x8) { + opt_bytes++; + td = data[opt_bytes + 2] >> 4; + } + } + if (len < 2 + historical_length + opt_bytes) { + DPRINTF(card, D_WARN, + "atr too short: len %d, but historical_len %d, T1 0x%X\n", + len, historical_length, data[1]); + return 0; + } + if (len > 2 + historical_length + opt_bytes) { + DPRINTF(card, D_WARN, + "atr too long: len %d, but hist/opt %d/%d, T1 0x%X\n", + len, historical_length, opt_bytes, data[1]); + /* let it through */ + } + DPRINTF(card, D_VERBOSE, + "atr passes check: %d total length, %d historical, %d optional\n", + len, historical_length, opt_bytes); + + return 1; +} + +static void ccid_card_vscard_handle_message(PassthruState *card, + VSCMsgHeader *scr_msg_header) +{ + uint8_t *data = (uint8_t *)&scr_msg_header[1]; + + switch (scr_msg_header->type) { + case VSC_ATR: + DPRINTF(card, D_INFO, "VSC_ATR %d\n", scr_msg_header->length); + if (scr_msg_header->length > MAX_ATR_SIZE) { + error_report("ATR size exceeds spec, ignoring"); + ccid_card_vscard_send_error(card, scr_msg_header->reader_id, + VSC_GENERAL_ERROR); + break; + } + if (!check_atr(card, data, scr_msg_header->length)) { + error_report("ATR is inconsistent, ignoring"); + ccid_card_vscard_send_error(card, scr_msg_header->reader_id, + VSC_GENERAL_ERROR); + break; + } + memcpy(card->atr, data, scr_msg_header->length); + card->atr_length = scr_msg_header->length; + ccid_card_card_inserted(&card->base); + ccid_card_vscard_send_error(card, scr_msg_header->reader_id, + VSC_SUCCESS); + break; + case VSC_APDU: + ccid_card_send_apdu_to_guest( + &card->base, data, scr_msg_header->length); + break; + case VSC_CardRemove: + DPRINTF(card, D_INFO, "VSC_CardRemove\n"); + ccid_card_card_removed(&card->base); + ccid_card_vscard_send_error(card, + scr_msg_header->reader_id, VSC_SUCCESS); + break; + case VSC_Init: + ccid_card_vscard_handle_init( + card, scr_msg_header, (VSCMsgInit *)data); + break; + case VSC_Error: + ccid_card_card_error(&card->base, *(uint32_t *)data); + break; + case VSC_ReaderAdd: + if (ccid_card_ccid_attach(&card->base) < 0) { + ccid_card_vscard_send_error(card, VSCARD_UNDEFINED_READER_ID, + VSC_CANNOT_ADD_MORE_READERS); + } else { + ccid_card_vscard_send_error(card, VSCARD_MINIMAL_READER_ID, + VSC_SUCCESS); + } + break; + case VSC_ReaderRemove: + ccid_card_ccid_detach(&card->base); + ccid_card_vscard_send_error(card, + scr_msg_header->reader_id, VSC_SUCCESS); + break; + default: + printf("usb-ccid: chardev: unexpected message of type %X\n", + scr_msg_header->type); + ccid_card_vscard_send_error(card, scr_msg_header->reader_id, + VSC_GENERAL_ERROR); + } +} + +static void ccid_card_vscard_drop_connection(PassthruState *card) +{ + qemu_chr_fe_deinit(&card->cs, true); + card->vscard_in_pos = card->vscard_in_hdr = 0; +} + +static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size) +{ + PassthruState *card = opaque; + VSCMsgHeader *hdr; + + if (card->vscard_in_pos + size > VSCARD_IN_SIZE) { + error_report("no room for data: pos %u + size %d > %" PRId64 "." + " dropping connection.", + card->vscard_in_pos, size, VSCARD_IN_SIZE); + ccid_card_vscard_drop_connection(card); + return; + } + assert(card->vscard_in_pos < VSCARD_IN_SIZE); + assert(card->vscard_in_hdr < VSCARD_IN_SIZE); + memcpy(card->vscard_in_data + card->vscard_in_pos, buf, size); + card->vscard_in_pos += size; + hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr); + + while ((card->vscard_in_pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader)) + &&(card->vscard_in_pos - card->vscard_in_hdr >= + sizeof(VSCMsgHeader) + ntohl(hdr->length))) { + hdr->reader_id = ntohl(hdr->reader_id); + hdr->length = ntohl(hdr->length); + hdr->type = ntohl(hdr->type); + ccid_card_vscard_handle_message(card, hdr); + card->vscard_in_hdr += hdr->length + sizeof(VSCMsgHeader); + hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr); + } + if (card->vscard_in_hdr == card->vscard_in_pos) { + card->vscard_in_pos = card->vscard_in_hdr = 0; + } +} + +static void ccid_card_vscard_event(void *opaque, QEMUChrEvent event) +{ + PassthruState *card = opaque; + + switch (event) { + case CHR_EVENT_BREAK: + card->vscard_in_pos = card->vscard_in_hdr = 0; + break; + case CHR_EVENT_OPENED: + DPRINTF(card, D_INFO, "%s: CHR_EVENT_OPENED\n", __func__); + break; + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + case CHR_EVENT_CLOSED: + /* Ignore */ + break; + } +} + +/* End VSCard handling */ + +static void passthru_apdu_from_guest( + CCIDCardState *base, const uint8_t *apdu, uint32_t len) +{ + PassthruState *card = PASSTHRU_CCID_CARD(base); + + if (!qemu_chr_fe_backend_connected(&card->cs)) { + printf("ccid-passthru: no chardev, discarding apdu length %u\n", len); + return; + } + ccid_card_vscard_send_apdu(card, apdu, len); +} + +static const uint8_t *passthru_get_atr(CCIDCardState *base, uint32_t *len) +{ + PassthruState *card = PASSTHRU_CCID_CARD(base); + + *len = card->atr_length; + return card->atr; +} + +static void passthru_realize(CCIDCardState *base, Error **errp) +{ + PassthruState *card = PASSTHRU_CCID_CARD(base); + + card->vscard_in_pos = 0; + card->vscard_in_hdr = 0; + if (qemu_chr_fe_backend_connected(&card->cs)) { + DPRINTF(card, D_INFO, "ccid-card-passthru: initing chardev"); + qemu_chr_fe_set_handlers(&card->cs, + ccid_card_vscard_can_read, + ccid_card_vscard_read, + ccid_card_vscard_event, NULL, card, NULL, true); + ccid_card_vscard_send_init(card); + } else { + error_setg(errp, "missing chardev"); + return; + } + card->debug = parse_debug_env("QEMU_CCID_PASSTHRU_DEBUG", D_VERBOSE, + card->debug); + assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE); + memcpy(card->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR)); + card->atr_length = sizeof(DEFAULT_ATR); +} + +static const VMStateDescription passthru_vmstate = { + .name = "ccid-card-passthru", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BUFFER(vscard_in_data, PassthruState), + VMSTATE_UINT32(vscard_in_pos, PassthruState), + VMSTATE_UINT32(vscard_in_hdr, PassthruState), + VMSTATE_BUFFER(atr, PassthruState), + VMSTATE_UINT8(atr_length, PassthruState), + VMSTATE_END_OF_LIST() + } +}; + +static Property passthru_card_properties[] = { + DEFINE_PROP_CHR("chardev", PassthruState, cs), + DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void passthru_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + CCIDCardClass *cc = CCID_CARD_CLASS(klass); + + cc->realize = passthru_realize; + cc->get_atr = passthru_get_atr; + cc->apdu_from_guest = passthru_apdu_from_guest; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + dc->desc = "passthrough smartcard"; + dc->vmsd = &passthru_vmstate; + device_class_set_props(dc, passthru_card_properties); +} + +static const TypeInfo passthru_card_info = { + .name = TYPE_CCID_PASSTHRU, + .parent = TYPE_CCID_CARD, + .instance_size = sizeof(PassthruState), + .class_init = passthru_class_initfn, +}; +module_obj(TYPE_CCID_PASSTHRU); + +static void ccid_card_passthru_register_types(void) +{ + type_register_static(&passthru_card_info); +} + +type_init(ccid_card_passthru_register_types) diff --git a/hw/usb/ccid.h b/hw/usb/ccid.h new file mode 100644 index 000000000..6b82a55bd --- /dev/null +++ b/hw/usb/ccid.h @@ -0,0 +1,62 @@ +/* + * CCID Passthru Card Device emulation + * + * Copyright (c) 2011 Red Hat. + * Written by Alon Levy. + * + * This code is licensed under the GNU LGPL, version 2 or later. + */ + +#ifndef CCID_H +#define CCID_H + +#include "hw/qdev-core.h" +#include "qom/object.h" + +typedef struct CCIDCardInfo CCIDCardInfo; + +#define TYPE_CCID_CARD "ccid-card" +OBJECT_DECLARE_TYPE(CCIDCardState, CCIDCardClass, CCID_CARD) + +/* + * callbacks to be used by the CCID device (hw/usb-ccid.c) to call + * into the smartcard device (hw/ccid-card-*.c) + */ +struct CCIDCardClass { + /*< private >*/ + DeviceClass parent_class; + /*< public >*/ + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); + void (*apdu_from_guest)(CCIDCardState *card, + const uint8_t *apdu, + uint32_t len); + void (*realize)(CCIDCardState *card, Error **errp); + void (*unrealize)(CCIDCardState *card); +}; + +/* + * state of the CCID Card device (i.e. hw/ccid-card-*.c) + */ +struct CCIDCardState { + DeviceState qdev; + uint32_t slot; /* For future use with multiple slot reader. */ +}; + +/* + * API for smartcard calling the CCID device (used by hw/ccid-card-*.c) + */ +void ccid_card_send_apdu_to_guest(CCIDCardState *card, + uint8_t *apdu, + uint32_t len); +void ccid_card_card_removed(CCIDCardState *card); +void ccid_card_card_inserted(CCIDCardState *card); +void ccid_card_card_error(CCIDCardState *card, uint64_t error); + +/* + * support guest visible insertion/removal of ccid devices based on actual + * devices connected/removed. Called by card implementation (passthru, local) + */ +int ccid_card_ccid_attach(CCIDCardState *card); +void ccid_card_ccid_detach(CCIDCardState *card); + +#endif /* CCID_H */ diff --git a/hw/usb/chipidea.c b/hw/usb/chipidea.c new file mode 100644 index 000000000..b1c85404d --- /dev/null +++ b/hw/usb/chipidea.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2018, Impinj, Inc. + * + * Chipidea USB block emulation code + * + * Author: Andrey Smirnov <andrew.smirnov@gmail.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/usb/hcd-ehci.h" +#include "hw/usb/chipidea.h" +#include "qemu/module.h" + +enum { + CHIPIDEA_USBx_DCIVERSION = 0x000, + CHIPIDEA_USBx_DCCPARAMS = 0x004, + CHIPIDEA_USBx_DCCPARAMS_HC = BIT(8), +}; + +static uint64_t chipidea_read(void *opaque, hwaddr offset, + unsigned size) +{ + return 0; +} + +static void chipidea_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ +} + +static const struct MemoryRegionOps chipidea_ops = { + .read = chipidea_read, + .write = chipidea_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the + * real device but in practice there is no reason for a guest + * to access this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static uint64_t chipidea_dc_read(void *opaque, hwaddr offset, + unsigned size) +{ + switch (offset) { + case CHIPIDEA_USBx_DCIVERSION: + return 0x1; + case CHIPIDEA_USBx_DCCPARAMS: + /* + * Real hardware (at least i.MX7) will also report the + * controller as "Device Capable" (and 8 supported endpoints), + * but there doesn't seem to be much point in doing so, since + * we don't emulate that part. + */ + return CHIPIDEA_USBx_DCCPARAMS_HC; + } + + return 0; +} + +static void chipidea_dc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ +} + +static const struct MemoryRegionOps chipidea_dc_ops = { + .read = chipidea_dc_read, + .write = chipidea_dc_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void chipidea_init(Object *obj) +{ + EHCIState *ehci = &SYS_BUS_EHCI(obj)->ehci; + ChipideaState *ci = CHIPIDEA(obj); + int i; + + for (i = 0; i < ARRAY_SIZE(ci->iomem); i++) { + const struct { + const char *name; + hwaddr offset; + uint64_t size; + const struct MemoryRegionOps *ops; + } regions[ARRAY_SIZE(ci->iomem)] = { + /* + * Registers located between offsets 0x000 and 0xFC + */ + { + .name = TYPE_CHIPIDEA ".misc", + .offset = 0x000, + .size = 0x100, + .ops = &chipidea_ops, + }, + /* + * Registers located between offsets 0x1A4 and 0x1DC + */ + { + .name = TYPE_CHIPIDEA ".endpoints", + .offset = 0x1A4, + .size = 0x1DC - 0x1A4 + 4, + .ops = &chipidea_ops, + }, + /* + * USB_x_DCIVERSION and USB_x_DCCPARAMS + */ + { + .name = TYPE_CHIPIDEA ".dc", + .offset = 0x120, + .size = 8, + .ops = &chipidea_dc_ops, + }, + }; + + memory_region_init_io(&ci->iomem[i], + obj, + regions[i].ops, + ci, + regions[i].name, + regions[i].size); + + memory_region_add_subregion(&ehci->mem, + regions[i].offset, + &ci->iomem[i]); + } +} + +static void chipidea_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(klass); + + /* + * Offsets used were taken from i.MX7Dual Applications Processor + * Reference Manual, Rev 0.1, p. 3177, Table 11-59 + */ + sec->capsbase = 0x100; + sec->opregbase = 0x140; + sec->portnr = 1; + + set_bit(DEVICE_CATEGORY_USB, dc->categories); + dc->desc = "Chipidea USB Module"; +} + +static const TypeInfo chipidea_info = { + .name = TYPE_CHIPIDEA, + .parent = TYPE_SYS_BUS_EHCI, + .instance_size = sizeof(ChipideaState), + .instance_init = chipidea_init, + .class_init = chipidea_class_init, +}; + +static void chipidea_register_type(void) +{ + type_register_static(&chipidea_info); +} +type_init(chipidea_register_type) diff --git a/hw/usb/combined-packet.c b/hw/usb/combined-packet.c new file mode 100644 index 000000000..e56802f89 --- /dev/null +++ b/hw/usb/combined-packet.c @@ -0,0 +1,190 @@ +/* + * QEMU USB packet combining code (for input pipelining) + * + * Copyright(c) 2012 Red Hat, Inc. + * + * Red Hat Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * 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.1 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 program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "hw/usb.h" +#include "qemu/iov.h" +#include "trace.h" + +static void usb_combined_packet_add(USBCombinedPacket *combined, USBPacket *p) +{ + qemu_iovec_concat(&combined->iov, &p->iov, 0, p->iov.size); + QTAILQ_INSERT_TAIL(&combined->packets, p, combined_entry); + p->combined = combined; +} + +/* Note will free combined when the last packet gets removed */ +static void usb_combined_packet_remove(USBCombinedPacket *combined, + USBPacket *p) +{ + assert(p->combined == combined); + p->combined = NULL; + QTAILQ_REMOVE(&combined->packets, p, combined_entry); + if (QTAILQ_EMPTY(&combined->packets)) { + qemu_iovec_destroy(&combined->iov); + g_free(combined); + } +} + +/* Also handles completion of non combined packets for pipelined input eps */ +void usb_combined_input_packet_complete(USBDevice *dev, USBPacket *p) +{ + USBCombinedPacket *combined = p->combined; + USBEndpoint *ep = p->ep; + USBPacket *next; + int status, actual_length; + bool short_not_ok, done = false; + + if (combined == NULL) { + usb_packet_complete_one(dev, p); + goto leave; + } + + assert(combined->first == p && p == QTAILQ_FIRST(&combined->packets)); + + status = combined->first->status; + actual_length = combined->first->actual_length; + short_not_ok = QTAILQ_LAST(&combined->packets)->short_not_ok; + + QTAILQ_FOREACH_SAFE(p, &combined->packets, combined_entry, next) { + if (!done) { + /* Distribute data over uncombined packets */ + if (actual_length >= p->iov.size) { + p->actual_length = p->iov.size; + } else { + /* Send short or error packet to complete the transfer */ + p->actual_length = actual_length; + done = true; + } + /* Report status on the last packet */ + if (done || next == NULL) { + p->status = status; + } else { + p->status = USB_RET_SUCCESS; + } + p->short_not_ok = short_not_ok; + /* Note will free combined when the last packet gets removed! */ + usb_combined_packet_remove(combined, p); + usb_packet_complete_one(dev, p); + actual_length -= p->actual_length; + } else { + /* Remove any leftover packets from the queue */ + p->status = USB_RET_REMOVE_FROM_QUEUE; + /* Note will free combined on the last packet! */ + dev->port->ops->complete(dev->port, p); + } + } + /* Do not use combined here, it has been freed! */ +leave: + /* Check if there are packets in the queue waiting for our completion */ + usb_ep_combine_input_packets(ep); +} + +/* May only be called for combined packets! */ +void usb_combined_packet_cancel(USBDevice *dev, USBPacket *p) +{ + USBCombinedPacket *combined = p->combined; + assert(combined != NULL); + USBPacket *first = p->combined->first; + + /* Note will free combined on the last packet! */ + usb_combined_packet_remove(combined, p); + if (p == first) { + usb_device_cancel_packet(dev, p); + } +} + +/* + * Large input transfers can get split into multiple input packets, this + * function recombines them, removing the short_not_ok checks which all but + * the last packet of such splits transfers have, thereby allowing input + * transfer pipelining (which we cannot do on short_not_ok transfers) + */ +void usb_ep_combine_input_packets(USBEndpoint *ep) +{ + USBPacket *p, *u, *next, *prev = NULL, *first = NULL; + USBPort *port = ep->dev->port; + int totalsize; + + assert(ep->pipeline); + assert(ep->pid == USB_TOKEN_IN); + + QTAILQ_FOREACH_SAFE(p, &ep->queue, queue, next) { + /* Empty the queue on a halt */ + if (ep->halted) { + p->status = USB_RET_REMOVE_FROM_QUEUE; + port->ops->complete(port, p); + continue; + } + + /* Skip packets already submitted to the device */ + if (p->state == USB_PACKET_ASYNC) { + prev = p; + continue; + } + usb_packet_check_state(p, USB_PACKET_QUEUED); + + /* + * If the previous (combined) packet has the short_not_ok flag set + * stop, as we must not submit packets to the device after a transfer + * ending with short_not_ok packet. + */ + if (prev && prev->short_not_ok) { + break; + } + + if (first) { + if (first->combined == NULL) { + USBCombinedPacket *combined = g_new0(USBCombinedPacket, 1); + + combined->first = first; + QTAILQ_INIT(&combined->packets); + qemu_iovec_init(&combined->iov, 2); + usb_combined_packet_add(combined, first); + } + usb_combined_packet_add(first->combined, p); + } else { + first = p; + } + + /* Is this packet the last one of a (combined) transfer? */ + totalsize = (p->combined) ? p->combined->iov.size : p->iov.size; + if ((p->iov.size % ep->max_packet_size) != 0 || !p->short_not_ok || + next == NULL || + /* Work around for Linux usbfs bulk splitting + migration */ + (totalsize == (16 * KiB - 36) && p->int_req) || + /* Next package may grow combined package over 1MiB */ + totalsize > 1 * MiB - ep->max_packet_size) { + usb_device_handle_data(ep->dev, first); + assert(first->status == USB_RET_ASYNC); + if (first->combined) { + QTAILQ_FOREACH(u, &first->combined->packets, combined_entry) { + usb_packet_set_state(u, USB_PACKET_ASYNC); + } + } else { + usb_packet_set_state(first, USB_PACKET_ASYNC); + } + first = NULL; + prev = p; + } + } +} diff --git a/hw/usb/core.c b/hw/usb/core.c new file mode 100644 index 000000000..975f76250 --- /dev/null +++ b/hw/usb/core.c @@ -0,0 +1,821 @@ +/* + * 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/osdep.h" +#include "hw/usb.h" +#include "qemu/iov.h" +#include "trace.h" + +void usb_pick_speed(USBPort *port) +{ + static const int speeds[] = { + USB_SPEED_SUPER, + USB_SPEED_HIGH, + USB_SPEED_FULL, + USB_SPEED_LOW, + }; + USBDevice *udev = port->dev; + int i; + + for (i = 0; i < ARRAY_SIZE(speeds); i++) { + if ((udev->speedmask & (1 << speeds[i])) && + (port->speedmask & (1 << speeds[i]))) { + udev->speed = speeds[i]; + return; + } + } +} + +void usb_attach(USBPort *port) +{ + USBDevice *dev = port->dev; + + assert(dev != NULL); + assert(dev->attached); + assert(dev->state == USB_STATE_NOTATTACHED); + usb_pick_speed(port); + 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; + } + usb_device_handle_reset(dev); + dev->remote_wakeup = 0; + dev->addr = 0; + dev->state = USB_STATE_DEFAULT; +} + +void usb_wakeup(USBEndpoint *ep, unsigned int stream) +{ + USBDevice *dev = ep->dev; + USBBus *bus = usb_bus_from_device(dev); + + if (!phase_check(PHASE_MACHINE_READY)) { + /* + * This is machine init cold plug. No need to wakeup anyone, + * all devices will be reset anyway. And trying to wakeup can + * cause problems due to hitting uninitialized devices. + */ + return; + } + 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, stream); + } +} + +/**********************/ + +/* 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 void do_token_setup(USBDevice *s, USBPacket *p) +{ + int request, value, index; + unsigned int setup_len; + + if (p->iov.size != 8) { + p->status = USB_RET_STALL; + return; + } + + usb_packet_copy(p, s->setup_buf, p->iov.size); + s->setup_index = 0; + p->actual_length = 0; + setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + if (setup_len > sizeof(s->data_buf)) { + fprintf(stderr, + "usb_generic_handle_packet: ctrl buffer too small (%u > %zu)\n", + setup_len, sizeof(s->data_buf)); + p->status = USB_RET_STALL; + return; + } + s->setup_len = setup_len; + + 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) { + usb_pcap_ctrl(p, true); + usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (p->status == USB_RET_ASYNC) { + s->setup_state = SETUP_STATE_SETUP; + } + if (p->status != USB_RET_SUCCESS) { + return; + } + + if (p->actual_length < s->setup_len) { + s->setup_len = p->actual_length; + } + s->setup_state = SETUP_STATE_DATA; + } else { + if (s->setup_len == 0) + s->setup_state = SETUP_STATE_ACK; + else + s->setup_state = SETUP_STATE_DATA; + } + + p->actual_length = 8; +} + +static void do_token_in(USBDevice *s, USBPacket *p) +{ + int request, value, index; + + 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)) { + usb_pcap_ctrl(p, true); + usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (p->status == USB_RET_ASYNC) { + return; + } + s->setup_state = SETUP_STATE_IDLE; + p->actual_length = 0; + usb_pcap_ctrl(p, false); + } + break; + + 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; + } + s->setup_state = SETUP_STATE_IDLE; + p->status = USB_RET_STALL; + usb_pcap_ctrl(p, false); + break; + + default: + p->status = USB_RET_STALL; + } +} + +static void 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; + usb_pcap_ctrl(p, false); + /* transfer OK */ + } else { + /* ignore additional output */ + } + break; + + 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; + } + s->setup_state = SETUP_STATE_IDLE; + p->status = USB_RET_STALL; + usb_pcap_ctrl(p, false); + break; + + default: + p->status = USB_RET_STALL; + } +} + +static void do_parameter(USBDevice *s, USBPacket *p) +{ + int i, request, value, index; + unsigned int setup_len; + + for (i = 0; i < 8; i++) { + s->setup_buf[i] = p->parameter >> (i*8); + } + + s->setup_state = SETUP_STATE_PARAM; + 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]; + + setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + if (setup_len > sizeof(s->data_buf)) { + fprintf(stderr, + "usb_generic_handle_packet: ctrl buffer too small (%u > %zu)\n", + setup_len, sizeof(s->data_buf)); + p->status = USB_RET_STALL; + return; + } + s->setup_len = setup_len; + + if (p->pid == USB_TOKEN_OUT) { + usb_packet_copy(p, s->data_buf, s->setup_len); + } + + usb_pcap_ctrl(p, true); + usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (p->status == USB_RET_ASYNC) { + return; + } + + if (p->actual_length < s->setup_len) { + s->setup_len = p->actual_length; + } + if (p->pid == USB_TOKEN_IN) { + p->actual_length = 0; + usb_packet_copy(p, s->data_buf, s->setup_len); + } + usb_pcap_ctrl(p, false); +} + +/* 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->status < 0) { + s->setup_state = SETUP_STATE_IDLE; + usb_pcap_ctrl(p, false); + } + + switch (s->setup_state) { + case SETUP_STATE_SETUP: + if (p->actual_length < s->setup_len) { + s->setup_len = p->actual_length; + } + s->setup_state = SETUP_STATE_DATA; + p->actual_length = 8; + break; + + case SETUP_STATE_ACK: + s->setup_state = SETUP_STATE_IDLE; + p->actual_length = 0; + usb_pcap_ctrl(p, false); + break; + + case SETUP_STATE_PARAM: + if (p->actual_length < s->setup_len) { + s->setup_len = p->actual_length; + } + if (p->pid == USB_TOKEN_IN) { + p->actual_length = 0; + usb_packet_copy(p, s->data_buf, s->setup_len); + } + break; + + default: + break; + } + usb_packet_complete(s, p); +} + +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 void usb_process_one(USBPacket *p) +{ + USBDevice *dev = p->ep->dev; + bool nak; + + /* + * Handlers expect status to be initialized to USB_RET_SUCCESS, but it + * can be USB_RET_NAK here from a previous usb_process_one() call, + * or USB_RET_ASYNC from going through usb_queue_one(). + */ + nak = (p->status == USB_RET_NAK); + p->status = USB_RET_SUCCESS; + + if (p->ep->nr == 0) { + /* control pipe */ + if (p->parameter) { + do_parameter(dev, p); + return; + } + switch (p->pid) { + case USB_TOKEN_SETUP: + do_token_setup(dev, p); + break; + case USB_TOKEN_IN: + do_token_in(dev, p); + break; + case USB_TOKEN_OUT: + do_token_out(dev, p); + break; + default: + p->status = USB_RET_STALL; + } + } else { + /* data pipe */ + if (!nak) { + usb_pcap_data(p, true); + } + usb_device_handle_data(dev, p); + } +} + +static void usb_queue_one(USBPacket *p) +{ + usb_packet_set_state(p, USB_PACKET_QUEUED); + QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue); + p->status = USB_RET_ASYNC; +} + +/* Hand over a packet to a device for processing. p->status == + USB_RET_ASYNC indicates the processing isn't finished yet, the + driver will call usb_packet_complete() when done processing it. */ +void usb_handle_packet(USBDevice *dev, USBPacket *p) +{ + if (dev == NULL) { + p->status = USB_RET_NODEV; + return; + } + assert(dev == p->ep->dev); + assert(dev->state == USB_STATE_DEFAULT); + usb_packet_check_state(p, USB_PACKET_SETUP); + assert(p->ep != NULL); + + /* Submitting a new packet clears halt */ + if (p->ep->halted) { + assert(QTAILQ_EMPTY(&p->ep->queue)); + p->ep->halted = false; + } + + if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline || p->stream) { + usb_process_one(p); + if (p->status == USB_RET_ASYNC) { + /* hcd drivers cannot handle async for isoc */ + assert(p->ep->type != USB_ENDPOINT_XFER_ISOC); + /* using async for interrupt packets breaks migration */ + assert(p->ep->type != USB_ENDPOINT_XFER_INT || + (dev->flags & (1 << USB_DEV_FLAG_IS_HOST))); + usb_packet_set_state(p, USB_PACKET_ASYNC); + QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue); + } else if (p->status == USB_RET_ADD_TO_QUEUE) { + usb_queue_one(p); + } else { + /* + * When pipelining is enabled usb-devices must always return async, + * otherwise packets can complete out of order! + */ + assert(p->stream || !p->ep->pipeline || + QTAILQ_EMPTY(&p->ep->queue)); + if (p->status != USB_RET_NAK) { + usb_pcap_data(p, false); + usb_packet_set_state(p, USB_PACKET_COMPLETE); + } + } + } else { + usb_queue_one(p); + } +} + +void usb_packet_complete_one(USBDevice *dev, USBPacket *p) +{ + USBEndpoint *ep = p->ep; + + assert(p->stream || QTAILQ_FIRST(&ep->queue) == p); + assert(p->status != USB_RET_ASYNC && p->status != USB_RET_NAK); + + if (p->status != USB_RET_SUCCESS || + (p->short_not_ok && (p->actual_length < p->iov.size))) { + ep->halted = true; + } + usb_pcap_data(p, false); + usb_packet_set_state(p, USB_PACKET_COMPLETE); + QTAILQ_REMOVE(&ep->queue, p, queue); + dev->port->ops->complete(dev->port, p); +} + +/* 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; + + usb_packet_check_state(p, USB_PACKET_ASYNC); + usb_packet_complete_one(dev, p); + + while (!QTAILQ_EMPTY(&ep->queue)) { + p = QTAILQ_FIRST(&ep->queue); + if (ep->halted) { + /* Empty the queue on a halt */ + p->status = USB_RET_REMOVE_FROM_QUEUE; + dev->port->ops->complete(dev->port, p); + continue; + } + if (p->state == USB_PACKET_ASYNC) { + break; + } + usb_packet_check_state(p, USB_PACKET_QUEUED); + usb_process_one(p); + if (p->status == USB_RET_ASYNC) { + usb_packet_set_state(p, USB_PACKET_ASYNC); + break; + } + usb_packet_complete_one(ep->dev, 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); +} + +static const char *usb_packet_state_name(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", + }; + if (state < ARRAY_SIZE(name)) { + return name[state]; + } + return "INVALID"; +} + +void usb_packet_check_state(USBPacket *p, USBPacketState expected) +{ + USBDevice *dev; + USBBus *bus; + + if (p->state == expected) { + return; + } + dev = p->ep->dev; + bus = usb_bus_from_device(dev); + trace_usb_packet_state_fault(bus->busnr, dev->port->path, p->ep->nr, p, + usb_packet_state_name(p->state), + usb_packet_state_name(expected)); + assert(!"usb packet state check failed"); +} + +void usb_packet_set_state(USBPacket *p, USBPacketState state) +{ + if (p->ep) { + 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, + usb_packet_state_name(p->state), + usb_packet_state_name(state)); + } else { + trace_usb_packet_state_change(-1, "", -1, p, + usb_packet_state_name(p->state), + usb_packet_state_name(state)); + } + p->state = state; +} + +void usb_packet_setup(USBPacket *p, int pid, + USBEndpoint *ep, unsigned int stream, + uint64_t id, bool short_not_ok, bool int_req) +{ + assert(!usb_packet_is_inflight(p)); + assert(p->iov.iov != NULL); + p->id = id; + p->pid = pid; + p->ep = ep; + p->stream = stream; + p->status = USB_RET_SUCCESS; + p->actual_length = 0; + p->parameter = 0; + p->short_not_ok = short_not_ok; + p->int_req = int_req; + p->combined = NULL; + 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) +{ + QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov; + + assert(p->actual_length >= 0); + assert(p->actual_length + bytes <= iov->size); + switch (p->pid) { + case USB_TOKEN_SETUP: + case USB_TOKEN_OUT: + iov_to_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); + break; + case USB_TOKEN_IN: + iov_from_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); + break; + default: + fprintf(stderr, "%s: invalid pid: %x\n", __func__, p->pid); + abort(); + } + p->actual_length += bytes; +} + +void usb_packet_skip(USBPacket *p, size_t bytes) +{ + QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov; + + assert(p->actual_length >= 0); + assert(p->actual_length + bytes <= iov->size); + if (p->pid == USB_TOKEN_IN) { + iov_memset(iov->iov, iov->niov, p->actual_length, 0, bytes); + } + p->actual_length += bytes; +} + +size_t usb_packet_size(USBPacket *p) +{ + return p->combined ? p->combined->iov.size : p->iov.size; +} + +void usb_packet_cleanup(USBPacket *p) +{ + assert(!usb_packet_is_inflight(p)); + qemu_iovec_destroy(&p->iov); +} + +void usb_ep_reset(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.max_packet_size = 64; + dev->ep_ctl.max_streams = 0; + dev->ep_ctl.dev = dev; + dev->ep_ctl.pipeline = false; + 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 = USB_INTERFACE_INVALID; + dev->ep_out[ep].ifnum = USB_INTERFACE_INVALID; + dev->ep_in[ep].max_packet_size = 0; + dev->ep_out[ep].max_packet_size = 0; + dev->ep_in[ep].max_streams = 0; + dev->ep_out[ep].max_streams = 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; + } +} + +void usb_ep_init(USBDevice *dev) +{ + int ep; + + usb_ep_reset(dev); + QTAILQ_INIT(&dev->ep_ctl.queue); + for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) { + 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; + + assert(dev != NULL); + if (ep == 0) { + return &dev->ep_ctl; + } + assert(pid == USB_TOKEN_IN || pid == USB_TOKEN_OUT); + assert(ep > 0 && ep <= USB_MAX_ENDPOINTS); + eps = (pid == USB_TOKEN_IN) ? dev->ep_in : dev->ep_out; + 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; +} + +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; +} + +void usb_ep_set_max_streams(USBDevice *dev, int pid, int ep, uint8_t raw) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + int MaxStreams; + + MaxStreams = raw & 0x1f; + if (MaxStreams) { + uep->max_streams = 1 << MaxStreams; + } else { + uep->max_streams = 0; + } +} + +void usb_ep_set_halted(USBDevice *dev, int pid, int ep, bool halted) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + uep->halted = halted; +} + +USBPacket *usb_ep_find_packet_by_id(USBDevice *dev, int pid, int ep, + uint64_t id) +{ + struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); + USBPacket *p; + + QTAILQ_FOREACH(p, &uep->queue, queue) { + if (p->id == id) { + return p; + } + } + + return NULL; +} diff --git a/hw/usb/desc-msos.c b/hw/usb/desc-msos.c new file mode 100644 index 000000000..c72c65b65 --- /dev/null +++ b/hw/usb/desc-msos.c @@ -0,0 +1,239 @@ +#include "qemu/osdep.h" +#include "hw/usb.h" +#include "desc.h" + +/* + * Microsoft OS Descriptors + * + * Windows tries to fetch some special descriptors with information + * specifically for windows. Presence is indicated using a special + * string @ index 0xee. There are two kinds of descriptors: + * + * compatid descriptor + * Used to bind drivers, if usb class isn't specific enough. + * Used for PTP/MTP for example (both share the same usb class). + * + * properties descriptor + * Does carry registry entries. They show up in + * HLM\SYSTEM\CurrentControlSet\Enum\USB\<devid>\<serial>\Device Parameters + * + * Note that Windows caches the stuff it got in the registry, so when + * playing with this you have to delete registry subtrees to make + * windows query the device again: + * HLM\SYSTEM\CurrentControlSet\Control\usbflags + * HLM\SYSTEM\CurrentControlSet\Enum\USB + * Windows will complain it can't delete entries on the second one. + * It has deleted everything it had permissions too, which is enough + * as this includes "Device Parameters". + * + * http://msdn.microsoft.com/en-us/library/windows/hardware/ff537430.aspx + * + */ + +/* ------------------------------------------------------------------ */ + +typedef struct msos_compat_hdr { + uint32_t dwLength; + uint8_t bcdVersion_lo; + uint8_t bcdVersion_hi; + uint8_t wIndex_lo; + uint8_t wIndex_hi; + uint8_t bCount; + uint8_t reserved[7]; +} QEMU_PACKED msos_compat_hdr; + +typedef struct msos_compat_func { + uint8_t bFirstInterfaceNumber; + uint8_t reserved_1; + char compatibleId[8]; + uint8_t subCompatibleId[8]; + uint8_t reserved_2[6]; +} QEMU_PACKED msos_compat_func; + +static int usb_desc_msos_compat(const USBDesc *desc, uint8_t *dest) +{ + msos_compat_hdr *hdr = (void *)dest; + msos_compat_func *func; + int length = sizeof(*hdr); + int count = 0; + + func = (void *)(dest + length); + func->bFirstInterfaceNumber = 0; + func->reserved_1 = 0x01; + if (desc->msos->CompatibleID) { + snprintf(func->compatibleId, sizeof(func->compatibleId), + "%s", desc->msos->CompatibleID); + } + length += sizeof(*func); + count++; + + hdr->dwLength = cpu_to_le32(length); + hdr->bcdVersion_lo = 0x00; + hdr->bcdVersion_hi = 0x01; + hdr->wIndex_lo = 0x04; + hdr->wIndex_hi = 0x00; + hdr->bCount = count; + return length; +} + +/* ------------------------------------------------------------------ */ + +typedef struct msos_prop_hdr { + uint32_t dwLength; + uint8_t bcdVersion_lo; + uint8_t bcdVersion_hi; + uint8_t wIndex_lo; + uint8_t wIndex_hi; + uint8_t wCount_lo; + uint8_t wCount_hi; +} QEMU_PACKED msos_prop_hdr; + +typedef struct msos_prop { + uint32_t dwLength; + uint32_t dwPropertyDataType; + uint8_t dwPropertyNameLength_lo; + uint8_t dwPropertyNameLength_hi; + uint8_t bPropertyName[]; +} QEMU_PACKED msos_prop; + +typedef struct msos_prop_data { + uint32_t dwPropertyDataLength; + uint8_t bPropertyData[]; +} QEMU_PACKED msos_prop_data; + +typedef enum msos_prop_type { + MSOS_REG_SZ = 1, + MSOS_REG_EXPAND_SZ = 2, + MSOS_REG_BINARY = 3, + MSOS_REG_DWORD_LE = 4, + MSOS_REG_DWORD_BE = 5, + MSOS_REG_LINK = 6, + MSOS_REG_MULTI_SZ = 7, +} msos_prop_type; + +static int usb_desc_msos_prop_name(struct msos_prop *prop, + const wchar_t *name) +{ + int length = wcslen(name) + 1; + int i; + + prop->dwPropertyNameLength_lo = usb_lo(length*2); + prop->dwPropertyNameLength_hi = usb_hi(length*2); + for (i = 0; i < length; i++) { + prop->bPropertyName[i*2] = usb_lo(name[i]); + prop->bPropertyName[i*2+1] = usb_hi(name[i]); + } + return length*2; +} + +static int usb_desc_msos_prop_str(uint8_t *dest, msos_prop_type type, + const wchar_t *name, const wchar_t *value) +{ + struct msos_prop *prop = (void *)dest; + struct msos_prop_data *data; + int length = sizeof(*prop); + int i, vlen = wcslen(value) + 1; + + prop->dwPropertyDataType = cpu_to_le32(type); + length += usb_desc_msos_prop_name(prop, name); + data = (void *)(dest + length); + + data->dwPropertyDataLength = cpu_to_le32(vlen*2); + length += sizeof(*prop); + + for (i = 0; i < vlen; i++) { + data->bPropertyData[i*2] = usb_lo(value[i]); + data->bPropertyData[i*2+1] = usb_hi(value[i]); + } + length += vlen*2; + + prop->dwLength = cpu_to_le32(length); + return length; +} + +static int usb_desc_msos_prop_dword(uint8_t *dest, const wchar_t *name, + uint32_t value) +{ + struct msos_prop *prop = (void *)dest; + struct msos_prop_data *data; + int length = sizeof(*prop); + + prop->dwPropertyDataType = cpu_to_le32(MSOS_REG_DWORD_LE); + length += usb_desc_msos_prop_name(prop, name); + data = (void *)(dest + length); + + data->dwPropertyDataLength = cpu_to_le32(4); + data->bPropertyData[0] = (value) & 0xff; + data->bPropertyData[1] = (value >> 8) & 0xff; + data->bPropertyData[2] = (value >> 16) & 0xff; + data->bPropertyData[3] = (value >> 24) & 0xff; + length += sizeof(*prop) + 4; + + prop->dwLength = cpu_to_le32(length); + return length; +} + +static int usb_desc_msos_prop(const USBDesc *desc, uint8_t *dest) +{ + msos_prop_hdr *hdr = (void *)dest; + int length = sizeof(*hdr); + int count = 0; + + if (desc->msos->Label) { + /* + * Given as example in the specs. Haven't figured yet where + * this label shows up in the windows gui. + */ + length += usb_desc_msos_prop_str(dest+length, MSOS_REG_SZ, + L"Label", desc->msos->Label); + count++; + } + + if (desc->msos->SelectiveSuspendEnabled) { + /* + * Signaling remote wakeup capability in the standard usb + * descriptors isn't enough to make windows actually use it. + * This is the "Yes, we really mean it" registry entry to flip + * the switch in the windows drivers. + */ + length += usb_desc_msos_prop_dword(dest+length, + L"SelectiveSuspendEnabled", 1); + count++; + } + + hdr->dwLength = cpu_to_le32(length); + hdr->bcdVersion_lo = 0x00; + hdr->bcdVersion_hi = 0x01; + hdr->wIndex_lo = 0x05; + hdr->wIndex_hi = 0x00; + hdr->wCount_lo = usb_lo(count); + hdr->wCount_hi = usb_hi(count); + return length; +} + +/* ------------------------------------------------------------------ */ + +int usb_desc_msos(const USBDesc *desc, USBPacket *p, + int index, uint8_t *dest, size_t len) +{ + void *buf = g_malloc0(4096); + int length = 0; + + switch (index) { + case 0x0004: + length = usb_desc_msos_compat(desc, buf); + break; + case 0x0005: + length = usb_desc_msos_prop(desc, buf); + break; + } + + if (length > len) { + length = len; + } + memcpy(dest, buf, length); + g_free(buf); + + p->actual_length = length; + return 0; +} diff --git a/hw/usb/desc.c b/hw/usb/desc.c new file mode 100644 index 000000000..8b6eaea40 --- /dev/null +++ b/hw/usb/desc.c @@ -0,0 +1,812 @@ +#include "qemu/osdep.h" + +#include "hw/usb.h" +#include "desc.h" +#include "trace.h" + +/* ------------------------------------------------------------------ */ + +int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, + bool msos, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x12; + USBDescriptor *d = (void *)dest; + + if (len < bLength) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_DEVICE; + + if (msos && dev->bcdUSB < 0x0200) { + /* + * Version 2.0+ required for microsoft os descriptors to work. + * Done this way so msos-desc compat property will handle both + * the version and the new descriptors being present. + */ + d->u.device.bcdUSB_lo = usb_lo(0x0200); + d->u.device.bcdUSB_hi = usb_hi(0x0200); + } else { + d->u.device.bcdUSB_lo = usb_lo(dev->bcdUSB); + d->u.device.bcdUSB_hi = usb_hi(dev->bcdUSB); + } + d->u.device.bDeviceClass = dev->bDeviceClass; + d->u.device.bDeviceSubClass = dev->bDeviceSubClass; + d->u.device.bDeviceProtocol = dev->bDeviceProtocol; + d->u.device.bMaxPacketSize0 = dev->bMaxPacketSize0; + + d->u.device.idVendor_lo = usb_lo(id->idVendor); + d->u.device.idVendor_hi = usb_hi(id->idVendor); + d->u.device.idProduct_lo = usb_lo(id->idProduct); + d->u.device.idProduct_hi = usb_hi(id->idProduct); + d->u.device.bcdDevice_lo = usb_lo(id->bcdDevice); + d->u.device.bcdDevice_hi = usb_hi(id->bcdDevice); + d->u.device.iManufacturer = id->iManufacturer; + d->u.device.iProduct = id->iProduct; + d->u.device.iSerialNumber = id->iSerialNumber; + + d->u.device.bNumConfigurations = dev->bNumConfigurations; + + return bLength; +} + +int usb_desc_device_qualifier(const USBDescDevice *dev, + uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x0a; + USBDescriptor *d = (void *)dest; + + if (len < bLength) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_DEVICE_QUALIFIER; + + d->u.device_qualifier.bcdUSB_lo = usb_lo(dev->bcdUSB); + d->u.device_qualifier.bcdUSB_hi = usb_hi(dev->bcdUSB); + d->u.device_qualifier.bDeviceClass = dev->bDeviceClass; + d->u.device_qualifier.bDeviceSubClass = dev->bDeviceSubClass; + d->u.device_qualifier.bDeviceProtocol = dev->bDeviceProtocol; + d->u.device_qualifier.bMaxPacketSize0 = dev->bMaxPacketSize0; + d->u.device_qualifier.bNumConfigurations = dev->bNumConfigurations; + d->u.device_qualifier.bReserved = 0; + + return bLength; +} + +int usb_desc_config(const USBDescConfig *conf, int flags, + uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x09; + uint16_t wTotalLength = 0; + USBDescriptor *d = (void *)dest; + int i, rc; + + if (len < bLength) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_CONFIG; + + d->u.config.bNumInterfaces = conf->bNumInterfaces; + d->u.config.bConfigurationValue = conf->bConfigurationValue; + d->u.config.iConfiguration = conf->iConfiguration; + d->u.config.bmAttributes = conf->bmAttributes; + d->u.config.bMaxPower = 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]), flags, + 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, flags, + dest + wTotalLength, len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + } + + d->u.config.wTotalLength_lo = usb_lo(wTotalLength); + d->u.config.wTotalLength_hi = usb_hi(wTotalLength); + return wTotalLength; +} + +int usb_desc_iface_group(const USBDescIfaceAssoc *iad, int flags, + 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]), flags, dest + pos, len - pos); + if (rc < 0) { + return rc; + } + pos += rc; + } + + return pos; +} + +int usb_desc_iface(const USBDescIface *iface, int flags, + uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x09; + int i, rc, pos = 0; + USBDescriptor *d = (void *)dest; + + if (len < bLength) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_INTERFACE; + + d->u.interface.bInterfaceNumber = iface->bInterfaceNumber; + d->u.interface.bAlternateSetting = iface->bAlternateSetting; + d->u.interface.bNumEndpoints = iface->bNumEndpoints; + d->u.interface.bInterfaceClass = iface->bInterfaceClass; + d->u.interface.bInterfaceSubClass = iface->bInterfaceSubClass; + d->u.interface.bInterfaceProtocol = iface->bInterfaceProtocol; + d->u.interface.iInterface = 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, flags, dest + pos, len - pos); + if (rc < 0) { + return rc; + } + pos += rc; + } + + return pos; +} + +int usb_desc_endpoint(const USBDescEndpoint *ep, int flags, + uint8_t *dest, size_t len) +{ + uint8_t bLength = ep->is_audio ? 0x09 : 0x07; + uint8_t extralen = ep->extra ? ep->extra[0] : 0; + uint8_t superlen = (flags & USB_DESC_FLAG_SUPER) ? 0x06 : 0; + USBDescriptor *d = (void *)dest; + + if (len < bLength + extralen + superlen) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_ENDPOINT; + + d->u.endpoint.bEndpointAddress = ep->bEndpointAddress; + d->u.endpoint.bmAttributes = ep->bmAttributes; + d->u.endpoint.wMaxPacketSize_lo = usb_lo(ep->wMaxPacketSize); + d->u.endpoint.wMaxPacketSize_hi = usb_hi(ep->wMaxPacketSize); + d->u.endpoint.bInterval = ep->bInterval; + if (ep->is_audio) { + d->u.endpoint.bRefresh = ep->bRefresh; + d->u.endpoint.bSynchAddress = ep->bSynchAddress; + } + + if (superlen) { + USBDescriptor *d = (void *)(dest + bLength); + + d->bLength = 0x06; + d->bDescriptorType = USB_DT_ENDPOINT_COMPANION; + + d->u.super_endpoint.bMaxBurst = ep->bMaxBurst; + d->u.super_endpoint.bmAttributes = ep->bmAttributes_super; + d->u.super_endpoint.wBytesPerInterval_lo = + usb_lo(ep->wBytesPerInterval); + d->u.super_endpoint.wBytesPerInterval_hi = + usb_hi(ep->wBytesPerInterval); + } + + if (ep->extra) { + memcpy(dest + bLength + superlen, ep->extra, extralen); + } + + return bLength + extralen + superlen; +} + +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 int usb_desc_cap_usb2_ext(const USBDesc *desc, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x07; + USBDescriptor *d = (void *)dest; + + if (len < bLength) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + d->u.cap.bDevCapabilityType = USB_DEV_CAP_USB2_EXT; + + d->u.cap.u.usb2_ext.bmAttributes_1 = (1 << 1); /* LPM */ + d->u.cap.u.usb2_ext.bmAttributes_2 = 0; + d->u.cap.u.usb2_ext.bmAttributes_3 = 0; + d->u.cap.u.usb2_ext.bmAttributes_4 = 0; + + return bLength; +} + +static int usb_desc_cap_super(const USBDesc *desc, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x0a; + USBDescriptor *d = (void *)dest; + + if (len < bLength) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + d->u.cap.bDevCapabilityType = USB_DEV_CAP_SUPERSPEED; + + d->u.cap.u.super.bmAttributes = 0; + d->u.cap.u.super.wSpeedsSupported_lo = 0; + d->u.cap.u.super.wSpeedsSupported_hi = 0; + d->u.cap.u.super.bFunctionalitySupport = 0; + d->u.cap.u.super.bU1DevExitLat = 0x0a; + d->u.cap.u.super.wU2DevExitLat_lo = 0x20; + d->u.cap.u.super.wU2DevExitLat_hi = 0; + + if (desc->full) { + d->u.cap.u.super.wSpeedsSupported_lo |= (1 << 1); + d->u.cap.u.super.bFunctionalitySupport = 1; + } + if (desc->high) { + d->u.cap.u.super.wSpeedsSupported_lo |= (1 << 2); + if (!d->u.cap.u.super.bFunctionalitySupport) { + d->u.cap.u.super.bFunctionalitySupport = 2; + } + } + if (desc->super) { + d->u.cap.u.super.wSpeedsSupported_lo |= (1 << 3); + if (!d->u.cap.u.super.bFunctionalitySupport) { + d->u.cap.u.super.bFunctionalitySupport = 3; + } + } + + return bLength; +} + +static int usb_desc_bos(const USBDesc *desc, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x05; + uint16_t wTotalLength = 0; + uint8_t bNumDeviceCaps = 0; + USBDescriptor *d = (void *)dest; + int rc; + + if (len < bLength) { + return -1; + } + + d->bLength = bLength; + d->bDescriptorType = USB_DT_BOS; + + wTotalLength += bLength; + + if (desc->high != NULL) { + rc = usb_desc_cap_usb2_ext(desc, dest + wTotalLength, + len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + bNumDeviceCaps++; + } + + if (desc->super != NULL) { + rc = usb_desc_cap_super(desc, dest + wTotalLength, + len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + bNumDeviceCaps++; + } + + d->u.bos.wTotalLength_lo = usb_lo(wTotalLength); + d->u.bos.wTotalLength_hi = usb_hi(wTotalLength); + d->u.bos.bNumDeviceCaps = bNumDeviceCaps; + return wTotalLength; +} + +/* ------------------------------------------------------------------ */ + +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); + usb_ep_set_max_streams(dev, pid, ep, + iface->eps[e].bmAttributes_super); + } + } +} + +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; + case USB_SPEED_SUPER: + dev->device = desc->super; + 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; + } + if (desc->super) { + dev->speedmask |= USB_SPEED_MASK_SUPER; + } + if (desc->msos && (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_ENABLE))) { + dev->flags |= (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE); + usb_desc_set_string(dev, 0xee, "MSFT100Q"); + } + usb_desc_setdefaults(dev); +} + +void usb_desc_attach(USBDevice *dev) +{ + 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); +} + +/* + * This function creates a serial number for a usb device. + * The serial number should: + * (a) Be unique within the virtual machine. + * (b) Be constant, so you don't get a new one each + * time the guest is started. + * So we are using the physical location to generate a serial number + * from it. It has three pieces: First a fixed, device-specific + * prefix. Second the device path of the host controller (which is + * the pci address in most cases). Third the physical port path. + * Results in serial numbers like this: "314159-0000:00:1d.7-3". + */ +void usb_desc_create_serial(USBDevice *dev) +{ + DeviceState *hcd = dev->qdev.parent_bus->parent; + const USBDesc *desc = usb_device_get_usb_desc(dev); + int index = desc->id.iSerialNumber; + char *path, *serial; + + if (dev->serial) { + /* 'serial' usb bus property has priority if present */ + usb_desc_set_string(dev, index, dev->serial); + return; + } + + assert(index != 0 && desc->str[index] != NULL); + path = qdev_get_dev_path(hcd); + if (path) { + serial = g_strdup_printf("%s-%s-%s", desc->str[index], + path, dev->port->path); + } else { + serial = g_strdup_printf("%s-%s", desc->str[index], dev->port->path); + } + usb_desc_set_string(dev, index, serial); + g_free(path); + g_free(serial); +} + +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, USBPacket *p, + int value, uint8_t *dest, size_t len) +{ + bool msos = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE)); + 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 flags, 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; + } + + flags = 0; + if (dev->device->bcdUSB >= 0x0300) { + flags |= USB_DESC_FLAG_SUPER; + } + + switch(type) { + case USB_DT_DEVICE: + ret = usb_desc_device(&desc->id, dev->device, msos, 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, flags, + 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, flags, + 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_BOS: + ret = usb_desc_bos(desc, buf, sizeof(buf)); + trace_usb_desc_bos(dev->addr, len, ret); + break; + + case USB_DT_DEBUG: + /* ignore silently */ + break; + + default: + fprintf(stderr, "%s: %d unknown type %d (len %zd)\n", __func__, + dev->addr, type, len); + break; + } + + if (ret > 0) { + if (ret > len) { + ret = len; + } + memcpy(dest, buf, ret); + p->actual_length = ret; + ret = 0; + } + return ret; +} + +int usb_desc_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + bool msos = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE)); + 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, p, 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; + p->actual_length = 1; + ret = 0; + 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 & USB_CFG_ATT_SELFPOWER) { + data[0] |= 1 << USB_DEVICE_SELF_POWERED; + } + if (dev->remote_wakeup) { + data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP; + } + data[1] = 0x00; + p->actual_length = 2; + ret = 0; + 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 DeviceOutRequest | USB_REQ_SET_SEL: + case DeviceOutRequest | USB_REQ_SET_ISOCH_DELAY: + if (dev->speed == USB_SPEED_SUPER) { + ret = 0; + } + break; + + case InterfaceRequest | USB_REQ_GET_INTERFACE: + if (index < 0 || index >= dev->ninterfaces) { + break; + } + data[0] = dev->altsetting[index]; + p->actual_length = 1; + ret = 0; + 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; + + case VendorDeviceRequest | 'Q': + if (msos) { + ret = usb_desc_msos(desc, p, index, data, length); + trace_usb_desc_msos(dev->addr, index, length, ret); + } + break; + case VendorInterfaceRequest | 'Q': + if (msos) { + ret = usb_desc_msos(desc, p, index, data, length); + trace_usb_desc_msos(dev->addr, index, length, ret); + } + break; + + } + return ret; +} diff --git a/hw/usb/desc.h b/hw/usb/desc.h new file mode 100644 index 000000000..3ac604ecf --- /dev/null +++ b/hw/usb/desc.h @@ -0,0 +1,244 @@ +#ifndef QEMU_HW_USB_DESC_H +#define QEMU_HW_USB_DESC_H + +#include <wchar.h> + +/* binary representation */ +typedef struct USBDescriptor { + uint8_t bLength; + uint8_t bDescriptorType; + union { + struct { + uint8_t bcdUSB_lo; + uint8_t bcdUSB_hi; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint8_t idVendor_lo; + uint8_t idVendor_hi; + uint8_t idProduct_lo; + uint8_t idProduct_hi; + uint8_t bcdDevice_lo; + uint8_t bcdDevice_hi; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + } device; + struct { + uint8_t bcdUSB_lo; + uint8_t bcdUSB_hi; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint8_t bNumConfigurations; + uint8_t bReserved; + } device_qualifier; + struct { + uint8_t wTotalLength_lo; + uint8_t wTotalLength_hi; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; + } config; + struct { + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + } interface; + struct { + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint8_t wMaxPacketSize_lo; + uint8_t wMaxPacketSize_hi; + uint8_t bInterval; + uint8_t bRefresh; /* only audio ep */ + uint8_t bSynchAddress; /* only audio ep */ + } endpoint; + struct { + uint8_t bMaxBurst; + uint8_t bmAttributes; + uint8_t wBytesPerInterval_lo; + uint8_t wBytesPerInterval_hi; + } super_endpoint; + struct { + uint8_t wTotalLength_lo; + uint8_t wTotalLength_hi; + uint8_t bNumDeviceCaps; + } bos; + struct { + uint8_t bDevCapabilityType; + union { + struct { + uint8_t bmAttributes_1; + uint8_t bmAttributes_2; + uint8_t bmAttributes_3; + uint8_t bmAttributes_4; + } usb2_ext; + struct { + uint8_t bmAttributes; + uint8_t wSpeedsSupported_lo; + uint8_t wSpeedsSupported_hi; + uint8_t bFunctionalitySupport; + uint8_t bU1DevExitLat; + uint8_t wU2DevExitLat_lo; + uint8_t wU2DevExitLat_hi; + } super; + } u; + } cap; + } u; +} QEMU_PACKED USBDescriptor; + +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 related 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; + + /* superspeed endpoint companion */ + uint8_t bMaxBurst; + uint8_t bmAttributes_super; + uint16_t wBytesPerInterval; +}; + +struct USBDescOther { + uint8_t length; + const uint8_t *data; +}; + +struct USBDescMSOS { + const char *CompatibleID; + const wchar_t *Label; + bool SelectiveSuspendEnabled; +}; + +typedef const char *USBDescStrings[256]; + +struct USBDesc { + USBDescID id; + const USBDescDevice *full; + const USBDescDevice *high; + const USBDescDevice *super; + const char* const *str; + const USBDescMSOS *msos; +}; + +#define USB_DESC_FLAG_SUPER (1 << 1) + +/* little helpers */ +static inline uint8_t usb_lo(uint16_t val) +{ + return val & 0xff; +} + +static inline uint8_t usb_hi(uint16_t val) +{ + return (val >> 8) & 0xff; +} + +/* generate usb packages from structs */ +int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, + bool msos, 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, int flags, + uint8_t *dest, size_t len); +int usb_desc_iface_group(const USBDescIfaceAssoc *iad, int flags, + uint8_t *dest, size_t len); +int usb_desc_iface(const USBDescIface *iface, int flags, + uint8_t *dest, size_t len); +int usb_desc_endpoint(const USBDescEndpoint *ep, int flags, + uint8_t *dest, size_t len); +int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len); +int usb_desc_msos(const USBDesc *desc, USBPacket *p, + int index, 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); +void usb_desc_create_serial(USBDevice *dev); +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, USBPacket *p, + 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 000000000..8748c1ba0 --- /dev/null +++ b/hw/usb/dev-audio.c @@ -0,0 +1,1029 @@ +/* + * 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/osdep.h" +#include "qemu/module.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "audio/audio.h" +#include "qom/object.h" + +static void usb_audio_reinit(USBDevice *dev, unsigned channels); + +#define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ +#define USBAUDIO_PRODUCT_NUM 0x0002 + +#define DEV_CONFIG_VALUE 1 /* The one and only */ + +#define USBAUDIO_MAX_CHANNELS(s) (s->multi ? 8 : 2) + +/* 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", +}; + +/* + * 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_STEREO = 0x01, /* Single endpoint */ + ALTSET_51 = 0x02, + ALTSET_71 = 0x03, +}; + +static unsigned altset_channels[] = { + [ALTSET_STEREO] = 2, + [ALTSET_51] = 6, + [ALTSET_71] = 8, +}; + +#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_BASE 96 +#define USBAUDIO_PACKET_SIZE(channels) (USBAUDIO_PACKET_SIZE_BASE * channels) +#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, /* u8 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 Output 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 = ALTSET_OFF, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, + .iInterface = STRING_NULL_STREAM, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = ALTSET_STEREO, + .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(2), + .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 = 0x0100, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 2, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = STRING_CONFIG, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .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, +}; + +/* multi channel compatible desc */ + +static const USBDescIface desc_iface_multi[] = { + { + .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(0x38), /* 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 */ + 0x08, /* u8 bNrChannels */ + U16(0x063f), /* u16 wChannelConfig */ + 0x00, /* u8 iChannelNames */ + STRING_INPUT_TERMINAL, /* u8 iTerminal */ + } + },{ + /* Generic Stereo Feature Unit ID2 Descriptor */ + .data = (uint8_t[]) { + 0x19, /* 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) */ + U16(0x0002), /* u16 bmaControls(3) */ + U16(0x0002), /* u16 bmaControls(4) */ + U16(0x0002), /* u16 bmaControls(5) */ + U16(0x0002), /* u16 bmaControls(6) */ + U16(0x0002), /* u16 bmaControls(7) */ + U16(0x0002), /* u16 bmaControls(8) */ + STRING_FEATURE_UNIT, /* u8 iFeature */ + } + },{ + /* Headphone Output 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 = ALTSET_OFF, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, + .iInterface = STRING_NULL_STREAM, + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = ALTSET_STEREO, + .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(2), + .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 */ + }, + }, + } + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = ALTSET_51, + .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 */ + 0x06, /* 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(6), + .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 */ + }, + }, + } + },{ + .bInterfaceNumber = 1, + .bAlternateSetting = ALTSET_71, + .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 */ + 0x08, /* 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(8), + .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_multi = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 2, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = STRING_CONFIG, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface_multi), + .ifs = desc_iface_multi, + } + }, +}; + +static const USBDesc desc_audio_multi = { + .id = { + .idVendor = USBAUDIO_VENDOR_NUM, + .idProduct = USBAUDIO_PRODUCT_NUM, + .bcdDevice = 0, + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIALNUMBER, + }, + .full = &desc_device_multi, + .str = usb_audio_stringtable, +}; + +/* + * 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; + size_t size; + uint64_t prod; + uint64_t cons; +}; + +static void streambuf_init(struct streambuf *buf, uint32_t size, + uint32_t channels) +{ + g_free(buf->data); + buf->size = size - (size % USBAUDIO_PACKET_SIZE(channels)); + 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 channels) +{ + int64_t free = buf->size - (buf->prod - buf->cons); + + if (free < USBAUDIO_PACKET_SIZE(channels)) { + return 0; + } + if (p->iov.size != USBAUDIO_PACKET_SIZE(channels)) { + return 0; + } + + /* can happen if prod overflows */ + assert(buf->prod % USBAUDIO_PACKET_SIZE(channels) == 0); + usb_packet_copy(p, buf->data + (buf->prod % buf->size), + USBAUDIO_PACKET_SIZE(channels)); + buf->prod += USBAUDIO_PACKET_SIZE(channels); + return USBAUDIO_PACKET_SIZE(channels); +} + +static uint8_t *streambuf_get(struct streambuf *buf, size_t *len) +{ + int64_t used = buf->prod - buf->cons; + uint8_t *data; + + if (used <= 0) { + *len = 0; + return NULL; + } + data = buf->data + (buf->cons % buf->size); + *len = MIN(buf->prod - buf->cons, + buf->size - (buf->cons % buf->size)); + return data; +} + +struct USBAudioState { + /* qemu interfaces */ + USBDevice dev; + QEMUSoundCard card; + + /* state */ + struct { + enum usb_audio_altset altset; + struct audsettings as; + SWVoiceOut *voice; + Volume vol; + struct streambuf buf; + uint32_t channels; + } out; + + /* properties */ + uint32_t debug; + uint32_t buffer_user, buffer; + bool multi; +}; + +#define TYPE_USB_AUDIO "usb-audio" +OBJECT_DECLARE_SIMPLE_TYPE(USBAudioState, USB_AUDIO) + +static void output_callback(void *opaque, int avail) +{ + USBAudioState *s = opaque; + uint8_t *data; + + while (avail) { + size_t written, len; + + data = streambuf_get(&s->out.buf, &len); + if (!data) { + return; + } + + written = AUD_write(s->out.voice, data, len); + avail -= written; + s->out.buf.cons += written; + + if (written < len) { + return; + } + } +} + +static int usb_audio_set_output_altset(USBAudioState *s, int altset) +{ + switch (altset) { + case ALTSET_OFF: + AUD_set_active_out(s->out.voice, false); + break; + case ALTSET_STEREO: + case ALTSET_51: + case ALTSET_71: + if (s->out.channels != altset_channels[altset]) { + usb_audio_reinit(USB_DEVICE(s), altset_channels[altset]); + } + streambuf_init(&s->out.buf, s->buffer, s->out.channels); + 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.vol.mute; + ret = 1; + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200): + if (cn < USBAUDIO_MAX_CHANNELS(s)) { + uint16_t vol = (s->out.vol.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 < USBAUDIO_MAX_CHANNELS(s)) { + data[0] = 0x01; + data[1] = 0x80; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): + if (cn < USBAUDIO_MAX_CHANNELS(s)) { + data[0] = 0x00; + data[1] = 0x08; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): + if (cn < USBAUDIO_MAX_CHANNELS(s)) { + 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.vol.mute = data[0] & 1; + set_vol = true; + ret = 0; + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200): + if (cn < USBAUDIO_MAX_CHANNELS(s)) { + uint16_t vol = data[0] + (data[1] << 8); + + if (s->debug) { + fprintf(stderr, "usb-audio: cn %d vol %04x\n", cn, + (uint16_t)vol); + } + + vol -= 0x8000; + vol = (vol * 255 + 0x4400) / 0x8800; + if (vol > 255) { + vol = 255; + } + + s->out.vol.vol[cn] = vol; + set_vol = true; + ret = 0; + } + break; + } + + if (set_vol) { + if (s->debug) { + int i; + fprintf(stderr, "usb-audio: mute %d", s->out.vol.mute); + for (i = 0; i < USBAUDIO_MAX_CHANNELS(s); ++i) { + fprintf(stderr, ", vol[%d] %3d", i, s->out.vol.vol[i]); + } + fprintf(stderr, "\n"); + } + audio_set_volume_out(s->out.voice, &s->out.vol); + } + + return ret; +} + +static void usb_audio_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + USBAudioState *s = USB_AUDIO(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; + } + + 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; + } + p->actual_length = ret; + 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); + } + p->status = USB_RET_STALL; + break; + } +} + +static void usb_audio_set_interface(USBDevice *dev, int iface, + int old, int value) +{ + USBAudioState *s = USB_AUDIO(dev); + + if (iface == 1) { + usb_audio_set_output_altset(s, value); + } +} + +static void usb_audio_handle_reset(USBDevice *dev) +{ + USBAudioState *s = USB_AUDIO(dev); + + if (s->debug) { + fprintf(stderr, "usb-audio: reset\n"); + } + usb_audio_set_output_altset(s, ALTSET_OFF); +} + +static void usb_audio_handle_dataout(USBAudioState *s, USBPacket *p) +{ + if (s->out.altset == ALTSET_OFF) { + p->status = USB_RET_STALL; + return; + } + + streambuf_put(&s->out.buf, p, s->out.channels); + if (p->actual_length < p->iov.size && s->debug > 1) { + fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n", + p->iov.size - p->actual_length); + } +} + +static void usb_audio_handle_data(USBDevice *dev, USBPacket *p) +{ + USBAudioState *s = (USBAudioState *) dev; + + if (p->pid == USB_TOKEN_OUT && p->ep->nr == 1) { + usb_audio_handle_dataout(s, p); + return; + } + + p->status = USB_RET_STALL; + if (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); + } +} + +static void usb_audio_unrealize(USBDevice *dev) +{ + USBAudioState *s = USB_AUDIO(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 void usb_audio_realize(USBDevice *dev, Error **errp) +{ + USBAudioState *s = USB_AUDIO(dev); + int i; + + dev->usb_desc = s->multi ? &desc_audio_multi : &desc_audio; + + usb_desc_create_serial(dev); + usb_desc_init(dev); + s->dev.opaque = s; + AUD_register_card(TYPE_USB_AUDIO, &s->card); + + s->out.altset = ALTSET_OFF; + s->out.vol.mute = false; + for (i = 0; i < USBAUDIO_MAX_CHANNELS(s); ++i) { + s->out.vol.vol[i] = 240; /* 0 dB */ + } + + usb_audio_reinit(dev, 2); +} + +static void usb_audio_reinit(USBDevice *dev, unsigned channels) +{ + USBAudioState *s = USB_AUDIO(dev); + + s->out.channels = channels; + if (!s->buffer_user) { + s->buffer = 32 * USBAUDIO_PACKET_SIZE(s->out.channels); + } else { + s->buffer = s->buffer_user; + } + + s->out.vol.channels = s->out.channels; + s->out.as.freq = USBAUDIO_SAMPLE_RATE; + s->out.as.nchannels = s->out.channels; + s->out.as.fmt = AUDIO_FORMAT_S16; + s->out.as.endianness = 0; + streambuf_init(&s->out.buf, s->buffer, s->out.channels); + + s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_AUDIO, + s, output_callback, &s->out.as); + audio_set_volume_out(s->out.voice, &s->out.vol); + AUD_set_active_out(s->out.voice, 0); +} + +static const VMStateDescription vmstate_usb_audio = { + .name = TYPE_USB_AUDIO, + .unmigratable = 1, +}; + +static Property usb_audio_properties[] = { + DEFINE_AUDIO_PROPERTIES(USBAudioState, card), + DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0), + DEFINE_PROP_UINT32("buffer", USBAudioState, buffer_user, 0), + DEFINE_PROP_BOOL("multi", USBAudioState, multi, false), + 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; + device_class_set_props(dc, usb_audio_properties); + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + k->product_desc = "QEMU USB Audio Interface"; + k->realize = usb_audio_realize; + k->handle_reset = usb_audio_handle_reset; + k->handle_control = usb_audio_handle_control; + k->handle_data = usb_audio_handle_data; + k->unrealize = usb_audio_unrealize; + k->set_interface = usb_audio_set_interface; +} + +static const TypeInfo usb_audio_info = { + .name = TYPE_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); +} + +type_init(usb_audio_register_types) diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c new file mode 100644 index 000000000..1c7ae97c3 --- /dev/null +++ b/hw/usb/dev-hid.c @@ -0,0 +1,879 @@ +/* + * 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 "qemu/osdep.h" +#include "ui/console.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "hw/input/hid.h" +#include "hw/usb/hid.h" +#include "hw/qdev-properties.h" +#include "qom/object.h" + +struct USBHIDState { + USBDevice dev; + USBEndpoint *intr; + HIDState hid; + uint32_t usb_version; + char *display; + uint32_t head; +}; + +#define TYPE_USB_HID "usb-hid" +OBJECT_DECLARE_SIMPLE_TYPE(USBHIDState, USB_HID) + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT_MOUSE, + STR_PRODUCT_TABLET, + STR_PRODUCT_KEYBOARD, + STR_SERIAL_COMPAT, + STR_CONFIG_MOUSE, + STR_CONFIG_TABLET, + STR_CONFIG_KEYBOARD, + STR_SERIAL_MOUSE, + STR_SERIAL_TABLET, + STR_SERIAL_KEYBOARD, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [STR_PRODUCT_MOUSE] = "QEMU USB Mouse", + [STR_PRODUCT_TABLET] = "QEMU USB Tablet", + [STR_PRODUCT_KEYBOARD] = "QEMU USB Keyboard", + [STR_SERIAL_COMPAT] = "42", + [STR_CONFIG_MOUSE] = "HID Mouse", + [STR_CONFIG_TABLET] = "HID Tablet", + [STR_CONFIG_KEYBOARD] = "HID Keyboard", + [STR_SERIAL_MOUSE] = "89126", + [STR_SERIAL_TABLET] = "28754", + [STR_SERIAL_KEYBOARD] = "68284", +}; + +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_mouse2 = { + .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 = 7, /* 2 ^ (8-1) * 125 usecs = 8 ms */ + }, + }, +}; + +static const USBDescIface desc_iface_tablet = { + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceProtocol = 0x00, + .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_tablet2 = { + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceProtocol = 0x00, + .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 = 4, /* 2 ^ (4-1) * 125 usecs = 1 ms */ + }, + }, +}; + +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 USBDescIface desc_iface_keyboard2 = { + .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 = 7, /* 2 ^ (8-1) * 125 usecs = 8 ms */ + }, + }, +}; + +static const USBDescDevice desc_device_mouse = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_MOUSE, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_mouse, + }, + }, +}; + +static const USBDescDevice desc_device_mouse2 = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_MOUSE, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_mouse2, + }, + }, +}; + +static const USBDescDevice desc_device_tablet = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_TABLET, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_tablet, + }, + }, +}; + +static const USBDescDevice desc_device_tablet2 = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_TABLET, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_tablet2, + }, + }, +}; + +static const USBDescDevice desc_device_keyboard = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_KEYBOARD, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_keyboard, + }, + }, +}; + +static const USBDescDevice desc_device_keyboard2 = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_KEYBOARD, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_keyboard2, + }, + }, +}; + +static const USBDescMSOS desc_msos_suspend = { + .SelectiveSuspendEnabled = true, +}; + +static const USBDesc desc_mouse = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_MOUSE, + .iSerialNumber = STR_SERIAL_MOUSE, + }, + .full = &desc_device_mouse, + .str = desc_strings, + .msos = &desc_msos_suspend, +}; + +static const USBDesc desc_mouse2 = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_MOUSE, + .iSerialNumber = STR_SERIAL_MOUSE, + }, + .full = &desc_device_mouse, + .high = &desc_device_mouse2, + .str = desc_strings, + .msos = &desc_msos_suspend, +}; + +static const USBDesc desc_tablet = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_TABLET, + .iSerialNumber = STR_SERIAL_TABLET, + }, + .full = &desc_device_tablet, + .str = desc_strings, + .msos = &desc_msos_suspend, +}; + +static const USBDesc desc_tablet2 = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_TABLET, + .iSerialNumber = STR_SERIAL_TABLET, + }, + .full = &desc_device_tablet, + .high = &desc_device_tablet2, + .str = desc_strings, + .msos = &desc_msos_suspend, +}; + +static const USBDesc desc_keyboard = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_KEYBOARD, + .iSerialNumber = STR_SERIAL_KEYBOARD, + }, + .full = &desc_device_keyboard, + .str = desc_strings, + .msos = &desc_msos_suspend, +}; + +static const USBDesc desc_keyboard2 = { + .id = { + .idVendor = 0x0627, + .idProduct = 0x0001, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_KEYBOARD, + .iSerialNumber = STR_SERIAL_KEYBOARD, + }, + .full = &desc_device_keyboard, + .high = &desc_device_keyboard2, + .str = desc_strings, + .msos = &desc_msos_suspend, +}; + +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, 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) */ + 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, 0); +} + +static void usb_hid_handle_reset(USBDevice *dev) +{ + USBHIDState *us = USB_HID(dev); + + hid_reset(&us->hid); +} + +static void usb_hid_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBHIDState *us = USB_HID(dev); + HIDState *hs = &us->hid; + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + 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)); + p->actual_length = 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)); + p->actual_length = 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)); + p->actual_length = sizeof(qemu_keyboard_hid_report_descriptor); + } + break; + default: + goto fail; + } + break; + case HID_GET_REPORT: + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + p->actual_length = hid_pointer_poll(hs, data, length); + } else if (hs->kind == HID_KEYBOARD) { + p->actual_length = hid_keyboard_poll(hs, data, length); + } + break; + case HID_SET_REPORT: + if (hs->kind == HID_KEYBOARD) { + p->actual_length = hid_keyboard_write(hs, data, length); + } else { + goto fail; + } + break; + case HID_GET_PROTOCOL: + if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { + goto fail; + } + data[0] = hs->protocol; + p->actual_length = 1; + break; + case HID_SET_PROTOCOL: + if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { + goto fail; + } + hs->protocol = value; + break; + case HID_GET_IDLE: + data[0] = hs->idle; + p->actual_length = 1; + break; + case HID_SET_IDLE: + hs->idle = (uint8_t) (value >> 8); + hid_set_next_idle(hs); + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + hid_pointer_activate(hs); + } + break; + default: + fail: + p->status = USB_RET_STALL; + break; + } +} + +static void usb_hid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHIDState *us = USB_HID(dev); + HIDState *hs = &us->hid; + g_autofree uint8_t *buf = g_malloc(p->iov.size); + int len = 0; + + switch (p->pid) { + case USB_TOKEN_IN: + if (p->ep->nr == 1) { + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + hid_pointer_activate(hs); + } + if (!hid_has_events(hs)) { + p->status = USB_RET_NAK; + return; + } + hid_set_next_idle(hs); + if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { + len = hid_pointer_poll(hs, buf, p->iov.size); + } else if (hs->kind == HID_KEYBOARD) { + len = hid_keyboard_poll(hs, buf, p->iov.size); + } + usb_packet_copy(p, buf, len); + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + p->status = USB_RET_STALL; + break; + } +} + +static void usb_hid_unrealize(USBDevice *dev) +{ + USBHIDState *us = USB_HID(dev); + + hid_free(&us->hid); +} + +static void usb_hid_initfn(USBDevice *dev, int kind, + const USBDesc *usb1, const USBDesc *usb2, + Error **errp) +{ + USBHIDState *us = USB_HID(dev); + switch (us->usb_version) { + case 1: + dev->usb_desc = usb1; + break; + case 2: + dev->usb_desc = usb2; + break; + default: + dev->usb_desc = NULL; + } + if (!dev->usb_desc) { + error_setg(errp, "Invalid usb version %d for usb hid device", + us->usb_version); + return; + } + + usb_desc_create_serial(dev); + usb_desc_init(dev); + us->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); + hid_init(&us->hid, kind, usb_hid_changed); + if (us->display && us->hid.s) { + qemu_input_handler_bind(us->hid.s, us->display, us->head, NULL); + } +} + +static void usb_tablet_realize(USBDevice *dev, Error **errp) +{ + + usb_hid_initfn(dev, HID_TABLET, &desc_tablet, &desc_tablet2, errp); +} + +static void usb_mouse_realize(USBDevice *dev, Error **errp) +{ + usb_hid_initfn(dev, HID_MOUSE, &desc_mouse, &desc_mouse2, errp); +} + +static void usb_keyboard_realize(USBDevice *dev, Error **errp) +{ + usb_hid_initfn(dev, HID_KEYBOARD, &desc_keyboard, &desc_keyboard2, errp); +} + +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->unrealize = usb_hid_unrealize; + uc->handle_attach = usb_desc_attach; +} + +static const TypeInfo usb_hid_type_info = { + .name = TYPE_USB_HID, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHIDState), + .abstract = true, + .class_init = usb_hid_class_initfn, +}; + +static Property usb_tablet_properties[] = { + DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2), + DEFINE_PROP_STRING("display", USBHIDState, display), + DEFINE_PROP_UINT32("head", USBHIDState, head, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_tablet_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_tablet_realize; + uc->product_desc = "QEMU USB Tablet"; + dc->vmsd = &vmstate_usb_ptr; + device_class_set_props(dc, usb_tablet_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo usb_tablet_info = { + .name = "usb-tablet", + .parent = TYPE_USB_HID, + .class_init = usb_tablet_class_initfn, +}; + +static Property usb_mouse_properties[] = { + DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_mouse_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_mouse_realize; + uc->product_desc = "QEMU USB Mouse"; + dc->vmsd = &vmstate_usb_ptr; + device_class_set_props(dc, usb_mouse_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo usb_mouse_info = { + .name = "usb-mouse", + .parent = TYPE_USB_HID, + .class_init = usb_mouse_class_initfn, +}; + +static Property usb_keyboard_properties[] = { + DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2), + DEFINE_PROP_STRING("display", USBHIDState, display), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_keyboard_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_keyboard_realize; + uc->product_desc = "QEMU USB Keyboard"; + dc->vmsd = &vmstate_usb_kbd; + device_class_set_props(dc, usb_keyboard_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo usb_keyboard_info = { + .name = "usb-kbd", + .parent = TYPE_USB_HID, + .class_init = usb_keyboard_class_initfn, +}; + +static void usb_hid_register_types(void) +{ + type_register_static(&usb_hid_type_info); + 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 000000000..e35813d77 --- /dev/null +++ b/hw/usb/dev-hub.c @@ -0,0 +1,704 @@ +/* + * 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/osdep.h" +#include "qapi/error.h" +#include "qemu/timer.h" +#include "trace.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "qom/object.h" + +#define MAX_PORTS 8 + +typedef struct USBHubPort { + USBPort port; + uint16_t wPortStatus; + uint16_t wPortChange; +} USBHubPort; + +struct USBHubState { + USBDevice dev; + USBEndpoint *intr; + uint32_t num_ports; + bool port_power; + QEMUTimer *port_timer; + USBHubPort ports[MAX_PORTS]; +}; + +#define TYPE_USB_HUB "usb-hub" +OBJECT_DECLARE_SIMPLE_TYPE(USBHubState, USB_HUB) + +#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", + [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 + DIV_ROUND_UP(MAX_PORTS, 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 = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER | + USB_CFG_ATT_WAKEUP, + .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 bool usb_hub_port_change(USBHubPort *port, uint16_t status) +{ + bool notify = false; + + if (status & 0x1f) { + port->wPortChange |= status; + notify = true; + } + return notify; +} + +static bool usb_hub_port_set(USBHubPort *port, uint16_t status) +{ + if (port->wPortStatus & status) { + return false; + } + port->wPortStatus |= status; + return usb_hub_port_change(port, status); +} + +static bool usb_hub_port_clear(USBHubPort *port, uint16_t status) +{ + if (!(port->wPortStatus & status)) { + return false; + } + port->wPortStatus &= ~status; + return usb_hub_port_change(port, status); +} + +static bool usb_hub_port_update(USBHubPort *port) +{ + bool notify = false; + + if (port->port.dev && port->port.dev->attached) { + notify = usb_hub_port_set(port, PORT_STAT_CONNECTION); + if (port->port.dev->speed == USB_SPEED_LOW) { + usb_hub_port_set(port, PORT_STAT_LOW_SPEED); + } else { + usb_hub_port_clear(port, PORT_STAT_LOW_SPEED); + } + } + return notify; +} + +static void usb_hub_port_update_timer(void *opaque) +{ + USBHubState *s = opaque; + bool notify = false; + int i; + + for (i = 0; i < s->num_ports; i++) { + notify |= usb_hub_port_update(&s->ports[i]); + } + if (notify) { + usb_wakeup(s->intr, 0); + } +} + +static void usb_hub_attach(USBPort *port1) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + trace_usb_hub_attach(s->dev.addr, port1->index + 1); + usb_hub_port_update(port); + usb_wakeup(s->intr, 0); +} + +static void usb_hub_detach(USBPort *port1) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + trace_usb_hub_detach(s->dev.addr, port1->index + 1); + usb_wakeup(s->intr, 0); + + /* Let upstream know the device on this port is gone */ + s->dev.port->ops->child_detach(s->dev.port, port1->dev); + + usb_hub_port_clear(port, PORT_STAT_CONNECTION); + usb_hub_port_clear(port, PORT_STAT_ENABLE); + usb_hub_port_clear(port, PORT_STAT_SUSPEND); + usb_wakeup(s->intr, 0); +} + +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 (usb_hub_port_clear(port, PORT_STAT_SUSPEND)) { + usb_wakeup(s->intr, 0); + } +} + +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 = USB_HUB(dev); + USBHubPort *port; + USBDevice *downstream; + int i; + + for (i = 0; i < s->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 = USB_HUB(dev); + USBHubPort *port; + int i; + + trace_usb_hub_reset(s->dev.addr); + for (i = 0; i < s->num_ports; i++) { + port = s->ports + i; + port->wPortStatus = 0; + port->wPortChange = 0; + usb_hub_port_set(port, PORT_STAT_POWER); + usb_hub_port_update(port); + } +} + +static const char *feature_name(int feature) +{ + static const char *name[] = { + [PORT_CONNECTION] = "connection", + [PORT_ENABLE] = "enable", + [PORT_SUSPEND] = "suspend", + [PORT_OVERCURRENT] = "overcurrent", + [PORT_RESET] = "reset", + [PORT_POWER] = "power", + [PORT_LOWSPEED] = "lowspeed", + [PORT_HIGHSPEED] = "highspeed", + [PORT_C_CONNECTION] = "change-connection", + [PORT_C_ENABLE] = "change-enable", + [PORT_C_SUSPEND] = "change-suspend", + [PORT_C_OVERCURRENT] = "change-overcurrent", + [PORT_C_RESET] = "change-reset", + [PORT_TEST] = "test", + [PORT_INDICATOR] = "indicator", + }; + if (feature < 0 || feature >= ARRAY_SIZE(name)) { + return "?"; + } + return name[feature] ?: "?"; +} + +static void 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; + + trace_usb_hub_control(s->dev.addr, request, value, index, length); + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch(request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0 && index != 0x81) { /* clear ep halt */ + goto fail; + } + break; + /* usb specific requests */ + case GetHubStatus: + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0; + p->actual_length = 4; + break; + case GetPortStatus: + { + unsigned int n = index - 1; + USBHubPort *port; + if (n >= s->num_ports) { + goto fail; + } + port = &s->ports[n]; + trace_usb_hub_get_port_status(s->dev.addr, index, + port->wPortStatus, + port->wPortChange); + data[0] = port->wPortStatus; + data[1] = port->wPortStatus >> 8; + data[2] = port->wPortChange; + data[3] = port->wPortChange >> 8; + p->actual_length = 4; + } + break; + case SetHubFeature: + case ClearHubFeature: + if (value != 0 && value != 1) { + goto fail; + } + break; + case SetPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + USBDevice *dev; + + trace_usb_hub_set_port_feature(s->dev.addr, index, + feature_name(value)); + + if (n >= s->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: + usb_hub_port_set(port, PORT_STAT_RESET); + usb_hub_port_clear(port, PORT_STAT_RESET); + if (dev && dev->attached) { + usb_device_reset(dev); + usb_hub_port_set(port, PORT_STAT_ENABLE); + } + usb_wakeup(s->intr, 0); + break; + case PORT_POWER: + if (s->port_power) { + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + usb_hub_port_set(port, PORT_STAT_POWER); + timer_mod(s->port_timer, now + 5000000); /* 5 ms */ + } + break; + default: + goto fail; + } + } + break; + case ClearPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + + trace_usb_hub_clear_port_feature(s->dev.addr, index, + feature_name(value)); + + if (n >= s->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: + usb_hub_port_clear(port, 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; + case PORT_POWER: + if (s->port_power) { + usb_hub_port_clear(port, PORT_STAT_POWER); + usb_hub_port_clear(port, PORT_STAT_CONNECTION); + usb_hub_port_clear(port, PORT_STAT_ENABLE); + usb_hub_port_clear(port, PORT_STAT_SUSPEND); + port->wPortChange = 0; + } + default: + goto fail; + } + } + break; + case GetHubDescriptor: + { + unsigned int n, limit, var_hub_size = 0; + memcpy(data, qemu_hub_hub_descriptor, + sizeof(qemu_hub_hub_descriptor)); + data[2] = s->num_ports; + + if (s->port_power) { + data[3] &= ~0x03; + data[3] |= 0x01; + } + + /* fill DeviceRemovable bits */ + limit = DIV_ROUND_UP(s->num_ports + 1, 8) + 7; + for (n = 7; n < limit; n++) { + data[n] = 0x00; + var_hub_size++; + } + + /* fill PortPwrCtrlMask bits */ + limit = limit + DIV_ROUND_UP(s->num_ports, 8); + for (;n < limit; n++) { + data[n] = 0xff; + var_hub_size++; + } + + p->actual_length = sizeof(qemu_hub_hub_descriptor) + var_hub_size; + data[0] = p->actual_length; + break; + } + default: + fail: + p->status = USB_RET_STALL; + break; + } +} + +static void usb_hub_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHubState *s = (USBHubState *)dev; + + 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 = DIV_ROUND_UP(s->num_ports + 1, 8); + if (p->iov.size == 1) { /* FreeBSD workaround */ + n = 1; + } else if (n > p->iov.size) { + p->status = USB_RET_BABBLE; + return; + } + status = 0; + for (i = 0; i < s->num_ports; i++) { + port = &s->ports[i]; + if (port->wPortChange) + status |= (1 << (i + 1)); + } + if (status != 0) { + trace_usb_hub_status_report(s->dev.addr, status); + for(i = 0; i < n; i++) { + buf[i] = status >> (8 * i); + } + usb_packet_copy(p, buf, n); + } else { + p->status = USB_RET_NAK; /* usb11 11.13.1 */ + } + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + p->status = USB_RET_STALL; + break; + } +} + +static void usb_hub_unrealize(USBDevice *dev) +{ + USBHubState *s = (USBHubState *)dev; + int i; + + for (i = 0; i < s->num_ports; i++) { + usb_unregister_port(usb_bus_from_device(dev), + &s->ports[i].port); + } + + timer_free(s->port_timer); +} + +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 void usb_hub_realize(USBDevice *dev, Error **errp) +{ + USBHubState *s = USB_HUB(dev); + USBHubPort *port; + int i; + + if (s->num_ports < 1 || s->num_ports > MAX_PORTS) { + error_setg(errp, "num_ports (%d) out of range (1..%d)", + s->num_ports, MAX_PORTS); + return; + } + + if (dev->port->hubcount == 5) { + error_setg(errp, "usb hub chain too deep"); + return; + } + + usb_desc_create_serial(dev); + usb_desc_init(dev); + s->port_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + usb_hub_port_update_timer, s); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); + for (i = 0; i < s->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); +} + +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 bool usb_hub_port_timer_needed(void *opaque) +{ + USBHubState *s = opaque; + + return s->port_power; +} + +static const VMStateDescription vmstate_usb_hub_port_timer = { + .name = "usb-hub/port-timer", + .version_id = 1, + .minimum_version_id = 1, + .needed = usb_hub_port_timer_needed, + .fields = (VMStateField[]) { + VMSTATE_TIMER_PTR(port_timer, USBHubState), + 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, MAX_PORTS, 0, + vmstate_usb_hub_port, USBHubPort), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription * []) { + &vmstate_usb_hub_port_timer, + NULL + } +}; + +static Property usb_hub_properties[] = { + DEFINE_PROP_UINT32("ports", USBHubState, num_ports, 8), + DEFINE_PROP_BOOL("port-power", USBHubState, port_power, false), + DEFINE_PROP_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->realize = usb_hub_realize; + 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->unrealize = usb_hub_unrealize; + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); + dc->fw_name = "hub"; + dc->vmsd = &vmstate_usb_hub; + device_class_set_props(dc, usb_hub_properties); +} + +static const TypeInfo hub_info = { + .name = TYPE_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-mtp.c b/hw/usb/dev-mtp.c new file mode 100644 index 000000000..c1d1694fd --- /dev/null +++ b/hw/usb/dev-mtp.c @@ -0,0 +1,2121 @@ +/* + * Media Transfer Protocol implementation, backed by host filesystem. + * + * Copyright Red Hat, Inc 2014 + * + * Author: + * Gerd Hoffmann <kraxel@redhat.com> + * + * This code is licensed under the GPL v2 or later. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include <wchar.h> +#include <dirent.h> + +#include <sys/statvfs.h> + + +#include "qemu/iov.h" +#include "qemu/module.h" +#include "qemu/filemonitor.h" +#include "trace.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "qemu/units.h" +#include "qom/object.h" + +/* ----------------------------------------------------------------------- */ + +enum mtp_container_type { + TYPE_COMMAND = 1, + TYPE_DATA = 2, + TYPE_RESPONSE = 3, + TYPE_EVENT = 4, +}; + +/* MTP write stage, for internal use only */ +enum mtp_write_status { + WRITE_START = 1, + WRITE_CONTINUE = 2, + WRITE_END = 3, +}; + +enum mtp_code { + /* command codes */ + CMD_GET_DEVICE_INFO = 0x1001, + CMD_OPEN_SESSION = 0x1002, + CMD_CLOSE_SESSION = 0x1003, + CMD_GET_STORAGE_IDS = 0x1004, + CMD_GET_STORAGE_INFO = 0x1005, + CMD_GET_NUM_OBJECTS = 0x1006, + CMD_GET_OBJECT_HANDLES = 0x1007, + CMD_GET_OBJECT_INFO = 0x1008, + CMD_GET_OBJECT = 0x1009, + CMD_DELETE_OBJECT = 0x100b, + CMD_SEND_OBJECT_INFO = 0x100c, + CMD_SEND_OBJECT = 0x100d, + CMD_GET_PARTIAL_OBJECT = 0x101b, + CMD_GET_OBJECT_PROPS_SUPPORTED = 0x9801, + CMD_GET_OBJECT_PROP_DESC = 0x9802, + CMD_GET_OBJECT_PROP_VALUE = 0x9803, + + /* response codes */ + RES_OK = 0x2001, + RES_GENERAL_ERROR = 0x2002, + RES_SESSION_NOT_OPEN = 0x2003, + RES_INVALID_TRANSACTION_ID = 0x2004, + RES_OPERATION_NOT_SUPPORTED = 0x2005, + RES_PARAMETER_NOT_SUPPORTED = 0x2006, + RES_INCOMPLETE_TRANSFER = 0x2007, + RES_INVALID_STORAGE_ID = 0x2008, + RES_INVALID_OBJECT_HANDLE = 0x2009, + RES_INVALID_OBJECT_FORMAT_CODE = 0x200b, + RES_STORE_FULL = 0x200c, + RES_STORE_READ_ONLY = 0x200e, + RES_PARTIAL_DELETE = 0x2012, + RES_STORE_NOT_AVAILABLE = 0x2013, + RES_SPEC_BY_FORMAT_UNSUPPORTED = 0x2014, + RES_INVALID_OBJECTINFO = 0x2015, + RES_DESTINATION_UNSUPPORTED = 0x2020, + RES_INVALID_PARENT_OBJECT = 0x201a, + RES_INVALID_PARAMETER = 0x201d, + RES_SESSION_ALREADY_OPEN = 0x201e, + RES_INVALID_OBJECT_PROP_CODE = 0xA801, + + /* format codes */ + FMT_UNDEFINED_OBJECT = 0x3000, + FMT_ASSOCIATION = 0x3001, + + /* event codes */ + EVT_CANCEL_TRANSACTION = 0x4001, + EVT_OBJ_ADDED = 0x4002, + EVT_OBJ_REMOVED = 0x4003, + EVT_OBJ_INFO_CHANGED = 0x4007, + + /* object properties */ + PROP_STORAGE_ID = 0xDC01, + PROP_OBJECT_FORMAT = 0xDC02, + PROP_OBJECT_COMPRESSED_SIZE = 0xDC04, + PROP_PARENT_OBJECT = 0xDC0B, + PROP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER = 0xDC41, + PROP_NAME = 0xDC44, +}; + +enum mtp_data_type { + DATA_TYPE_UINT16 = 0x0004, + DATA_TYPE_UINT32 = 0x0006, + DATA_TYPE_UINT64 = 0x0008, + DATA_TYPE_UINT128 = 0x000a, + DATA_TYPE_STRING = 0xffff, +}; + +typedef struct { + uint32_t length; + uint16_t type; + uint16_t code; + uint32_t trans; +} QEMU_PACKED mtp_container; + +/* ----------------------------------------------------------------------- */ + +typedef struct MTPState MTPState; +typedef struct MTPControl MTPControl; +typedef struct MTPData MTPData; +typedef struct MTPObject MTPObject; + +enum { + EP_DATA_IN = 1, + EP_DATA_OUT, + EP_EVENT, +}; + +typedef struct MTPMonEntry MTPMonEntry; + +struct MTPMonEntry { + uint32_t event; + uint32_t handle; + + QTAILQ_ENTRY(MTPMonEntry) next; +}; + +struct MTPControl { + uint16_t code; + uint32_t trans; + int argc; + uint32_t argv[5]; +}; + +struct MTPData { + uint16_t code; + uint32_t trans; + uint64_t offset; + uint64_t length; + uint64_t alloc; + uint8_t *data; + bool first; + /* Used for >4G file sizes */ + bool pending; + int fd; + uint8_t write_status; + /* Internal pointer per every MTP_WRITE_BUF_SZ */ + uint64_t data_offset; +}; + +struct MTPObject { + uint32_t handle; + uint16_t format; + char *name; + char *path; + struct stat stat; + /* file monitor watch id */ + int64_t watchid; + MTPObject *parent; + uint32_t nchildren; + QLIST_HEAD(, MTPObject) children; + QLIST_ENTRY(MTPObject) list; + bool have_children; + QTAILQ_ENTRY(MTPObject) next; +}; + +struct MTPState { + USBDevice dev; + char *root; + char *desc; + uint32_t flags; + + MTPData *data_in; + MTPData *data_out; + MTPControl *result; + uint32_t session; + uint32_t next_handle; + bool readonly; + + QTAILQ_HEAD(, MTPObject) objects; + QFileMonitor *file_monitor; + QTAILQ_HEAD(, MTPMonEntry) events; + /* Responder is expecting a write operation */ + bool write_pending; + struct { + uint32_t parent_handle; + uint16_t format; + uint32_t size; + char *filename; + } dataset; +}; + +/* + * ObjectInfo dataset received from initiator + * Fields we don't care about are ignored + */ +typedef struct { + uint32_t storage_id; /*unused*/ + uint16_t format; + uint16_t protection_status; /*unused*/ + uint32_t size; + uint16_t thumb_format; /*unused*/ + uint32_t thumb_comp_sz; /*unused*/ + uint32_t thumb_pix_width; /*unused*/ + uint32_t thumb_pix_height; /*unused*/ + uint32_t image_pix_width; /*unused*/ + uint32_t image_pix_height; /*unused*/ + uint32_t image_bit_depth; /*unused*/ + uint32_t parent; /*unused*/ + uint16_t assoc_type; + uint32_t assoc_desc; + uint32_t seq_no; /*unused*/ + uint8_t length; /*part of filename field*/ + uint8_t filename[0]; /* UTF-16 encoded */ + char date_created[0]; /*unused*/ + char date_modified[0]; /*unused*/ + char keywords[0]; /*unused*/ + /* string and other data follows */ +} QEMU_PACKED ObjectInfo; + +#define TYPE_USB_MTP "usb-mtp" +OBJECT_DECLARE_SIMPLE_TYPE(MTPState, USB_MTP) + +#define QEMU_STORAGE_ID 0x00010001 + +#define MTP_FLAG_WRITABLE 0 + +#define FLAG_SET(_mtp, _flag) ((_mtp)->flags & (1 << (_flag))) + +/* ----------------------------------------------------------------------- */ + +#define MTP_MANUFACTURER "QEMU" +#define MTP_PRODUCT "QEMU filesharing" +#define MTP_WRITE_BUF_SZ (512 * KiB) + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_MTP, + STR_CONFIG_FULL, + STR_CONFIG_HIGH, + STR_CONFIG_SUPER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = MTP_MANUFACTURER, + [STR_PRODUCT] = MTP_PRODUCT, + [STR_SERIALNUMBER] = "34617", + [STR_MTP] = "MTP", + [STR_CONFIG_FULL] = "Full speed config (usb 1.1)", + [STR_CONFIG_HIGH] = "High speed config (usb 2.0)", + [STR_CONFIG_SUPER] = "Super speed config (usb 3.0)", +}; + +static const USBDescIface desc_iface_full = { + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x01, + .iInterface = STR_MTP, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | EP_DATA_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + },{ + .bEndpointAddress = USB_DIR_OUT | EP_DATA_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + },{ + .bEndpointAddress = USB_DIR_IN | EP_EVENT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 64, + .bInterval = 0x0a, + }, + } +}; + +static const USBDescDevice desc_device_full = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_FULL, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 2, + .nif = 1, + .ifs = &desc_iface_full, + }, + }, +}; + +static const USBDescIface desc_iface_high = { + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x01, + .iInterface = STR_MTP, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | EP_DATA_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + },{ + .bEndpointAddress = USB_DIR_OUT | EP_DATA_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + },{ + .bEndpointAddress = USB_DIR_IN | EP_EVENT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = 64, + .bInterval = 0x0a, + }, + } +}; + +static const USBDescDevice desc_device_high = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_HIGH, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 2, + .nif = 1, + .ifs = &desc_iface_high, + }, + }, +}; + +static const USBDescMSOS desc_msos = { + .CompatibleID = "MTP", + .SelectiveSuspendEnabled = true, +}; + +static const USBDesc desc = { + .id = { + .idVendor = 0x46f4, /* CRC16() of "QEMU" */ + .idProduct = 0x0004, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_full, + .high = &desc_device_high, + .str = desc_strings, + .msos = &desc_msos, +}; + +/* ----------------------------------------------------------------------- */ + +static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle, + MTPObject *parent, const char *name) +{ + MTPObject *o = g_new0(MTPObject, 1); + + if (name[0] == '.') { + goto ignore; + } + + o->watchid = -1; + o->handle = handle; + o->parent = parent; + o->name = g_strdup(name); + if (parent == NULL) { + o->path = g_strdup(name); + } else { + o->path = g_strdup_printf("%s/%s", parent->path, name); + } + + if (lstat(o->path, &o->stat) != 0) { + goto ignore; + } + if (S_ISREG(o->stat.st_mode)) { + o->format = FMT_UNDEFINED_OBJECT; + } else if (S_ISDIR(o->stat.st_mode)) { + o->format = FMT_ASSOCIATION; + } else { + goto ignore; + } + + if (access(o->path, R_OK) != 0) { + goto ignore; + } + + trace_usb_mtp_object_alloc(s->dev.addr, o->handle, o->path); + + QTAILQ_INSERT_TAIL(&s->objects, o, next); + return o; + +ignore: + g_free(o->name); + g_free(o->path); + g_free(o); + return NULL; +} + +static void usb_mtp_object_free(MTPState *s, MTPObject *o) +{ + MTPObject *iter; + + if (!o) { + return; + } + + trace_usb_mtp_object_free(s->dev.addr, o->handle, o->path); + + if (o->watchid != -1 && s->file_monitor) { + qemu_file_monitor_remove_watch(s->file_monitor, o->path, o->watchid); + } + + QTAILQ_REMOVE(&s->objects, o, next); + if (o->parent) { + QLIST_REMOVE(o, list); + o->parent->nchildren--; + } + + while (!QLIST_EMPTY(&o->children)) { + iter = QLIST_FIRST(&o->children); + usb_mtp_object_free(s, iter); + } + g_free(o->name); + g_free(o->path); + g_free(o); +} + +static MTPObject *usb_mtp_object_lookup(MTPState *s, uint32_t handle) +{ + MTPObject *o; + + QTAILQ_FOREACH(o, &s->objects, next) { + if (o->handle == handle) { + return o; + } + } + return NULL; +} + +static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o, + const char *name) +{ + MTPObject *child = + usb_mtp_object_alloc(s, s->next_handle++, o, name); + + if (child) { + trace_usb_mtp_add_child(s->dev.addr, child->handle, child->path); + QLIST_INSERT_HEAD(&o->children, child, list); + o->nchildren++; + + if (child->format == FMT_ASSOCIATION) { + QLIST_INIT(&child->children); + } + } + + return child; +} + +static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent, + const char *name, int len) +{ + MTPObject *iter; + + if (len == -1) { + len = strlen(name); + } + + QLIST_FOREACH(iter, &parent->children, list) { + if (strncmp(iter->name, name, len) == 0) { + return iter; + } + } + + return NULL; +} + +static MTPObject *usb_mtp_object_lookup_id(MTPState *s, int64_t id) +{ + MTPObject *iter; + + QTAILQ_FOREACH(iter, &s->objects, next) { + if (iter->watchid == id) { + return iter; + } + } + + return NULL; +} + +static void file_monitor_event(int64_t id, + QFileMonitorEvent ev, + const char *name, + void *opaque) +{ + MTPState *s = opaque; + MTPObject *parent = usb_mtp_object_lookup_id(s, id); + MTPMonEntry *entry = NULL; + MTPObject *o; + + if (!parent) { + return; + } + + switch (ev) { + case QFILE_MONITOR_EVENT_CREATED: + if (usb_mtp_object_lookup_name(parent, name, -1)) { + /* Duplicate create event */ + return; + } + entry = g_new0(MTPMonEntry, 1); + entry->handle = s->next_handle; + entry->event = EVT_OBJ_ADDED; + o = usb_mtp_add_child(s, parent, name); + if (!o) { + g_free(entry); + return; + } + trace_usb_mtp_file_monitor_event(s->dev.addr, name, "Obj Added"); + break; + + case QFILE_MONITOR_EVENT_DELETED: + /* + * The kernel issues a IN_IGNORED event + * when a dir containing a watchpoint is + * deleted, so we don't have to delete the + * watchpoint + */ + o = usb_mtp_object_lookup_name(parent, name, -1); + if (!o) { + return; + } + entry = g_new0(MTPMonEntry, 1); + entry->handle = o->handle; + entry->event = EVT_OBJ_REMOVED; + trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Deleted"); + usb_mtp_object_free(s, o); + break; + + case QFILE_MONITOR_EVENT_MODIFIED: + o = usb_mtp_object_lookup_name(parent, name, -1); + if (!o) { + return; + } + entry = g_new0(MTPMonEntry, 1); + entry->handle = o->handle; + entry->event = EVT_OBJ_INFO_CHANGED; + trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Modified"); + break; + + case QFILE_MONITOR_EVENT_IGNORED: + trace_usb_mtp_file_monitor_event(s->dev.addr, parent->path, + "Obj parent dir ignored"); + break; + + case QFILE_MONITOR_EVENT_ATTRIBUTES: + break; + + default: + g_assert_not_reached(); + } + + if (entry) { + QTAILQ_INSERT_HEAD(&s->events, entry, next); + } +} + +static void usb_mtp_file_monitor_cleanup(MTPState *s) +{ + MTPMonEntry *e, *p; + + QTAILQ_FOREACH_SAFE(e, &s->events, next, p) { + QTAILQ_REMOVE(&s->events, e, next); + g_free(e); + } + + qemu_file_monitor_free(s->file_monitor); + s->file_monitor = NULL; +} + + +static void usb_mtp_object_readdir(MTPState *s, MTPObject *o) +{ + struct dirent *entry; + DIR *dir; + int fd; + Error *err = NULL; + + if (o->have_children) { + return; + } + o->have_children = true; + + fd = open(o->path, O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); + if (fd < 0) { + return; + } + dir = fdopendir(fd); + if (!dir) { + close(fd); + return; + } + + if (s->file_monitor) { + int64_t id = qemu_file_monitor_add_watch(s->file_monitor, o->path, NULL, + file_monitor_event, s, &err); + if (id == -1) { + error_reportf_err(err, + "usb-mtp: failed to add watch for %s: ", + o->path); + } else { + trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, + "Watch Added"); + o->watchid = id; + } + } + + while ((entry = readdir(dir)) != NULL) { + usb_mtp_add_child(s, o, entry->d_name); + } + closedir(dir); +} + +/* ----------------------------------------------------------------------- */ + +static MTPData *usb_mtp_data_alloc(MTPControl *c) +{ + MTPData *data = g_new0(MTPData, 1); + + data->code = c->code; + data->trans = c->trans; + data->fd = -1; + data->first = true; + return data; +} + +static void usb_mtp_data_free(MTPData *data) +{ + if (data == NULL) { + return; + } + if (data->fd != -1) { + close(data->fd); + } + g_free(data->data); + g_free(data); +} + +static void usb_mtp_realloc(MTPData *data, uint32_t bytes) +{ + if (data->length + bytes <= data->alloc) { + return; + } + data->alloc = (data->length + bytes + 0xff) & ~0xff; + data->data = g_realloc(data->data, data->alloc); +} + +static void usb_mtp_add_u8(MTPData *data, uint8_t val) +{ + usb_mtp_realloc(data, 1); + data->data[data->length++] = val; +} + +static void usb_mtp_add_u16(MTPData *data, uint16_t val) +{ + usb_mtp_realloc(data, 2); + data->data[data->length++] = (val >> 0) & 0xff; + data->data[data->length++] = (val >> 8) & 0xff; +} + +static void usb_mtp_add_u32(MTPData *data, uint32_t val) +{ + usb_mtp_realloc(data, 4); + data->data[data->length++] = (val >> 0) & 0xff; + data->data[data->length++] = (val >> 8) & 0xff; + data->data[data->length++] = (val >> 16) & 0xff; + data->data[data->length++] = (val >> 24) & 0xff; +} + +static void usb_mtp_add_u64(MTPData *data, uint64_t val) +{ + usb_mtp_realloc(data, 8); + data->data[data->length++] = (val >> 0) & 0xff; + data->data[data->length++] = (val >> 8) & 0xff; + data->data[data->length++] = (val >> 16) & 0xff; + data->data[data->length++] = (val >> 24) & 0xff; + data->data[data->length++] = (val >> 32) & 0xff; + data->data[data->length++] = (val >> 40) & 0xff; + data->data[data->length++] = (val >> 48) & 0xff; + data->data[data->length++] = (val >> 56) & 0xff; +} + +static void usb_mtp_add_u16_array(MTPData *data, uint32_t len, + const uint16_t *vals) +{ + int i; + + usb_mtp_add_u32(data, len); + for (i = 0; i < len; i++) { + usb_mtp_add_u16(data, vals[i]); + } +} + +static void usb_mtp_add_u32_array(MTPData *data, uint32_t len, + const uint32_t *vals) +{ + int i; + + usb_mtp_add_u32(data, len); + for (i = 0; i < len; i++) { + usb_mtp_add_u32(data, vals[i]); + } +} + +static void usb_mtp_add_wstr(MTPData *data, const wchar_t *str) +{ + uint32_t len = wcslen(str); + int i; + + if (len > 0) { + len++; /* include terminating L'\0' */ + } + + usb_mtp_add_u8(data, len); + for (i = 0; i < len; i++) { + usb_mtp_add_u16(data, str[i]); + } +} + +static void usb_mtp_add_str(MTPData *data, const char *str) +{ + uint32_t len = strlen(str)+1; + wchar_t *wstr = g_new(wchar_t, len); + size_t ret; + + ret = mbstowcs(wstr, str, len); + if (ret == -1) { + usb_mtp_add_wstr(data, L"Oops"); + } else { + usb_mtp_add_wstr(data, wstr); + } + + g_free(wstr); +} + +static void usb_mtp_add_time(MTPData *data, time_t time) +{ + g_autoptr(GDateTime) then = g_date_time_new_from_unix_utc(time); + g_autofree char *thenstr = g_date_time_format(then, "%Y%m%dT%H%M%S"); + usb_mtp_add_str(data, thenstr); +} + +/* ----------------------------------------------------------------------- */ + +static void usb_mtp_queue_result(MTPState *s, uint16_t code, uint32_t trans, + int argc, uint32_t arg0, uint32_t arg1, + uint32_t arg2) +{ + MTPControl *c = g_new0(MTPControl, 1); + + c->code = code; + c->trans = trans; + c->argc = argc; + if (argc > 0) { + c->argv[0] = arg0; + } + if (argc > 1) { + c->argv[1] = arg1; + } + if (argc > 2) { + c->argv[2] = arg2; + } + + assert(s->result == NULL); + s->result = c; +} + +/* ----------------------------------------------------------------------- */ + +static MTPData *usb_mtp_get_device_info(MTPState *s, MTPControl *c) +{ + static const uint16_t ops[] = { + CMD_GET_DEVICE_INFO, + CMD_OPEN_SESSION, + CMD_CLOSE_SESSION, + CMD_GET_STORAGE_IDS, + CMD_GET_STORAGE_INFO, + CMD_GET_NUM_OBJECTS, + CMD_GET_OBJECT_HANDLES, + CMD_GET_OBJECT_INFO, + CMD_DELETE_OBJECT, + CMD_SEND_OBJECT_INFO, + CMD_SEND_OBJECT, + CMD_GET_OBJECT, + CMD_GET_PARTIAL_OBJECT, + CMD_GET_OBJECT_PROPS_SUPPORTED, + CMD_GET_OBJECT_PROP_DESC, + CMD_GET_OBJECT_PROP_VALUE, + }; + static const uint16_t fmt[] = { + FMT_UNDEFINED_OBJECT, + FMT_ASSOCIATION, + }; + MTPData *d = usb_mtp_data_alloc(c); + + trace_usb_mtp_op_get_device_info(s->dev.addr); + + usb_mtp_add_u16(d, 100); + usb_mtp_add_u32(d, 0x00000006); + usb_mtp_add_u16(d, 0x0064); + usb_mtp_add_wstr(d, L""); + usb_mtp_add_u16(d, 0x0000); + + usb_mtp_add_u16_array(d, ARRAY_SIZE(ops), ops); + usb_mtp_add_u16_array(d, 0, NULL); + usb_mtp_add_u16_array(d, 0, NULL); + usb_mtp_add_u16_array(d, 0, NULL); + usb_mtp_add_u16_array(d, ARRAY_SIZE(fmt), fmt); + + usb_mtp_add_wstr(d, L"" MTP_MANUFACTURER); + usb_mtp_add_wstr(d, L"" MTP_PRODUCT); + usb_mtp_add_wstr(d, L"0.1"); + usb_mtp_add_wstr(d, L"0123456789abcdef0123456789abcdef"); + + return d; +} + +static MTPData *usb_mtp_get_storage_ids(MTPState *s, MTPControl *c) +{ + static const uint32_t ids[] = { + QEMU_STORAGE_ID, + }; + MTPData *d = usb_mtp_data_alloc(c); + + trace_usb_mtp_op_get_storage_ids(s->dev.addr); + + usb_mtp_add_u32_array(d, ARRAY_SIZE(ids), ids); + + return d; +} + +static MTPData *usb_mtp_get_storage_info(MTPState *s, MTPControl *c) +{ + MTPData *d = usb_mtp_data_alloc(c); + struct statvfs buf; + int rc; + + trace_usb_mtp_op_get_storage_info(s->dev.addr); + + if (FLAG_SET(s, MTP_FLAG_WRITABLE)) { + usb_mtp_add_u16(d, 0x0003); + usb_mtp_add_u16(d, 0x0002); + usb_mtp_add_u16(d, 0x0000); + } else { + usb_mtp_add_u16(d, 0x0001); + usb_mtp_add_u16(d, 0x0002); + usb_mtp_add_u16(d, 0x0001); + } + + rc = statvfs(s->root, &buf); + if (rc == 0) { + usb_mtp_add_u64(d, (uint64_t)buf.f_frsize * buf.f_blocks); + usb_mtp_add_u64(d, (uint64_t)buf.f_bavail * buf.f_blocks); + usb_mtp_add_u32(d, buf.f_ffree); + } else { + usb_mtp_add_u64(d, 0xffffffff); + usb_mtp_add_u64(d, 0xffffffff); + usb_mtp_add_u32(d, 0xffffffff); + } + + usb_mtp_add_str(d, s->desc); + usb_mtp_add_wstr(d, L"123456789abcdef"); + return d; +} + +static MTPData *usb_mtp_get_object_handles(MTPState *s, MTPControl *c, + MTPObject *o) +{ + MTPData *d = usb_mtp_data_alloc(c); + uint32_t i = 0; + g_autofree uint32_t *handles = g_new(uint32_t, o->nchildren); + MTPObject *iter; + + trace_usb_mtp_op_get_object_handles(s->dev.addr, o->handle, o->path); + + QLIST_FOREACH(iter, &o->children, list) { + handles[i++] = iter->handle; + } + assert(i == o->nchildren); + usb_mtp_add_u32_array(d, o->nchildren, handles); + + return d; +} + +static MTPData *usb_mtp_get_object_info(MTPState *s, MTPControl *c, + MTPObject *o) +{ + MTPData *d = usb_mtp_data_alloc(c); + + trace_usb_mtp_op_get_object_info(s->dev.addr, o->handle, o->path); + + usb_mtp_add_u32(d, QEMU_STORAGE_ID); + usb_mtp_add_u16(d, o->format); + usb_mtp_add_u16(d, 0); + + if (o->stat.st_size > 0xFFFFFFFF) { + usb_mtp_add_u32(d, 0xFFFFFFFF); + } else { + usb_mtp_add_u32(d, o->stat.st_size); + } + + usb_mtp_add_u16(d, 0); + usb_mtp_add_u32(d, 0); + usb_mtp_add_u32(d, 0); + usb_mtp_add_u32(d, 0); + usb_mtp_add_u32(d, 0); + usb_mtp_add_u32(d, 0); + usb_mtp_add_u32(d, 0); + + if (o->parent) { + usb_mtp_add_u32(d, o->parent->handle); + } else { + usb_mtp_add_u32(d, 0); + } + if (o->format == FMT_ASSOCIATION) { + usb_mtp_add_u16(d, 0x0001); + usb_mtp_add_u32(d, 0x00000001); + usb_mtp_add_u32(d, 0); + } else { + usb_mtp_add_u16(d, 0); + usb_mtp_add_u32(d, 0); + usb_mtp_add_u32(d, 0); + } + + usb_mtp_add_str(d, o->name); + usb_mtp_add_time(d, o->stat.st_ctime); + usb_mtp_add_time(d, o->stat.st_mtime); + usb_mtp_add_wstr(d, L""); + + return d; +} + +static MTPData *usb_mtp_get_object(MTPState *s, MTPControl *c, + MTPObject *o) +{ + MTPData *d = usb_mtp_data_alloc(c); + + trace_usb_mtp_op_get_object(s->dev.addr, o->handle, o->path); + + d->fd = open(o->path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + if (d->fd == -1) { + usb_mtp_data_free(d); + return NULL; + } + d->length = o->stat.st_size; + d->alloc = 512; + d->data = g_malloc(d->alloc); + return d; +} + +static MTPData *usb_mtp_get_partial_object(MTPState *s, MTPControl *c, + MTPObject *o) +{ + MTPData *d; + off_t offset; + + if (c->argc <= 2) { + return NULL; + } + trace_usb_mtp_op_get_partial_object(s->dev.addr, o->handle, o->path, + c->argv[1], c->argv[2]); + + d = usb_mtp_data_alloc(c); + d->fd = open(o->path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + if (d->fd == -1) { + usb_mtp_data_free(d); + return NULL; + } + + offset = c->argv[1]; + if (offset > o->stat.st_size) { + offset = o->stat.st_size; + } + if (lseek(d->fd, offset, SEEK_SET) < 0) { + usb_mtp_data_free(d); + return NULL; + } + + d->length = c->argv[2]; + if (d->length > o->stat.st_size - offset) { + d->length = o->stat.st_size - offset; + } + + return d; +} + +static MTPData *usb_mtp_get_object_props_supported(MTPState *s, MTPControl *c) +{ + static const uint16_t props[] = { + PROP_STORAGE_ID, + PROP_OBJECT_FORMAT, + PROP_OBJECT_COMPRESSED_SIZE, + PROP_PARENT_OBJECT, + PROP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER, + PROP_NAME, + }; + MTPData *d = usb_mtp_data_alloc(c); + usb_mtp_add_u16_array(d, ARRAY_SIZE(props), props); + + return d; +} + +static MTPData *usb_mtp_get_object_prop_desc(MTPState *s, MTPControl *c) +{ + MTPData *d = usb_mtp_data_alloc(c); + switch (c->argv[0]) { + case PROP_STORAGE_ID: + usb_mtp_add_u16(d, PROP_STORAGE_ID); + usb_mtp_add_u16(d, DATA_TYPE_UINT32); + usb_mtp_add_u8(d, 0x00); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u8(d, 0x00); + break; + case PROP_OBJECT_FORMAT: + usb_mtp_add_u16(d, PROP_OBJECT_FORMAT); + usb_mtp_add_u16(d, DATA_TYPE_UINT16); + usb_mtp_add_u8(d, 0x00); + usb_mtp_add_u16(d, 0x0000); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u8(d, 0x00); + break; + case PROP_OBJECT_COMPRESSED_SIZE: + usb_mtp_add_u16(d, PROP_OBJECT_COMPRESSED_SIZE); + usb_mtp_add_u16(d, DATA_TYPE_UINT64); + usb_mtp_add_u8(d, 0x00); + usb_mtp_add_u64(d, 0x0000000000000000); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u8(d, 0x00); + break; + case PROP_PARENT_OBJECT: + usb_mtp_add_u16(d, PROP_PARENT_OBJECT); + usb_mtp_add_u16(d, DATA_TYPE_UINT32); + usb_mtp_add_u8(d, 0x00); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u8(d, 0x00); + break; + case PROP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER: + usb_mtp_add_u16(d, PROP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER); + usb_mtp_add_u16(d, DATA_TYPE_UINT128); + usb_mtp_add_u8(d, 0x00); + usb_mtp_add_u64(d, 0x0000000000000000); + usb_mtp_add_u64(d, 0x0000000000000000); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u8(d, 0x00); + break; + case PROP_NAME: + usb_mtp_add_u16(d, PROP_NAME); + usb_mtp_add_u16(d, DATA_TYPE_STRING); + usb_mtp_add_u8(d, 0x00); + usb_mtp_add_u8(d, 0x00); + usb_mtp_add_u32(d, 0x00000000); + usb_mtp_add_u8(d, 0x00); + break; + default: + usb_mtp_data_free(d); + return NULL; + } + + return d; +} + +static MTPData *usb_mtp_get_object_prop_value(MTPState *s, MTPControl *c, + MTPObject *o) +{ + MTPData *d = usb_mtp_data_alloc(c); + switch (c->argv[1]) { + case PROP_STORAGE_ID: + usb_mtp_add_u32(d, QEMU_STORAGE_ID); + break; + case PROP_OBJECT_FORMAT: + usb_mtp_add_u16(d, o->format); + break; + case PROP_OBJECT_COMPRESSED_SIZE: + usb_mtp_add_u64(d, o->stat.st_size); + break; + case PROP_PARENT_OBJECT: + if (o->parent == NULL) { + usb_mtp_add_u32(d, 0x00000000); + } else { + usb_mtp_add_u32(d, o->parent->handle); + } + break; + case PROP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER: + /* Should be persistent between sessions, + * but using our objedt ID is "good enough" + * for now */ + usb_mtp_add_u64(d, 0x0000000000000000); + usb_mtp_add_u64(d, o->handle); + break; + case PROP_NAME: + usb_mtp_add_str(d, o->name); + break; + default: + usb_mtp_data_free(d); + return NULL; + } + + return d; +} + +/* + * Return values when object @o is deleted. + * If at least one of the deletions succeeded, + * DELETE_SUCCESS is set and if at least one + * of the deletions failed, DELETE_FAILURE is + * set. Both bits being set (DELETE_PARTIAL) + * signifies a RES_PARTIAL_DELETE being sent + * back to the initiator. + */ +enum { + DELETE_SUCCESS = (1 << 0), + DELETE_FAILURE = (1 << 1), + DELETE_PARTIAL = (DELETE_FAILURE | DELETE_SUCCESS), +}; + +static int usb_mtp_deletefn(MTPState *s, MTPObject *o, uint32_t trans) +{ + MTPObject *iter, *iter2; + int ret = 0; + + /* + * TODO: Add support for Protection Status + */ + + QLIST_FOREACH(iter, &o->children, list) { + if (iter->format == FMT_ASSOCIATION) { + QLIST_FOREACH(iter2, &iter->children, list) { + ret |= usb_mtp_deletefn(s, iter2, trans); + } + } + } + + if (o->format == FMT_UNDEFINED_OBJECT) { + if (remove(o->path)) { + ret |= DELETE_FAILURE; + } else { + usb_mtp_object_free(s, o); + ret |= DELETE_SUCCESS; + } + } else if (o->format == FMT_ASSOCIATION) { + if (rmdir(o->path)) { + ret |= DELETE_FAILURE; + } else { + usb_mtp_object_free(s, o); + ret |= DELETE_SUCCESS; + } + } + + return ret; +} + +static void usb_mtp_object_delete(MTPState *s, uint32_t handle, + uint32_t format_code, uint32_t trans) +{ + MTPObject *o; + int ret; + + /* Return error if store is read-only */ + if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) { + usb_mtp_queue_result(s, RES_STORE_READ_ONLY, + trans, 0, 0, 0, 0); + return; + } + + if (format_code != 0) { + usb_mtp_queue_result(s, RES_SPEC_BY_FORMAT_UNSUPPORTED, + trans, 0, 0, 0, 0); + return; + } + + if (handle == 0xFFFFFFF) { + o = QTAILQ_FIRST(&s->objects); + } else { + o = usb_mtp_object_lookup(s, handle); + } + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + trans, 0, 0, 0, 0); + return; + } + + ret = usb_mtp_deletefn(s, o, trans); + switch (ret) { + case DELETE_SUCCESS: + usb_mtp_queue_result(s, RES_OK, trans, + 0, 0, 0, 0); + break; + case DELETE_FAILURE: + usb_mtp_queue_result(s, RES_PARTIAL_DELETE, + trans, 0, 0, 0, 0); + break; + case DELETE_PARTIAL: + usb_mtp_queue_result(s, RES_PARTIAL_DELETE, + trans, 0, 0, 0, 0); + break; + default: + g_assert_not_reached(); + } + + return; +} + +static void usb_mtp_command(MTPState *s, MTPControl *c) +{ + MTPData *data_in = NULL; + MTPObject *o = NULL; + uint32_t nres = 0, res0 = 0; + Error *err = NULL; + + /* sanity checks */ + if (c->code >= CMD_CLOSE_SESSION && s->session == 0) { + usb_mtp_queue_result(s, RES_SESSION_NOT_OPEN, + c->trans, 0, 0, 0, 0); + return; + } + + /* process commands */ + switch (c->code) { + case CMD_GET_DEVICE_INFO: + data_in = usb_mtp_get_device_info(s, c); + break; + case CMD_OPEN_SESSION: + if (s->session) { + usb_mtp_queue_result(s, RES_SESSION_ALREADY_OPEN, + c->trans, 1, s->session, 0, 0); + return; + } + if (c->argv[0] == 0) { + usb_mtp_queue_result(s, RES_INVALID_PARAMETER, + c->trans, 0, 0, 0, 0); + return; + } + trace_usb_mtp_op_open_session(s->dev.addr); + s->session = c->argv[0]; + usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root); + + s->file_monitor = qemu_file_monitor_new(&err); + if (err) { + error_reportf_err(err, + "usb-mtp: file monitoring init failed: "); + } else { + QTAILQ_INIT(&s->events); + } + break; + case CMD_CLOSE_SESSION: + trace_usb_mtp_op_close_session(s->dev.addr); + s->session = 0; + s->next_handle = 0; + usb_mtp_file_monitor_cleanup(s); + usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects)); + assert(QTAILQ_EMPTY(&s->objects)); + break; + case CMD_GET_STORAGE_IDS: + data_in = usb_mtp_get_storage_ids(s, c); + break; + case CMD_GET_STORAGE_INFO: + if (c->argv[0] != QEMU_STORAGE_ID && + c->argv[0] != 0xffffffff) { + usb_mtp_queue_result(s, RES_INVALID_STORAGE_ID, + c->trans, 0, 0, 0, 0); + return; + } + data_in = usb_mtp_get_storage_info(s, c); + break; + case CMD_GET_NUM_OBJECTS: + case CMD_GET_OBJECT_HANDLES: + if (c->argv[0] != QEMU_STORAGE_ID && + c->argv[0] != 0xffffffff) { + usb_mtp_queue_result(s, RES_INVALID_STORAGE_ID, + c->trans, 0, 0, 0, 0); + return; + } + if (c->argv[1] != 0x00000000) { + usb_mtp_queue_result(s, RES_SPEC_BY_FORMAT_UNSUPPORTED, + c->trans, 0, 0, 0, 0); + return; + } + if (c->argv[2] == 0x00000000 || + c->argv[2] == 0xffffffff) { + o = QTAILQ_FIRST(&s->objects); + } else { + o = usb_mtp_object_lookup(s, c->argv[2]); + } + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + c->trans, 0, 0, 0, 0); + return; + } + if (o->format != FMT_ASSOCIATION) { + usb_mtp_queue_result(s, RES_INVALID_PARENT_OBJECT, + c->trans, 0, 0, 0, 0); + return; + } + usb_mtp_object_readdir(s, o); + if (c->code == CMD_GET_NUM_OBJECTS) { + trace_usb_mtp_op_get_num_objects(s->dev.addr, o->handle, o->path); + nres = 1; + res0 = o->nchildren; + } else { + data_in = usb_mtp_get_object_handles(s, c, o); + } + break; + case CMD_GET_OBJECT_INFO: + o = usb_mtp_object_lookup(s, c->argv[0]); + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + c->trans, 0, 0, 0, 0); + return; + } + data_in = usb_mtp_get_object_info(s, c, o); + break; + case CMD_GET_OBJECT: + o = usb_mtp_object_lookup(s, c->argv[0]); + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + c->trans, 0, 0, 0, 0); + return; + } + if (o->format == FMT_ASSOCIATION) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + c->trans, 0, 0, 0, 0); + return; + } + data_in = usb_mtp_get_object(s, c, o); + if (data_in == NULL) { + usb_mtp_queue_result(s, RES_GENERAL_ERROR, + c->trans, 0, 0, 0, 0); + return; + } + break; + case CMD_DELETE_OBJECT: + usb_mtp_object_delete(s, c->argv[0], c->argv[1], c->trans); + return; + case CMD_GET_PARTIAL_OBJECT: + o = usb_mtp_object_lookup(s, c->argv[0]); + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + c->trans, 0, 0, 0, 0); + return; + } + if (o->format == FMT_ASSOCIATION) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + c->trans, 0, 0, 0, 0); + return; + } + data_in = usb_mtp_get_partial_object(s, c, o); + if (data_in == NULL) { + usb_mtp_queue_result(s, RES_GENERAL_ERROR, + c->trans, 0, 0, 0, 0); + return; + } + nres = 1; + res0 = data_in->length; + break; + case CMD_SEND_OBJECT_INFO: + /* Return error if store is read-only */ + if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) { + usb_mtp_queue_result(s, RES_STORE_READ_ONLY, + c->trans, 0, 0, 0, 0); + } else if (c->argv[0] && (c->argv[0] != QEMU_STORAGE_ID)) { + /* First parameter points to storage id or is 0 */ + usb_mtp_queue_result(s, RES_STORE_NOT_AVAILABLE, c->trans, + 0, 0, 0, 0); + } else if (c->argv[1] && !c->argv[0]) { + /* If second parameter is specified, first must also be specified */ + usb_mtp_queue_result(s, RES_DESTINATION_UNSUPPORTED, c->trans, + 0, 0, 0, 0); + } else { + uint32_t handle = c->argv[1]; + if (handle == 0xFFFFFFFF || handle == 0) { + /* root object */ + o = QTAILQ_FIRST(&s->objects); + } else { + o = usb_mtp_object_lookup(s, handle); + } + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, c->trans, + 0, 0, 0, 0); + } else if (o->format != FMT_ASSOCIATION) { + usb_mtp_queue_result(s, RES_INVALID_PARENT_OBJECT, c->trans, + 0, 0, 0, 0); + } + } + if (o) { + s->dataset.parent_handle = o->handle; + } + s->data_out = usb_mtp_data_alloc(c); + return; + case CMD_SEND_OBJECT: + if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) { + usb_mtp_queue_result(s, RES_STORE_READ_ONLY, + c->trans, 0, 0, 0, 0); + return; + } + if (!s->write_pending) { + usb_mtp_queue_result(s, RES_INVALID_OBJECTINFO, + c->trans, 0, 0, 0, 0); + return; + } + s->data_out = usb_mtp_data_alloc(c); + return; + case CMD_GET_OBJECT_PROPS_SUPPORTED: + if (c->argv[0] != FMT_UNDEFINED_OBJECT && + c->argv[0] != FMT_ASSOCIATION) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_FORMAT_CODE, + c->trans, 0, 0, 0, 0); + return; + } + data_in = usb_mtp_get_object_props_supported(s, c); + break; + case CMD_GET_OBJECT_PROP_DESC: + if (c->argv[1] != FMT_UNDEFINED_OBJECT && + c->argv[1] != FMT_ASSOCIATION) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_FORMAT_CODE, + c->trans, 0, 0, 0, 0); + return; + } + data_in = usb_mtp_get_object_prop_desc(s, c); + if (data_in == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_PROP_CODE, + c->trans, 0, 0, 0, 0); + return; + } + break; + case CMD_GET_OBJECT_PROP_VALUE: + o = usb_mtp_object_lookup(s, c->argv[0]); + if (o == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, + c->trans, 0, 0, 0, 0); + return; + } + data_in = usb_mtp_get_object_prop_value(s, c, o); + if (data_in == NULL) { + usb_mtp_queue_result(s, RES_INVALID_OBJECT_PROP_CODE, + c->trans, 0, 0, 0, 0); + return; + } + break; + default: + trace_usb_mtp_op_unknown(s->dev.addr, c->code); + usb_mtp_queue_result(s, RES_OPERATION_NOT_SUPPORTED, + c->trans, 0, 0, 0, 0); + return; + } + + /* return results on success */ + if (data_in) { + assert(s->data_in == NULL); + s->data_in = data_in; + } + usb_mtp_queue_result(s, RES_OK, c->trans, nres, res0, 0, 0); +} + +/* ----------------------------------------------------------------------- */ + +static void usb_mtp_handle_reset(USBDevice *dev) +{ + MTPState *s = USB_MTP(dev); + + trace_usb_mtp_reset(s->dev.addr); + + usb_mtp_file_monitor_cleanup(s); + usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects)); + s->session = 0; + usb_mtp_data_free(s->data_in); + s->data_in = NULL; + usb_mtp_data_free(s->data_out); + s->data_out = NULL; + g_free(s->result); + s->result = NULL; +} + +static void usb_mtp_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + int ret; + MTPState *s = USB_MTP(dev); + uint16_t *event = (uint16_t *)data; + + switch (request) { + case ClassInterfaceOutRequest | 0x64: + if (*event == EVT_CANCEL_TRANSACTION) { + g_free(s->result); + s->result = NULL; + usb_mtp_data_free(s->data_in); + s->data_in = NULL; + if (s->write_pending) { + g_free(s->dataset.filename); + s->write_pending = false; + s->dataset.size = 0; + } + usb_mtp_data_free(s->data_out); + s->data_out = NULL; + } else { + p->status = USB_RET_STALL; + } + break; + default: + ret = usb_desc_handle_control(dev, p, request, + value, index, length, data); + if (ret >= 0) { + return; + } + } + + trace_usb_mtp_stall(dev->addr, "unknown control request"); +} + +static void usb_mtp_cancel_packet(USBDevice *dev, USBPacket *p) +{ + /* we don't use async packets, so this should never be called */ + fprintf(stderr, "%s\n", __func__); +} + +static char *utf16_to_str(uint8_t len, uint8_t *str16) +{ + wchar_t *wstr = g_new0(wchar_t, len + 1); + int count, dlen; + char *dest; + + for (count = 0; count < len; count++) { + /* FIXME: not working for surrogate pairs */ + wstr[count] = lduw_le_p(str16 + (count * 2)); + } + wstr[count] = 0; + + dlen = wcstombs(NULL, wstr, 0) + 1; + dest = g_malloc(dlen); + wcstombs(dest, wstr, dlen); + g_free(wstr); + return dest; +} + +/* Wrapper around write, returns 0 on failure */ +static uint64_t write_retry(int fd, void *buf, uint64_t size, off_t offset) +{ + uint64_t ret = 0; + + if (lseek(fd, offset, SEEK_SET) < 0) { + goto done; + } + + ret = qemu_write_full(fd, buf, size); + +done: + return ret; +} + +static int usb_mtp_update_object(MTPObject *parent, char *name) +{ + int ret = 0; + + MTPObject *o = + usb_mtp_object_lookup_name(parent, name, strlen(name)); + + if (o) { + ret = lstat(o->path, &o->stat); + } + + return ret; +} + +static void usb_mtp_write_data(MTPState *s, uint32_t handle) +{ + MTPData *d = s->data_out; + MTPObject *parent = + usb_mtp_object_lookup(s, s->dataset.parent_handle); + char *path = NULL; + uint64_t rc; + mode_t mask = 0644; + int ret = 0; + + assert(d != NULL); + + switch (d->write_status) { + case WRITE_START: + if (!parent || !s->write_pending) { + usb_mtp_queue_result(s, RES_INVALID_OBJECTINFO, d->trans, + 0, 0, 0, 0); + return; + } + + if (s->dataset.filename) { + path = g_strdup_printf("%s/%s", parent->path, s->dataset.filename); + if (s->dataset.format == FMT_ASSOCIATION) { + ret = mkdir(path, mask); + if (!ret) { + usb_mtp_queue_result(s, RES_OK, d->trans, 3, + QEMU_STORAGE_ID, + s->dataset.parent_handle, + handle); + goto close; + } + goto done; + } + + d->fd = open(path, O_CREAT | O_WRONLY | + O_CLOEXEC | O_NOFOLLOW, mask); + if (d->fd == -1) { + ret = 1; + goto done; + } + + /* Return success if initiator sent 0 sized data */ + if (!s->dataset.size) { + goto done; + } + if (d->length != MTP_WRITE_BUF_SZ && !d->pending) { + d->write_status = WRITE_END; + } + } + /* fall through */ + case WRITE_CONTINUE: + case WRITE_END: + rc = write_retry(d->fd, d->data, d->data_offset, + d->offset - d->data_offset); + if (rc != d->data_offset) { + ret = 1; + goto done; + } + if (d->write_status != WRITE_END) { + g_free(path); + return; + } else { + /* + * Return an incomplete transfer if file size doesn't match + * for < 4G file or if lstat fails which will result in an incorrect + * file size + */ + if ((s->dataset.size != 0xFFFFFFFF && + d->offset != s->dataset.size) || + usb_mtp_update_object(parent, s->dataset.filename)) { + usb_mtp_queue_result(s, RES_INCOMPLETE_TRANSFER, d->trans, + 0, 0, 0, 0); + goto close; + } + } + } + +done: + if (ret) { + usb_mtp_queue_result(s, RES_STORE_FULL, d->trans, + 0, 0, 0, 0); + } else { + usb_mtp_queue_result(s, RES_OK, d->trans, + 0, 0, 0, 0); + } +close: + /* + * The write dataset is kept around and freed only + * on success or if another write request comes in + */ + if (d->fd != -1) { + close(d->fd); + d->fd = -1; + } + g_free(s->dataset.filename); + s->dataset.size = 0; + g_free(path); + s->write_pending = false; +} + +static void usb_mtp_write_metadata(MTPState *s, uint64_t dlen) +{ + MTPData *d = s->data_out; + ObjectInfo *dataset = (ObjectInfo *)d->data; + char *filename; + MTPObject *o; + MTPObject *p = usb_mtp_object_lookup(s, s->dataset.parent_handle); + uint32_t next_handle = s->next_handle; + size_t filename_chars = dlen - offsetof(ObjectInfo, filename); + + /* + * filename is utf-16. We're intentionally doing + * integer division to truncate if malicious guest + * sent an odd number of bytes. + */ + filename_chars /= 2; + + assert(!s->write_pending); + assert(p != NULL); + + filename = utf16_to_str(MIN(dataset->length, filename_chars), + dataset->filename); + + if (strchr(filename, '/')) { + usb_mtp_queue_result(s, RES_PARAMETER_NOT_SUPPORTED, d->trans, + 0, 0, 0, 0); + g_free(filename); + return; + } + + o = usb_mtp_object_lookup_name(p, filename, -1); + if (o != NULL) { + next_handle = o->handle; + } + + s->dataset.filename = filename; + s->dataset.format = dataset->format; + s->dataset.size = dataset->size; + s->write_pending = true; + + if (s->dataset.format == FMT_ASSOCIATION) { + usb_mtp_write_data(s, next_handle); + } else { + usb_mtp_queue_result(s, RES_OK, d->trans, 3, QEMU_STORAGE_ID, + s->dataset.parent_handle, next_handle); + } +} + +static void usb_mtp_get_data(MTPState *s, mtp_container *container, + USBPacket *p) +{ + MTPData *d = s->data_out; + uint64_t dlen; + uint32_t data_len = p->iov.size; + uint64_t total_len; + + if (!d) { + usb_mtp_queue_result(s, RES_INVALID_OBJECTINFO, 0, + 0, 0, 0, 0); + return; + } + if (d->first) { + /* Total length of incoming data */ + total_len = cpu_to_le32(container->length) - sizeof(mtp_container); + /* Length of data in this packet */ + data_len -= sizeof(mtp_container); + if (total_len < MTP_WRITE_BUF_SZ) { + usb_mtp_realloc(d, total_len); + d->length += total_len; + } else { + usb_mtp_realloc(d, MTP_WRITE_BUF_SZ - sizeof(mtp_container)); + d->length += MTP_WRITE_BUF_SZ - sizeof(mtp_container); + } + d->offset = 0; + d->first = false; + d->pending = false; + d->data_offset = 0; + d->write_status = WRITE_START; + } + + if (d->pending) { + memset(d->data, 0, d->length); + if (d->length != MTP_WRITE_BUF_SZ) { + usb_mtp_realloc(d, MTP_WRITE_BUF_SZ - d->length); + d->length += (MTP_WRITE_BUF_SZ - d->length); + } + d->pending = false; + d->write_status = WRITE_CONTINUE; + d->data_offset = 0; + } + + if (d->length - d->data_offset > data_len) { + dlen = data_len; + } else { + dlen = d->length - d->data_offset; + } + + switch (d->code) { + case CMD_SEND_OBJECT_INFO: + usb_packet_copy(p, d->data + d->data_offset, dlen); + d->offset += dlen; + d->data_offset += dlen; + if (d->data_offset == d->length) { + /* The operation might have already failed */ + if (!s->result) { + usb_mtp_write_metadata(s, dlen); + } + usb_mtp_data_free(s->data_out); + s->data_out = NULL; + return; + } + break; + case CMD_SEND_OBJECT: + usb_packet_copy(p, d->data + d->data_offset, dlen); + d->offset += dlen; + d->data_offset += dlen; + if ((p->iov.size % 64) || !p->iov.size) { + assert((s->dataset.size == 0xFFFFFFFF) || + (s->dataset.size == d->offset)); + + if (d->length == MTP_WRITE_BUF_SZ) { + d->write_status = WRITE_END; + } else { + d->write_status = WRITE_START; + } + usb_mtp_write_data(s, 0); + usb_mtp_data_free(s->data_out); + s->data_out = NULL; + return; + } + if (d->data_offset == d->length) { + d->pending = true; + usb_mtp_write_data(s, 0); + } + break; + default: + p->status = USB_RET_STALL; + return; + } +} + +static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p) +{ + MTPState *s = USB_MTP(dev); + MTPControl cmd; + mtp_container container; + uint32_t params[5]; + uint16_t container_type; + int i, rc; + + switch (p->ep->nr) { + case EP_DATA_IN: + if (s->data_out != NULL) { + /* guest bug */ + trace_usb_mtp_stall(s->dev.addr, "awaiting data-out"); + p->status = USB_RET_STALL; + return; + } + if (p->iov.size < sizeof(container)) { + trace_usb_mtp_stall(s->dev.addr, "packet too small"); + p->status = USB_RET_STALL; + return; + } + if (s->data_in != NULL) { + MTPData *d = s->data_in; + uint64_t dlen = d->length - d->offset; + if (d->first) { + trace_usb_mtp_data_in(s->dev.addr, d->trans, d->length); + if (d->length + sizeof(container) > 0xFFFFFFFF) { + container.length = cpu_to_le32(0xFFFFFFFF); + } else { + container.length = + cpu_to_le32(d->length + sizeof(container)); + } + container.type = cpu_to_le16(TYPE_DATA); + container.code = cpu_to_le16(d->code); + container.trans = cpu_to_le32(d->trans); + usb_packet_copy(p, &container, sizeof(container)); + d->first = false; + if (dlen > p->iov.size - sizeof(container)) { + dlen = p->iov.size - sizeof(container); + } + } else { + if (dlen > p->iov.size) { + dlen = p->iov.size; + } + } + if (d->fd == -1) { + usb_packet_copy(p, d->data + d->offset, dlen); + } else { + if (d->alloc < p->iov.size) { + d->alloc = p->iov.size; + d->data = g_realloc(d->data, d->alloc); + } + rc = read(d->fd, d->data, dlen); + if (rc != dlen) { + memset(d->data, 0, dlen); + s->result->code = RES_INCOMPLETE_TRANSFER; + } + usb_packet_copy(p, d->data, dlen); + } + d->offset += dlen; + if (d->offset == d->length) { + usb_mtp_data_free(s->data_in); + s->data_in = NULL; + } + } else if (s->result != NULL) { + MTPControl *r = s->result; + int length = sizeof(container) + r->argc * sizeof(uint32_t); + if (r->code == RES_OK) { + trace_usb_mtp_success(s->dev.addr, r->trans, + (r->argc > 0) ? r->argv[0] : 0, + (r->argc > 1) ? r->argv[1] : 0); + } else { + trace_usb_mtp_error(s->dev.addr, r->code, r->trans, + (r->argc > 0) ? r->argv[0] : 0, + (r->argc > 1) ? r->argv[1] : 0); + } + container.length = cpu_to_le32(length); + container.type = cpu_to_le16(TYPE_RESPONSE); + container.code = cpu_to_le16(r->code); + container.trans = cpu_to_le32(r->trans); + for (i = 0; i < r->argc; i++) { + params[i] = cpu_to_le32(r->argv[i]); + } + usb_packet_copy(p, &container, sizeof(container)); + usb_packet_copy(p, ¶ms, length - sizeof(container)); + g_free(s->result); + s->result = NULL; + } + break; + case EP_DATA_OUT: + if (p->iov.size < sizeof(container)) { + trace_usb_mtp_stall(s->dev.addr, "packet too small"); + p->status = USB_RET_STALL; + return; + } + if ((s->data_out != NULL) && !s->data_out->first) { + container_type = TYPE_DATA; + } else { + usb_packet_copy(p, &container, sizeof(container)); + container_type = le16_to_cpu(container.type); + } + switch (container_type) { + case TYPE_COMMAND: + if (s->data_in || s->data_out || s->result) { + trace_usb_mtp_stall(s->dev.addr, "transaction inflight"); + p->status = USB_RET_STALL; + return; + } + cmd.code = le16_to_cpu(container.code); + cmd.argc = (le32_to_cpu(container.length) - sizeof(container)) + / sizeof(uint32_t); + cmd.trans = le32_to_cpu(container.trans); + if (cmd.argc > ARRAY_SIZE(cmd.argv)) { + cmd.argc = ARRAY_SIZE(cmd.argv); + } + if (p->iov.size < sizeof(container) + cmd.argc * sizeof(uint32_t)) { + trace_usb_mtp_stall(s->dev.addr, "packet too small"); + p->status = USB_RET_STALL; + return; + } + usb_packet_copy(p, ¶ms, cmd.argc * sizeof(uint32_t)); + for (i = 0; i < cmd.argc; i++) { + cmd.argv[i] = le32_to_cpu(params[i]); + } + trace_usb_mtp_command(s->dev.addr, cmd.code, cmd.trans, + (cmd.argc > 0) ? cmd.argv[0] : 0, + (cmd.argc > 1) ? cmd.argv[1] : 0, + (cmd.argc > 2) ? cmd.argv[2] : 0, + (cmd.argc > 3) ? cmd.argv[3] : 0, + (cmd.argc > 4) ? cmd.argv[4] : 0); + usb_mtp_command(s, &cmd); + break; + case TYPE_DATA: + /* One of the previous transfers has already errored but the + * responder is still sending data associated with it + */ + if (s->result != NULL) { + return; + } + usb_mtp_get_data(s, &container, p); + break; + default: + /* not needed as long as the mtp device is read-only */ + p->status = USB_RET_STALL; + return; + } + break; + case EP_EVENT: + if (!QTAILQ_EMPTY(&s->events)) { + struct MTPMonEntry *e = QTAILQ_LAST(&s->events); + uint32_t handle; + int len = sizeof(container) + sizeof(uint32_t); + + if (p->iov.size < len) { + trace_usb_mtp_stall(s->dev.addr, + "packet too small to send event"); + p->status = USB_RET_STALL; + return; + } + + QTAILQ_REMOVE(&s->events, e, next); + container.length = cpu_to_le32(len); + container.type = cpu_to_le32(TYPE_EVENT); + container.code = cpu_to_le16(e->event); + container.trans = 0; /* no trans specific events */ + handle = cpu_to_le32(e->handle); + usb_packet_copy(p, &container, sizeof(container)); + usb_packet_copy(p, &handle, sizeof(uint32_t)); + g_free(e); + return; + } + p->status = USB_RET_NAK; + return; + default: + trace_usb_mtp_stall(s->dev.addr, "invalid endpoint"); + p->status = USB_RET_STALL; + return; + } + + if (p->actual_length == 0) { + trace_usb_mtp_nak(s->dev.addr, p->ep->nr); + p->status = USB_RET_NAK; + return; + } else { + trace_usb_mtp_xfer(s->dev.addr, p->ep->nr, p->actual_length, + p->iov.size); + return; + } +} + +static void usb_mtp_realize(USBDevice *dev, Error **errp) +{ + MTPState *s = USB_MTP(dev); + + if ((s->root == NULL) || !g_path_is_absolute(s->root)) { + error_setg(errp, "usb-mtp: rootdir must be configured and be an absolute path"); + return; + } + + if (access(s->root, R_OK) != 0) { + error_setg(errp, "usb-mtp: rootdir does not exist/not readable"); + return; + } else if (!s->readonly && access(s->root, W_OK) != 0) { + error_setg(errp, "usb-mtp: rootdir does not have write permissions"); + return; + } + + /* Mark store as RW */ + if (!s->readonly) { + s->flags |= (1 << MTP_FLAG_WRITABLE); + } + + if (s->desc == NULL) { + /* + * This does not check if path exists + * but we have the checks above + */ + s->desc = g_path_get_basename(s->root); + } + + usb_desc_create_serial(dev); + usb_desc_init(dev); + QTAILQ_INIT(&s->objects); + +} + +static const VMStateDescription vmstate_usb_mtp = { + .name = "usb-mtp", + .unmigratable = 1, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(dev, MTPState), + VMSTATE_END_OF_LIST() + } +}; + +static Property mtp_properties[] = { + DEFINE_PROP_STRING("rootdir", MTPState, root), + DEFINE_PROP_STRING("desc", MTPState, desc), + DEFINE_PROP_BOOL("readonly", MTPState, readonly, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_mtp_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_mtp_realize; + uc->product_desc = "QEMU USB MTP"; + uc->usb_desc = &desc; + uc->cancel_packet = usb_mtp_cancel_packet; + uc->handle_attach = usb_desc_attach; + uc->handle_reset = usb_mtp_handle_reset; + uc->handle_control = usb_mtp_handle_control; + uc->handle_data = usb_mtp_handle_data; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->desc = "USB Media Transfer Protocol device"; + dc->fw_name = "mtp"; + dc->vmsd = &vmstate_usb_mtp; + device_class_set_props(dc, mtp_properties); +} + +static TypeInfo mtp_info = { + .name = TYPE_USB_MTP, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(MTPState), + .class_init = usb_mtp_class_initfn, +}; + +static void usb_mtp_register_types(void) +{ + type_register_static(&mtp_info); +} + +type_init(usb_mtp_register_types) diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c new file mode 100644 index 000000000..6c49c1601 --- /dev/null +++ b/hw/usb/dev-network.c @@ -0,0 +1,1429 @@ +/* + * 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/osdep.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "net/net.h" +#include "qemu/error-report.h" +#include "qemu/queue.h" +#include "qemu/config-file.h" +#include "sysemu/sysemu.h" +#include "qemu/iov.h" +#include "qemu/module.h" +#include "qemu/cutils.h" +#include "qom/object.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 = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface_rndis), + .ifs = desc_iface_rndis, + },{ + .bNumInterfaces = 2, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = STRING_CDC, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .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[]; +}; + +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]; + + unsigned int in_ptr, in_len; + uint8_t in_buf[2048]; + + USBEndpoint *intr; + + char usbstring_mac[13]; + NICState *nic; + NICConf conf; + QTAILQ_HEAD(, rndis_response) rndis_resp; +}; + +#define TYPE_USB_NET "usb-net" +OBJECT_DECLARE_SIMPLE_TYPE(USBNetState, USB_NET) + +static int is_rndis(USBNetState *s) +{ + return s->dev.config ? + s->dev.config->bConfigurationValue == DEV_RNDIS_CONFIG_VALUE : 0; +} + +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++) { + stl_le_p(outbuf + (i * sizeof(le32)), oid_supported_list[i]); + } + return sizeof(oid_supported_list); + + /* mandatory */ + case OID_GEN_HARDWARE_STATUS: + stl_le_p(outbuf, 0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MEDIA_SUPPORTED: + stl_le_p(outbuf, s->medium); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MEDIA_IN_USE: + stl_le_p(outbuf, s->medium); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MAXIMUM_FRAME_SIZE: + stl_le_p(outbuf, ETH_FRAME_LEN); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_LINK_SPEED: + stl_le_p(outbuf, s->speed); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_TRANSMIT_BLOCK_SIZE: + stl_le_p(outbuf, ETH_FRAME_LEN); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RECEIVE_BLOCK_SIZE: + stl_le_p(outbuf, ETH_FRAME_LEN); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_VENDOR_ID: + stl_le_p(outbuf, 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: + stl_le_p(outbuf, 1); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_CURRENT_PACKET_FILTER: + stl_le_p(outbuf, s->filter); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MAXIMUM_TOTAL_SIZE: + stl_le_p(outbuf, RNDIS_MAX_TOTAL_SIZE); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_MEDIA_CONNECT_STATUS: + stl_le_p(outbuf, s->media_state); + return sizeof(le32); + + case OID_GEN_PHYSICAL_MEDIUM: + stl_le_p(outbuf, 0); + return sizeof(le32); + + case OID_GEN_MAC_OPTIONS: + stl_le_p(outbuf, NDIS_MAC_OPTION_RECEIVE_SERIALIZED | + NDIS_MAC_OPTION_FULL_DUPLEX); + return sizeof(le32); + + /* statistics OIDs (table 4-2) */ + /* mandatory */ + case OID_GEN_XMIT_OK: + stl_le_p(outbuf, 0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RCV_OK: + stl_le_p(outbuf, 0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_XMIT_ERROR: + stl_le_p(outbuf, 0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RCV_ERROR: + stl_le_p(outbuf, 0); + return sizeof(le32); + + /* mandatory */ + case OID_GEN_RCV_NO_BUFFER: + stl_le_p(outbuf, 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: + stl_le_p(outbuf, 0xe0000000); + return sizeof(le32); + + /* mandatory */ + case OID_802_3_MAXIMUM_LIST_SIZE: + stl_le_p(outbuf, 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: + stl_le_p(outbuf, 0); + return sizeof(le32); + + /* mandatory */ + case OID_802_3_XMIT_ONE_COLLISION: + stl_le_p(outbuf, 0); + return sizeof(le32); + + /* mandatory */ + case OID_802_3_XMIT_MORE_COLLISIONS: + stl_le_p(outbuf, 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 = ldl_le_p(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); + + if (QTAILQ_EMPTY(&s->rndis_resp)) { + usb_wakeup(s->intr, 0); + } + + 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 (buflen > length || bufoffs >= length || 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 (buflen > length || bufoffs >= length || 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; +} + +/* Prepare to receive the next packet */ +static void usb_net_reset_in_buf(USBNetState *s) +{ + s->in_ptr = s->in_len = 0; + qemu_flush_queued_packets(qemu_get_queue(s->nic)); +} + +static int rndis_parse(USBNetState *s, uint8_t *data, int length) +{ + uint32_t msg_type = ldl_le_p(data); + + 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 = 0; + usb_net_reset_in_buf(s); + 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 void 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; + } + + 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); + if (ret < 0) { + p->status = ret; + } + break; + + case ClassInterfaceRequest | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (!is_rndis(s) || value || index != 0) { + goto fail; + } + p->actual_length = rndis_get_response(s, data); + if (p->actual_length == 0) { + data[0] = 0; + p->actual_length = 1; + } +#ifdef TRAFFIC_DEBUG + { + unsigned int i; + fprintf(stderr, "GET_ENCAPSULATED_RESPONSE:"); + for (i = 0; i < p->actual_length; 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); + p->status = USB_RET_STALL; + break; + } +} + +static void usb_net_handle_statusin(USBNetState *s, USBPacket *p) +{ + le32 buf[2]; + + if (p->iov.size < 8) { + p->status = USB_RET_STALL; + return; + } + + buf[0] = cpu_to_le32(1); + buf[1] = cpu_to_le32(0); + usb_packet_copy(p, buf, 8); + if (!s->rndis_resp.tqh_first) { + p->status = USB_RET_NAK; + } + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "usbnet: interrupt poll len %zu return %d", + p->iov.size, p->status); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->status); +#endif +} + +static void usb_net_handle_datain(USBNetState *s, USBPacket *p) +{ + int len; + + if (s->in_ptr > s->in_len) { + usb_net_reset_in_buf(s); + p->status = USB_RET_NAK; + return; + } + if (!s->in_len) { + p->status = USB_RET_NAK; + return; + } + len = s->in_len - s->in_ptr; + if (len > p->iov.size) { + len = p->iov.size; + } + usb_packet_copy(p, &s->in_buf[s->in_ptr], len); + s->in_ptr += len; + if (s->in_ptr >= s->in_len && + (is_rndis(s) || (s->in_len & (64 - 1)) || !len)) { + /* no short packet necessary */ + usb_net_reset_in_buf(s); + } + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "usbnet: data in len %zu return %d", p->iov.size, len); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", len); +#endif +} + +static void usb_net_handle_dataout(USBNetState *s, USBPacket *p) +{ + 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 > p->iov.size) { + sz = p->iov.size; + } + usb_packet_copy(p, &s->out_buf[s->out_ptr], sz); + s->out_ptr += sz; + + if (!is_rndis(s)) { + if (p->iov.size < 64) { + qemu_send_packet(qemu_get_queue(s->nic), s->out_buf, s->out_ptr); + s->out_ptr = 0; + } + return; + } + len = le32_to_cpu(msg->MessageLength); + if (s->out_ptr < 8 || s->out_ptr < len) { + return; + } + 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 < len && size < len && offs + size <= len) { + qemu_send_packet(qemu_get_queue(s->nic), s->out_buf + offs, size); + } + } + s->out_ptr -= len; + memmove(s->out_buf, &s->out_buf[len], s->out_ptr); +} + +static void usb_net_handle_data(USBDevice *dev, USBPacket *p) +{ + USBNetState *s = (USBNetState *) dev; + + switch(p->pid) { + case USB_TOKEN_IN: + switch (p->ep->nr) { + case 1: + usb_net_handle_statusin(s, p); + break; + + case 2: + usb_net_handle_datain(s, p); + break; + + default: + goto fail; + } + break; + + case USB_TOKEN_OUT: + switch (p->ep->nr) { + case 2: + usb_net_handle_dataout(s, p); + break; + + default: + goto fail; + } + break; + + default: + fail: + p->status = USB_RET_STALL; + break; + } + + if (p->status == 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); + } +} + +static ssize_t usbnet_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + USBNetState *s = qemu_get_nic_opaque(nc); + uint8_t *in_buf = s->in_buf; + size_t total_size = size; + + if (!s->dev.config) { + return -1; + } + + if (is_rndis(s)) { + if (s->rndis_state != RNDIS_DATA_INITIALIZED) { + return -1; + } + total_size += sizeof(struct rndis_packet_msg_type); + } + if (total_size > sizeof(s->in_buf)) { + return -1; + } + + /* Only accept packet if input buffer is empty */ + if (s->in_len > 0) { + return 0; + } + + if (is_rndis(s)) { + struct rndis_packet_msg_type *msg; + + msg = (struct rndis_packet_msg_type *)in_buf; + memset(msg, 0, sizeof(struct rndis_packet_msg_type)); + msg->MessageType = cpu_to_le32(RNDIS_PACKET_MSG); + msg->MessageLength = cpu_to_le32(size + sizeof(*msg)); + msg->DataOffset = cpu_to_le32(sizeof(*msg) - 8); + msg->DataLength = cpu_to_le32(size); + /* msg->OOBDataOffset; + * msg->OOBDataLength; + * msg->NumOOBDataElements; + * msg->PerPacketInfoOffset; + * msg->PerPacketInfoLength; + * msg->VcHandle; + * msg->Reserved; + */ + in_buf += sizeof(*msg); + } + + memcpy(in_buf, buf, size); + s->in_len = total_size; + s->in_ptr = 0; + return size; +} + +static void usbnet_cleanup(NetClientState *nc) +{ + USBNetState *s = qemu_get_nic_opaque(nc); + + s->nic = NULL; +} + +static void usb_net_unrealize(USBDevice *dev) +{ + USBNetState *s = (USBNetState *) dev; + + /* TODO: remove the nd_table[] entry */ + rndis_clear_responsequeue(s); + qemu_del_nic(s->nic); +} + +static NetClientInfo net_usbnet_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .receive = usbnet_receive, + .cleanup = usbnet_cleanup, +}; + +static void usb_net_realize(USBDevice *dev, Error **errp) +{ + USBNetState *s = USB_NET(dev); + + usb_desc_create_serial(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; + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); + + 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(qemu_get_queue(s->nic), 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); +} + +static void usb_net_instance_init(Object *obj) +{ + USBDevice *dev = USB_DEVICE(obj); + USBNetState *s = USB_NET(dev); + + device_add_bootindex_property(obj, &s->conf.bootindex, + "bootindex", "/ethernet-phy@0", + &dev->qdev); +} + +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->realize = usb_net_realize; + 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->unrealize = usb_net_unrealize; + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); + dc->fw_name = "network"; + dc->vmsd = &vmstate_usb_net; + device_class_set_props(dc, net_properties); +} + +static const TypeInfo net_info = { + .name = TYPE_USB_NET, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBNetState), + .class_init = usb_net_class_initfn, + .instance_init = usb_net_instance_init, +}; + +static void usb_net_register_types(void) +{ + type_register_static(&net_info); +} + +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 000000000..63047d79c --- /dev/null +++ b/hw/usb/dev-serial.c @@ -0,0 +1,709 @@ +/* + * 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/osdep.h" +#include "qapi/error.h" +#include "qemu/cutils.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "chardev/char-serial.h" +#include "chardev/char-fe.h" +#include "qom/object.h" +#include "trace.h" + + +#define RECV_BUF (512 - (2 * 8)) + +/* 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 + +/* 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_NO_HS 0 +#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 */ + +struct USBSerialState { + USBDevice dev; + + USBEndpoint *intr; + 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; + bool always_plugged; + uint8_t flow_control; + uint8_t xon; + uint8_t xoff; + QEMUSerialSetParams params; + int latency; /* ms */ + CharBackend cs; +}; + +#define TYPE_USB_SERIAL "usb-serial-dev" +OBJECT_DECLARE_SIMPLE_TYPE(USBSerialState, USB_SERIAL) + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT_SERIAL, + STR_PRODUCT_BRAILLE, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [STR_PRODUCT_SERIAL] = "QEMU USB SERIAL", + [STR_PRODUCT_BRAILLE] = "QEMU USB BAUM 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 = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .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_set_flow_control(USBSerialState *s, + uint8_t flow_control) +{ + USBDevice *dev = USB_DEVICE(s); + USBBus *bus = usb_bus_from_device(dev); + + /* TODO: ioctl */ + s->flow_control = flow_control; + trace_usb_serial_set_flow_control(bus->busnr, dev->addr, flow_control); +} + +static void usb_serial_set_xonxoff(USBSerialState *s, int xonxoff) +{ + USBDevice *dev = USB_DEVICE(s); + USBBus *bus = usb_bus_from_device(dev); + + s->xon = xonxoff & 0xff; + s->xoff = (xonxoff >> 8) & 0xff; + + trace_usb_serial_set_xonxoff(bus->busnr, dev->addr, s->xon, s->xoff); +} + +static void usb_serial_reset(USBSerialState *s) +{ + s->event_chr = 0x0d; + s->event_trigger = 0; + s->recv_ptr = 0; + s->recv_used = 0; + /* TODO: purge in char driver */ + usb_serial_set_flow_control(s, FTDI_NO_HS); +} + +static void usb_serial_handle_reset(USBDevice *dev) +{ + USBSerialState *s = USB_SERIAL(dev); + USBBus *bus = usb_bus_from_device(dev); + + trace_usb_serial_reset(bus->busnr, dev->addr); + + 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 void usb_serial_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + USBSerialState *s = USB_SERIAL(dev); + USBBus *bus = usb_bus_from_device(dev); + int ret; + + trace_usb_serial_handle_control(bus->busnr, dev->addr, request, value); + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch (request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + break; + + /* Class specific requests. */ + case VendorDeviceOutRequest | 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 VendorDeviceOutRequest | 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 VendorDeviceOutRequest | FTDI_SET_FLOW_CTRL: { + uint8_t flow_control = index >> 8; + + usb_serial_set_flow_control(s, flow_control); + if (flow_control & FTDI_XON_XOFF_HS) { + usb_serial_set_xonxoff(s, value); + } + break; + } + case VendorDeviceOutRequest | 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); + trace_usb_serial_set_baud(bus->busnr, dev->addr, s->params.speed); + qemu_chr_fe_ioctl(&s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + break; + } + case VendorDeviceOutRequest | FTDI_SET_DATA: + switch (value & 0xff) { + case 7: + s->params.data_bits = 7; + break; + case 8: + s->params.data_bits = 8; + break; + default: + /* + * According to a comment in Linux's ftdi_sio.c original FTDI + * chips fall back to 8 data bits for unsupported data_bits + */ + trace_usb_serial_unsupported_data_bits(bus->busnr, dev->addr, + value & 0xff); + s->params.data_bits = 8; + } + + 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: + trace_usb_serial_unsupported_parity(bus->busnr, dev->addr, + 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: + trace_usb_serial_unsupported_stopbits(bus->busnr, dev->addr, + value & FTDI_STOP); + goto fail; + } + + trace_usb_serial_set_data(bus->busnr, dev->addr, s->params.parity, + s->params.data_bits, s->params.stop_bits); + qemu_chr_fe_ioctl(&s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + /* TODO: TX ON/OFF */ + break; + case VendorDeviceRequest | FTDI_GET_MDM_ST: + data[0] = usb_get_modem_lines(s) | 1; + data[1] = FTDI_THRE | FTDI_TEMT; + p->actual_length = 2; + break; + case VendorDeviceOutRequest | FTDI_SET_EVENT_CHR: + /* TODO: handle it */ + s->event_chr = value; + break; + case VendorDeviceOutRequest | FTDI_SET_ERROR_CHR: + /* TODO: handle it */ + s->error_chr = value; + break; + case VendorDeviceOutRequest | FTDI_SET_LATENCY: + s->latency = value; + break; + case VendorDeviceRequest | FTDI_GET_LATENCY: + data[0] = s->latency; + p->actual_length = 1; + break; + default: + fail: + trace_usb_serial_unsupported_control(bus->busnr, dev->addr, request, + value); + p->status = USB_RET_STALL; + break; + } +} + +static void usb_serial_token_in(USBSerialState *s, USBPacket *p) +{ + const int max_packet_size = desc_iface0.eps[0].wMaxPacketSize; + int packet_len; + uint8_t header[2]; + + packet_len = p->iov.size; + if (packet_len <= 2) { + p->status = USB_RET_NAK; + return; + } + + 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); + return; + } else { + header[1] = 0; + } + + if (!s->recv_used) { + p->status = USB_RET_NAK; + return; + } + + while (s->recv_used && packet_len > 2) { + int first_len, len; + + len = MIN(packet_len, max_packet_size); + len -= 2; + if (len > s->recv_used) { + len = s->recv_used; + } + + first_len = RECV_BUF - s->recv_ptr; + 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; + packet_len -= len + 2; + } + + return; +} + +static void usb_serial_handle_data(USBDevice *dev, USBPacket *p) +{ + USBSerialState *s = USB_SERIAL(dev); + USBBus *bus = usb_bus_from_device(dev); + uint8_t devep = p->ep->nr; + struct iovec *iov; + int i; + + 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; + /* + * XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks + */ + qemu_chr_fe_write_all(&s->cs, iov->iov_base, iov->iov_len); + } + p->actual_length = p->iov.size; + break; + + case USB_TOKEN_IN: + if (devep != 1) { + goto fail; + } + usb_serial_token_in(s, p); + break; + + default: + trace_usb_serial_bad_token(bus->busnr, dev->addr); + fail: + p->status = USB_RET_STALL; + break; + } +} + +static int usb_serial_can_read(void *opaque) +{ + USBSerialState *s = opaque; + + if (!s->dev.attached) { + return 0; + } + 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; + + usb_wakeup(s->intr, 0); +} + +static void usb_serial_event(void *opaque, QEMUChrEvent event) +{ + USBSerialState *s = opaque; + + switch (event) { + case CHR_EVENT_BREAK: + s->event_trigger |= FTDI_BI; + break; + case CHR_EVENT_OPENED: + if (!s->always_plugged && !s->dev.attached) { + usb_device_attach(&s->dev, &error_abort); + } + break; + case CHR_EVENT_CLOSED: + if (!s->always_plugged && s->dev.attached) { + usb_device_detach(&s->dev); + } + break; + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + /* Ignore */ + break; + } +} + +static void usb_serial_realize(USBDevice *dev, Error **errp) +{ + USBSerialState *s = USB_SERIAL(dev); + Error *local_err = NULL; + + usb_desc_create_serial(dev); + usb_desc_init(dev); + dev->auto_attach = 0; + + if (!qemu_chr_fe_backend_connected(&s->cs)) { + error_setg(errp, "Property chardev is required"); + return; + } + + usb_check_attach(dev, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + qemu_chr_fe_set_handlers(&s->cs, usb_serial_can_read, usb_serial_read, + usb_serial_event, NULL, s, NULL, true); + usb_serial_handle_reset(dev); + + if ((s->always_plugged || qemu_chr_fe_backend_open(&s->cs)) && + !dev->attached) { + usb_device_attach(dev, &error_abort); + } + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); +} + +static USBDevice *usb_braille_init(void) +{ + USBDevice *dev; + Chardev *cdrv; + + cdrv = qemu_chr_new("braille", "braille", NULL); + if (!cdrv) { + return NULL; + } + + dev = usb_new("usb-braille"); + qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); + 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_BOOL("always-plugged", USBSerialState, always_plugged, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_serial_dev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_serial_realize; + uc->handle_reset = usb_serial_handle_reset; + uc->handle_control = usb_serial_handle_control; + uc->handle_data = usb_serial_handle_data; + dc->vmsd = &vmstate_usb_serial; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo usb_serial_dev_type_info = { + .name = TYPE_USB_SERIAL, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBSerialState), + .abstract = true, + .class_init = usb_serial_dev_class_init, +}; + +static void usb_serial_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "QEMU USB Serial"; + uc->usb_desc = &desc_serial; + device_class_set_props(dc, serial_properties); +} + +static const TypeInfo serial_info = { + .name = "usb-serial", + .parent = TYPE_USB_SERIAL, + .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->product_desc = "QEMU USB Braille"; + uc->usb_desc = &desc_braille; + device_class_set_props(dc, braille_properties); +} + +static const TypeInfo braille_info = { + .name = "usb-braille", + .parent = TYPE_USB_SERIAL, + .class_init = usb_braille_class_initfn, +}; + +static void usb_serial_register_types(void) +{ + type_register_static(&usb_serial_dev_type_info); + type_register_static(&serial_info); + 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 000000000..91ffd9f8a --- /dev/null +++ b/hw/usb/dev-smartcard-reader.c @@ -0,0 +1,1496 @@ +/* + * 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/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "qemu-common.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" + +#include "ccid.h" +#include "qom/object.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 TYPE_USB_CCID_DEV "usb-ccid" +OBJECT_DECLARE_SIMPLE_TYPE(USBCCIDState, USB_CCID_DEV) +/* + * 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 (64 * KiB) +#define PENDING_ANSWERS_NUM 128 + +#define BULK_IN_BUF_SIZE 384 +#define BULK_IN_PENDING_NUM 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" +#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_T0ProtocolDataStructure { + uint8_t bmFindexDindex; + uint8_t bmTCCKST0; + uint8_t bGuardTimeT0; + uint8_t bWaitingIntegerT0; + uint8_t bClockStop; +} CCID_T0ProtocolDataStructure; + +typedef struct QEMU_PACKED CCID_T1ProtocolDataStructure { + uint8_t bmFindexDindex; + uint8_t bmTCCKST1; + uint8_t bGuardTimeT1; + uint8_t bWaitingIntegerT1; + uint8_t bClockStop; + uint8_t bIFSC; + uint8_t bNadValue; +} CCID_T1ProtocolDataStructure; + +typedef union CCID_ProtocolDataStructure { + CCID_T0ProtocolDataStructure t0; + CCID_T1ProtocolDataStructure t1; + uint8_t data[7]; /* must be = max(sizeof(t0), sizeof(t1)) */ +} CCID_ProtocolDataStructure; + +typedef struct QEMU_PACKED CCID_Parameter { + CCID_BULK_IN b; + uint8_t bProtocolNum; + CCID_ProtocolDataStructure abProtocolDataStructure; +} CCID_Parameter; + +typedef struct QEMU_PACKED CCID_DataBlock { + CCID_BULK_IN b; + uint8_t bChainParameter; + uint8_t abData[]; +} 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[]; +} 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; + CCID_ProtocolDataStructure abProtocolDataStructure; +} 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; + +struct CCIDBus { + BusState qbus; +}; +typedef struct CCIDBus CCIDBus; + +/* + * powered - defaults to true, changed by PowerOn/PowerOff messages + */ +struct USBCCIDState { + USBDevice dev; + USBEndpoint *intr; + USBEndpoint *bulk; + 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; + CCID_ProtocolDataStructure abProtocolDataStructure; + uint32_t ulProtocolDataStructureSize; + uint32_t state_vmstate; + uint8_t bmSlotICCState; + uint8_t powered; + uint8_t notify_slot_change; + uint8_t debug; +}; + +/* + * 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 */ + + 0x01, 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, 0x01, 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", + [STR_PRODUCT] = "QEMU USB CCID", + [STR_SERIALNUMBER] = "1", + [STR_INTERFACE] = "CCID Interface", +}; + +static const USBDescIface desc_iface0 = { + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_CSCID, + .bInterfaceSubClass = USB_SUBCLASS_UNDEFINED, + .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 = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER | + USB_CFG_ATT_WAKEUP, + .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 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 = USB_CCID_DEV(dev); + + DPRINTF(s, 1, "Reset\n"); + + ccid_reset(s); +} + +static const char *ccid_control_to_str(USBCCIDState *s, int request) +{ + switch (request) { + /* generic - should be factored out if there are other debugees */ + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + return "(generic) set address"; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + return "(generic) get descriptor"; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + return "(generic) get configuration"; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + return "(generic) set configuration"; + case DeviceRequest | USB_REQ_GET_STATUS: + return "(generic) get status"; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + return "(generic) clear feature"; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + return "(generic) set_feature"; + case InterfaceRequest | USB_REQ_GET_INTERFACE: + return "(generic) get interface"; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + return "(generic) set interface"; + /* class requests */ + case ClassInterfaceOutRequest | CCID_CONTROL_ABORT: + return "ABORT"; + case ClassInterfaceRequest | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + return "GET_CLOCK_FREQUENCIES"; + case ClassInterfaceRequest | CCID_CONTROL_GET_DATA_RATES: + return "GET_DATA_RATES"; + } + return "unknown"; +} + +static void ccid_handle_control(USBDevice *dev, USBPacket *p, int request, + int value, int index, int length, uint8_t *data) +{ + USBCCIDState *s = USB_CCID_DEV(dev); + int ret; + + DPRINTF(s, 1, "%s: got control %s (%x), value %x\n", __func__, + ccid_control_to_str(s, request), request, value); + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch (request) { + /* Class specific requests. */ + case ClassInterfaceOutRequest | CCID_CONTROL_ABORT: + DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); + p->status = USB_RET_STALL; + break; + case ClassInterfaceRequest | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); + p->status = USB_RET_STALL; + break; + case ClassInterfaceRequest | CCID_CONTROL_GET_DATA_RATES: + DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); + p->status = USB_RET_STALL; + break; + default: + DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", + request, value); + p->status = USB_RET_STALL; + break; + } +} + +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, "%s: status = %d\n", __func__, 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); + usb_wakeup(s->bulk, 0); +} + +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; + h->abProtocolDataStructure = s->abProtocolDataStructure; + ccid_reset_error_status(s); + usb_wakeup(s->bulk, 0); +} + +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\n", p->b.bError); + } + if (len) { + assert(data); + memcpy(p->abData, data, len); + } + ccid_reset_error_status(s); + usb_wakeup(s->bulk, 0); +} + +static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) +{ + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = error; +} + +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)) { + DPRINTF(s, D_WARN, "error: no pending answer to return to guest\n"); + ccid_report_error_failed(s, ERROR_ICC_MUTE); + return; + } + ccid_remove_pending_answer(s, &slot, &seq); + ccid_write_data_block(s, slot, seq, data, len); +} + +static uint8_t atr_get_protocol_num(const uint8_t *atr, uint32_t len) +{ + int i; + + if (len < 2 || !(atr[1] & 0x80)) { + /* too short or TD1 not included */ + return 0; /* T=0, default */ + } + i = 1 + !!(atr[1] & 0x10) + !!(atr[1] & 0x20) + !!(atr[1] & 0x40); + i += !!(atr[1] & 0x80); + return atr[i] & 0x0f; +} + +static void ccid_write_data_block_atr(USBCCIDState *s, CCID_Header *recv) +{ + const uint8_t *atr = NULL; + uint32_t len = 0; + uint8_t atr_protocol_num; + CCID_T0ProtocolDataStructure *t0 = &s->abProtocolDataStructure.t0; + CCID_T1ProtocolDataStructure *t1 = &s->abProtocolDataStructure.t1; + + if (s->card) { + atr = ccid_card_get_atr(s->card, &len); + } + atr_protocol_num = atr_get_protocol_num(atr, len); + DPRINTF(s, D_VERBOSE, "%s: atr contains protocol=%d\n", __func__, + atr_protocol_num); + /* set parameters from ATR - see spec page 109 */ + s->bProtocolNum = (atr_protocol_num <= 1 ? atr_protocol_num + : s->bProtocolNum); + switch (atr_protocol_num) { + case 0: + /* TODO: unimplemented ATR T0 parameters */ + t0->bmFindexDindex = 0; + t0->bmTCCKST0 = 0; + t0->bGuardTimeT0 = 0; + t0->bWaitingIntegerT0 = 0; + t0->bClockStop = 0; + break; + case 1: + /* TODO: unimplemented ATR T1 parameters */ + t1->bmFindexDindex = 0; + t1->bmTCCKST1 = 0; + t1->bGuardTimeT1 = 0; + t1->bWaitingIntegerT1 = 0; + t1->bClockStop = 0; + t1->bIFSC = 0; + t1->bNadValue = 0; + break; + default: + DPRINTF(s, D_WARN, "%s: error: unsupported ATR protocol %d\n", + __func__, atr_protocol_num); + } + 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 protocol_num = ph->bProtocolNum & 3; + + if (protocol_num != 0 && protocol_num != 1) { + ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); + return; + } + s->bProtocolNum = protocol_num; + s->abProtocolDataStructure = ph->abProtocolDataStructure; +} + +/* + * must be 5 bytes for T=0, 7 bytes for T=1 + * See page 52 + */ +static const CCID_ProtocolDataStructure defaultProtocolDataStructure = { + .t1 = { + .bmFindexDindex = 0x77, + .bmTCCKST1 = 0x00, + .bGuardTimeT1 = 0x00, + .bWaitingIntegerT1 = 0x00, + .bClockStop = 0x00, + .bIFSC = 0xfe, + .bNadValue = 0x00, + } +}; + +static void ccid_reset_parameters(USBCCIDState *s) +{ + s->bProtocolNum = 0; /* T=0 */ + s->abProtocolDataStructure = defaultProtocolDataStructure; +} + +/* 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, 0); +} + +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 %u\n", __func__, + recv->hdr.bSeq, len); + ccid_add_pending_answer(s, (CCID_Header *)recv); + if (s->card && len <= BULK_OUT_DATA_SIZE) { + ccid_card_apdu_from_guest(s->card, recv->abData, len); + } else { + DPRINTF(s, D_WARN, "warning: discarded apdu\n"); + } +} + +static const char *ccid_message_type_to_str(uint8_t type) +{ + switch (type) { + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: return "IccPowerOn"; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: return "IccPowerOff"; + case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: return "GetSlotStatus"; + case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: return "XfrBlock"; + case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: return "GetParameters"; + case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: return "ResetParameters"; + case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: return "SetParameters"; + case CCID_MESSAGE_TYPE_PC_to_RDR_Escape: return "Escape"; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccClock: return "IccClock"; + case CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU: return "T0APDU"; + case CCID_MESSAGE_TYPE_PC_to_RDR_Secure: return "Secure"; + case CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical: return "Mechanical"; + case CCID_MESSAGE_TYPE_PC_to_RDR_Abort: return "Abort"; + case CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency: + return "SetDataRateAndClockFrequency"; + } + return "unknown"; +} + +static void ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) +{ + CCID_Header *ccid_header; + + if (p->iov.size + s->bulk_out_pos > BULK_OUT_DATA_SIZE) { + goto err; + } + usb_packet_copy(p, s->bulk_out_data + s->bulk_out_pos, p->iov.size); + s->bulk_out_pos += p->iov.size; + if (s->bulk_out_pos < 10) { + DPRINTF(s, 1, "%s: header incomplete\n", __func__); + goto err; + } + + ccid_header = (CCID_Header *)s->bulk_out_data; + if ((s->bulk_out_pos - 10 < ccid_header->dwLength) && + (p->iov.size == CCID_MAX_PACKET_SIZE)) { + DPRINTF(s, D_VERBOSE, + "usb-ccid: bulk_in: expecting more packets (%u/%u)\n", + s->bulk_out_pos - 10, ccid_header->dwLength); + return; + } + if (s->bulk_out_pos - 10 != ccid_header->dwLength) { + DPRINTF(s, 1, + "usb-ccid: bulk_in: message size mismatch (got %u, expected %u)\n", + s->bulk_out_pos - 10, ccid_header->dwLength); + goto err; + } + + DPRINTF(s, D_MORE_INFO, "%s %x %s\n", __func__, + ccid_header->bMessageType, + ccid_message_type_to_str(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, "%s: PowerOn: %d\n", __func__, + ((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: + 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; + case CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical: + ccid_report_error_failed(s, 0); + ccid_write_slot_status(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; + +err: + p->status = USB_RET_STALL; + s->bulk_out_pos = 0; + return; +} + +static void ccid_bulk_in_copy_to_guest(USBCCIDState *s, USBPacket *p, + unsigned int max_packet_size) +{ + int len = 0; + + ccid_bulk_in_get(s); + if (s->current_bulk_in != NULL) { + len = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, + p->iov.size); + if (len) { + usb_packet_copy(p, s->current_bulk_in->data + + s->current_bulk_in->pos, len); + } + s->current_bulk_in->pos += len; + if (s->current_bulk_in->pos == s->current_bulk_in->len + && len != max_packet_size) { + ccid_bulk_in_release(s); + } + } else { + /* return when device has no data - usb 2.0 spec Table 8-4 */ + p->status = USB_RET_NAK; + } + if (len) { + DPRINTF(s, D_MORE_INFO, + "%s: %zd/%d req/act to guest (BULK_IN)\n", + __func__, p->iov.size, len); + } + if (len < p->iov.size) { + DPRINTF(s, 1, + "%s: returning short (EREMOTEIO) %d < %zd\n", + __func__, len, p->iov.size); + } +} + +static void ccid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBCCIDState *s = USB_CCID_DEV(dev); + uint8_t buf[2]; + + switch (p->pid) { + case USB_TOKEN_OUT: + ccid_handle_bulk_out(s, p); + break; + + case USB_TOKEN_IN: + switch (p->ep->nr) { + case CCID_BULK_IN_EP: + ccid_bulk_in_copy_to_guest(s, p, dev->ep_ctl.max_packet_size); + 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); + 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); + } else { + p->status = USB_RET_NAK; + } + break; + default: + DPRINTF(s, 1, "Bad endpoint\n"); + p->status = USB_RET_STALL; + break; + } + break; + default: + DPRINTF(s, 1, "Bad token\n"); + p->status = USB_RET_STALL; + break; + } +} + +static void ccid_unrealize(USBDevice *dev) +{ + USBCCIDState *s = USB_CCID_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 Property ccid_props[] = { + DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +#define TYPE_CCID_BUS "ccid-bus" +OBJECT_DECLARE_SIMPLE_TYPE(CCIDBus, CCID_BUS) + +static const TypeInfo ccid_bus_info = { + .name = TYPE_CCID_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(CCIDBus), +}; + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, + uint8_t *apdu, uint32_t len) +{ + DeviceState *qdev = DEVICE(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + 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) { + DPRINTF(s, D_WARN, "%s: error: unexpected lack of answer\n", __func__); + ccid_report_error_failed(s, ERROR_HW_ERROR); + return; + } + DPRINTF(s, 1, "APDU returned to guest %u (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) +{ + DeviceState *qdev = DEVICE(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + + ccid_on_slot_change(s, false); + ccid_flush_pending_answers(s); + ccid_reset(s); +} + +int ccid_card_ccid_attach(CCIDCardState *card) +{ + DeviceState *qdev = DEVICE(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + + DPRINTF(s, 1, "CCID Attach\n"); + return 0; +} + +void ccid_card_ccid_detach(CCIDCardState *card) +{ + DeviceState *qdev = DEVICE(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + + 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) +{ + DeviceState *qdev = DEVICE(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + + 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) +{ + DeviceState *qdev = DEVICE(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + ccid_flush_pending_answers(s); + ccid_on_slot_change(s, true); +} + +static void ccid_card_unrealize(DeviceState *qdev) +{ + CCIDCardState *card = CCID_CARD(qdev); + CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + + if (ccid_card_inserted(s)) { + ccid_card_card_removed(card); + } + if (cc->unrealize) { + cc->unrealize(card); + } + s->card = NULL; +} + +static void ccid_card_realize(DeviceState *qdev, Error **errp) +{ + CCIDCardState *card = CCID_CARD(qdev); + CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); + USBCCIDState *s = USB_CCID_DEV(dev); + Error *local_err = NULL; + + if (card->slot != 0) { + error_setg(errp, "usb-ccid supports one slot, can't add %d", + card->slot); + return; + } + if (s->card != NULL) { + error_setg(errp, "usb-ccid card already full, not adding"); + return; + } + if (cc->realize) { + cc->realize(card, &local_err); + if (local_err != NULL) { + error_propagate(errp, local_err); + return; + } + } + s->card = card; +} + +static void ccid_realize(USBDevice *dev, Error **errp) +{ + USBCCIDState *s = USB_CCID_DEV(dev); + + usb_desc_create_serial(dev); + usb_desc_init(dev); + qbus_init(&s->bus, sizeof(s->bus), TYPE_CCID_BUS, DEVICE(dev), NULL); + qbus_set_hotplug_handler(BUS(&s->bus), OBJECT(dev)); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, CCID_INT_IN_EP); + s->bulk = usb_ep_get(dev, USB_TOKEN_IN, CCID_BULK_IN_EP); + s->card = NULL; + 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); + s->debug = parse_debug_env("QEMU_CCID_DEBUG", D_VERBOSE, s->debug); +} + +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 int ccid_pre_save(void *opaque) +{ + USBCCIDState *s = opaque; + + s->state_vmstate = s->dev.state; + + return 0; +} + +static const 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 const 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 const 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 const VMStateDescription ccid_vmstate = { + .name = "usb-ccid", + .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.data, 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_UNUSED(1), /* was migration_state */ + 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); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); + + uc->realize = ccid_realize; + 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->unrealize = ccid_unrealize; + dc->desc = "CCID Rev 1.1 smartcard reader"; + dc->vmsd = &ccid_vmstate; + device_class_set_props(dc, ccid_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + hc->unplug = qdev_simple_device_unplug_cb; +} + +static const TypeInfo ccid_info = { + .name = TYPE_USB_CCID_DEV, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBCCIDState), + .class_init = ccid_class_initfn, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } +}; + +static void ccid_card_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->bus_type = TYPE_CCID_BUS; + k->realize = ccid_card_realize; + k->unrealize = ccid_card_unrealize; + device_class_set_props(k, ccid_props); +} + +static const 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_bus_info); + type_register_static(&ccid_card_type_info); + type_register_static(&ccid_info); +} + +type_init(ccid_register_types) diff --git a/hw/usb/dev-storage-bot.c b/hw/usb/dev-storage-bot.c new file mode 100644 index 000000000..b24b3148c --- /dev/null +++ b/hw/usb/dev-storage-bot.c @@ -0,0 +1,63 @@ +/* + * USB Mass Storage Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the LGPL. + */ + +#include "qemu/osdep.h" +#include "qemu/typedefs.h" +#include "qapi/error.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/usb/msd.h" + +static const struct SCSIBusInfo usb_msd_scsi_info_bot = { + .tcq = false, + .max_target = 0, + .max_lun = 15, + + .transfer_data = usb_msd_transfer_data, + .complete = usb_msd_command_complete, + .cancel = usb_msd_request_cancelled, + .load_request = usb_msd_load_request, +}; + +static void usb_msd_bot_realize(USBDevice *dev, Error **errp) +{ + MSDState *s = USB_STORAGE_DEV(dev); + DeviceState *d = DEVICE(dev); + + usb_desc_create_serial(dev); + usb_desc_init(dev); + dev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE); + if (d->hotplugged) { + s->dev.auto_attach = 0; + } + + scsi_bus_init(&s->bus, sizeof(s->bus), DEVICE(dev), &usb_msd_scsi_info_bot); + usb_msd_handle_reset(dev); +} + +static void usb_msd_class_bot_initfn(ObjectClass *klass, void *data) +{ + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_msd_bot_realize; + uc->attached_settable = true; +} + +static const TypeInfo bot_info = { + .name = "usb-bot", + .parent = TYPE_USB_STORAGE, + .class_init = usb_msd_class_bot_initfn, +}; + +static void register_types(void) +{ + type_register_static(&bot_info); +} + +type_init(register_types) diff --git a/hw/usb/dev-storage-classic.c b/hw/usb/dev-storage-classic.c new file mode 100644 index 000000000..00f25bade --- /dev/null +++ b/hw/usb/dev-storage-classic.c @@ -0,0 +1,157 @@ +/* + * USB Mass Storage Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the LGPL. + */ + +#include "qemu/osdep.h" +#include "qemu/typedefs.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/usb/msd.h" +#include "sysemu/sysemu.h" +#include "sysemu/block-backend.h" + +static const struct SCSIBusInfo usb_msd_scsi_info_storage = { + .tcq = false, + .max_target = 0, + .max_lun = 0, + + .transfer_data = usb_msd_transfer_data, + .complete = usb_msd_command_complete, + .cancel = usb_msd_request_cancelled, + .load_request = usb_msd_load_request, +}; + +static void usb_msd_storage_realize(USBDevice *dev, Error **errp) +{ + MSDState *s = USB_STORAGE_DEV(dev); + BlockBackend *blk = s->conf.blk; + SCSIDevice *scsi_dev; + + if (!blk) { + error_setg(errp, "drive property not set"); + return; + } + + if (!blkconf_blocksizes(&s->conf, errp)) { + return; + } + + if (!blkconf_apply_backend_options(&s->conf, !blk_supports_write_perm(blk), + true, errp)) { + return; + } + + /* + * 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. We also need to take another reference so that + * blk_detach_dev() doesn't free blk while we still need it. + * + * The hack is probably a bad idea. + */ + blk_ref(blk); + blk_detach_dev(blk, DEVICE(s)); + s->conf.blk = NULL; + + usb_desc_create_serial(dev); + usb_desc_init(dev); + dev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE); + scsi_bus_init(&s->bus, sizeof(s->bus), DEVICE(dev), + &usb_msd_scsi_info_storage); + scsi_dev = scsi_bus_legacy_add_drive(&s->bus, blk, 0, !!s->removable, + s->conf.bootindex, s->conf.share_rw, + s->conf.rerror, s->conf.werror, + dev->serial, + errp); + blk_unref(blk); + if (!scsi_dev) { + return; + } + usb_msd_handle_reset(dev); + s->scsi_dev = scsi_dev; +} + +static Property msd_properties[] = { + DEFINE_BLOCK_PROPERTIES(MSDState, conf), + DEFINE_BLOCK_ERROR_PROPERTIES(MSDState, conf), + DEFINE_PROP_BOOL("removable", MSDState, removable, false), + DEFINE_PROP_BOOL("commandlog", MSDState, commandlog, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_msd_class_storage_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_msd_storage_realize; + device_class_set_props(dc, msd_properties); +} + +static void usb_msd_get_bootindex(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + USBDevice *dev = USB_DEVICE(obj); + MSDState *s = USB_STORAGE_DEV(dev); + + visit_type_int32(v, name, &s->conf.bootindex, errp); +} + +static void usb_msd_set_bootindex(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + USBDevice *dev = USB_DEVICE(obj); + MSDState *s = USB_STORAGE_DEV(dev); + int32_t boot_index; + Error *local_err = NULL; + + if (!visit_type_int32(v, name, &boot_index, errp)) { + return; + } + /* check whether bootindex is present in fw_boot_order list */ + check_boot_index(boot_index, &local_err); + if (local_err) { + goto out; + } + /* change bootindex to a new one */ + s->conf.bootindex = boot_index; + + if (s->scsi_dev) { + object_property_set_int(OBJECT(s->scsi_dev), "bootindex", boot_index, + &error_abort); + } + +out: + error_propagate(errp, local_err); +} + +static void usb_msd_instance_init(Object *obj) +{ + object_property_add(obj, "bootindex", "int32", + usb_msd_get_bootindex, + usb_msd_set_bootindex, NULL, NULL); + object_property_set_int(obj, "bootindex", -1, NULL); +} + +static const TypeInfo msd_info = { + .name = "usb-storage", + .parent = TYPE_USB_STORAGE, + .class_init = usb_msd_class_storage_initfn, + .instance_init = usb_msd_instance_init, +}; + +static void register_types(void) +{ + type_register_static(&msd_info); +} + +type_init(register_types) diff --git a/hw/usb/dev-storage.c b/hw/usb/dev-storage.c new file mode 100644 index 000000000..dca62d544 --- /dev/null +++ b/hw/usb/dev-storage.c @@ -0,0 +1,589 @@ +/* + * USB Mass Storage Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the LGPL. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "qemu/option.h" +#include "qemu/config-file.h" +#include "hw/usb.h" +#include "hw/usb/msd.h" +#include "desc.h" +#include "hw/qdev-properties.h" +#include "hw/scsi/scsi.h" +#include "migration/vmstate.h" +#include "qemu/cutils.h" +#include "qom/object.h" +#include "trace.h" + +/* USB requests. */ +#define MassStorageReset 0xff +#define GetMaxLun 0xfe + +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, + STR_CONFIG_SUPER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [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)", + [STR_CONFIG_SUPER] = "Super speed config (usb 3.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 = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .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 = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .nif = 1, + .ifs = &desc_iface_high, + }, + }, +}; + +static const USBDescIface desc_iface_super = { + .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 = 1024, + .bMaxBurst = 15, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 1024, + .bMaxBurst = 15, + }, + } +}; + +static const USBDescDevice desc_device_super = { + .bcdUSB = 0x0300, + .bMaxPacketSize0 = 9, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_SUPER, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .nif = 1, + .ifs = &desc_iface_super, + }, + }, +}; + +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, + .super = &desc_device_super, + .str = desc_strings, +}; + +static void usb_msd_copy_data(MSDState *s, USBPacket *p) +{ + uint32_t len; + len = p->iov.size - p->actual_length; + if (len > s->scsi_len) + len = s->scsi_len; + usb_packet_copy(p, scsi_req_get_buf(s->req) + s->scsi_off, len); + s->scsi_len -= len; + s->scsi_off += len; + if (len > s->data_len) { + len = s->data_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; + + trace_usb_msd_send_status(s->csw.status, le32_to_cpu(s->csw.tag), + p->iov.size); + + assert(s->csw.sig == cpu_to_le32(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_packet_complete(MSDState *s) +{ + USBPacket *p = s->packet; + + /* Set s->packet to NULL before calling usb_packet_complete + because another request may be issued before + usb_packet_complete returns. */ + trace_usb_msd_packet_complete(); + s->packet = NULL; + usb_packet_complete(&s->dev, p); +} + +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_off = 0; + if (p) { + usb_msd_copy_data(s, p); + p = s->packet; + if (p && p->actual_length == p->iov.size) { + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + usb_msd_packet_complete(s); + } + } +} + +void usb_msd_command_complete(SCSIRequest *req, size_t resid) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + USBPacket *p = s->packet; + + trace_usb_msd_cmd_complete(req->status, req->tag); + + s->csw.sig = cpu_to_le32(0x53425355); + s->csw.tag = cpu_to_le32(req->tag); + s->csw.residue = cpu_to_le32(s->data_len); + s->csw.status = req->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->mode == USB_MSDM_CSW) { + usb_msd_send_status(s, p); + s->mode = USB_MSDM_CBW; + } else { + if (s->data_len) { + int len = (p->iov.size - p->actual_length); + usb_packet_skip(p, len); + if (len > s->data_len) { + len = s->data_len; + } + s->data_len -= len; + } + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + } + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + usb_msd_packet_complete(s); + } else if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + scsi_req_unref(req); + s->req = NULL; +} + +void usb_msd_request_cancelled(SCSIRequest *req) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + + trace_usb_msd_cmd_cancel(req->tag); + + if (req == s->req) { + s->csw.sig = cpu_to_le32(0x53425355); + s->csw.tag = cpu_to_le32(req->tag); + s->csw.status = 1; /* error */ + + scsi_req_unref(s->req); + s->req = NULL; + s->scsi_len = 0; + } +} + +void usb_msd_handle_reset(USBDevice *dev) +{ + MSDState *s = (MSDState *)dev; + + trace_usb_msd_reset(); + if (s->req) { + scsi_req_cancel(s->req); + } + assert(s->req == NULL); + + if (s->packet) { + s->packet->status = USB_RET_STALL; + usb_msd_packet_complete(s); + } + + memset(&s->csw, 0, sizeof(s->csw)); + s->mode = USB_MSDM_CBW; +} + +static void usb_msd_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + MSDState *s = (MSDState *)dev; + SCSIDevice *scsi_dev; + int ret, maxlun; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch (request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + break; + /* Class specific requests. */ + case ClassInterfaceOutRequest | MassStorageReset: + /* Reset state ready for the next CBW. */ + s->mode = USB_MSDM_CBW; + break; + case ClassInterfaceRequest | GetMaxLun: + maxlun = 0; + for (;;) { + scsi_dev = scsi_device_find(&s->bus, 0, 0, maxlun+1); + if (scsi_dev == NULL) { + break; + } + if (scsi_dev->lun != maxlun+1) { + break; + } + maxlun++; + } + trace_usb_msd_maxlun(maxlun); + data[0] = maxlun; + p->actual_length = 1; + break; + default: + p->status = USB_RET_STALL; + break; + } +} + +static void usb_msd_cancel_io(USBDevice *dev, USBPacket *p) +{ + MSDState *s = USB_STORAGE_DEV(dev); + + assert(s->packet == p); + s->packet = NULL; + + if (s->req) { + scsi_req_cancel(s->req); + } +} + +static void usb_msd_handle_data(USBDevice *dev, USBPacket *p) +{ + MSDState *s = (MSDState *)dev; + uint32_t tag; + struct usb_msd_cbw cbw; + uint8_t devep = p->ep->nr; + SCSIDevice *scsi_dev; + uint32_t len; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) + goto fail; + + switch (s->mode) { + case USB_MSDM_CBW: + if (p->iov.size != 31) { + error_report("usb-msd: Bad CBW size"); + goto fail; + } + usb_packet_copy(p, &cbw, 31); + if (le32_to_cpu(cbw.sig) != 0x43425355) { + error_report("usb-msd: Bad signature %08x", + le32_to_cpu(cbw.sig)); + goto fail; + } + scsi_dev = scsi_device_find(&s->bus, 0, 0, cbw.lun); + if (scsi_dev == NULL) { + error_report("usb-msd: Bad LUN %d", 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; + } + trace_usb_msd_cmd_submit(cbw.lun, tag, cbw.flags, + cbw.cmd_len, s->data_len); + assert(le32_to_cpu(s->csw.residue) == 0); + s->scsi_len = 0; + s->req = scsi_req_new(scsi_dev, tag, cbw.lun, cbw.cmd, NULL); + if (s->commandlog) { + scsi_req_print(s->req); + } + len = scsi_req_enqueue(s->req); + if (len) { + scsi_req_continue(s->req); + } + break; + + case USB_MSDM_DATAOUT: + trace_usb_msd_data_out(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 (le32_to_cpu(s->csw.residue)) { + int len = p->iov.size - p->actual_length; + if (len) { + usb_packet_skip(p, len); + if (len > s->data_len) { + len = s->data_len; + } + s->data_len -= len; + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + } + } + if (p->actual_length < p->iov.size) { + trace_usb_msd_packet_async(); + s->packet = p; + p->status = USB_RET_ASYNC; + } + break; + + default: + 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. */ + trace_usb_msd_packet_async(); + s->packet = p; + p->status = USB_RET_ASYNC; + break; + + case USB_MSDM_CSW: + if (p->iov.size < 13) { + goto fail; + } + + if (s->req) { + /* still in flight */ + trace_usb_msd_packet_async(); + s->packet = p; + p->status = USB_RET_ASYNC; + } else { + usb_msd_send_status(s, p); + s->mode = USB_MSDM_CBW; + } + break; + + case USB_MSDM_DATAIN: + trace_usb_msd_data_in(p->iov.size, s->data_len, s->scsi_len); + if (s->scsi_len) { + usb_msd_copy_data(s, p); + } + if (le32_to_cpu(s->csw.residue)) { + int len = p->iov.size - p->actual_length; + if (len) { + usb_packet_skip(p, len); + if (len > s->data_len) { + len = s->data_len; + } + s->data_len -= len; + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + } + } + if (p->actual_length < p->iov.size && s->mode == USB_MSDM_DATAIN) { + trace_usb_msd_packet_async(); + s->packet = p; + p->status = USB_RET_ASYNC; + } + break; + + default: + goto fail; + } + break; + + default: + fail: + p->status = USB_RET_STALL; + break; + } +} + +void *usb_msd_load_request(QEMUFile *f, SCSIRequest *req) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + + /* nothing to load, just store req in our state struct */ + assert(s->req == NULL); + scsi_req_ref(req); + s->req = req; + return NULL; +} + +static const VMStateDescription vmstate_usb_msd = { + .name = "usb-storage", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(dev, MSDState), + VMSTATE_UINT32(mode, MSDState), + VMSTATE_UINT32(scsi_len, MSDState), + VMSTATE_UINT32(scsi_off, MSDState), + VMSTATE_UINT32(data_len, MSDState), + VMSTATE_UINT32(csw.sig, MSDState), + VMSTATE_UINT32(csw.tag, MSDState), + VMSTATE_UINT32(csw.residue, MSDState), + VMSTATE_UINT8(csw.status, MSDState), + VMSTATE_END_OF_LIST() + } +}; + +static void usb_msd_class_initfn_common(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + 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; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->fw_name = "storage"; + dc->vmsd = &vmstate_usb_msd; +} + +static const TypeInfo usb_storage_dev_type_info = { + .name = TYPE_USB_STORAGE, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(MSDState), + .abstract = true, + .class_init = usb_msd_class_initfn_common, +}; + +static void usb_msd_register_types(void) +{ + type_register_static(&usb_storage_dev_type_info); +} + +type_init(usb_msd_register_types) diff --git a/hw/usb/dev-uas.c b/hw/usb/dev-uas.c new file mode 100644 index 000000000..599d6b52a --- /dev/null +++ b/hw/usb/dev-uas.c @@ -0,0 +1,991 @@ +/* + * UAS (USB Attached SCSI) emulation + * + * Copyright Red Hat, Inc. 2012 + * + * Author: Gerd Hoffmann <kraxel@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/option.h" +#include "qemu/config-file.h" +#include "trace.h" +#include "qemu/error-report.h" +#include "qemu/main-loop.h" +#include "qemu/module.h" +#include "qemu/log.h" + +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "hw/qdev-properties.h" +#include "hw/scsi/scsi.h" +#include "scsi/constants.h" +#include "qom/object.h" + +/* --------------------------------------------------------------------- */ + +#define UAS_UI_COMMAND 0x01 +#define UAS_UI_SENSE 0x03 +#define UAS_UI_RESPONSE 0x04 +#define UAS_UI_TASK_MGMT 0x05 +#define UAS_UI_READ_READY 0x06 +#define UAS_UI_WRITE_READY 0x07 + +#define UAS_RC_TMF_COMPLETE 0x00 +#define UAS_RC_INVALID_INFO_UNIT 0x02 +#define UAS_RC_TMF_NOT_SUPPORTED 0x04 +#define UAS_RC_TMF_FAILED 0x05 +#define UAS_RC_TMF_SUCCEEDED 0x08 +#define UAS_RC_INCORRECT_LUN 0x09 +#define UAS_RC_OVERLAPPED_TAG 0x0a + +#define UAS_TMF_ABORT_TASK 0x01 +#define UAS_TMF_ABORT_TASK_SET 0x02 +#define UAS_TMF_CLEAR_TASK_SET 0x04 +#define UAS_TMF_LOGICAL_UNIT_RESET 0x08 +#define UAS_TMF_I_T_NEXUS_RESET 0x10 +#define UAS_TMF_CLEAR_ACA 0x40 +#define UAS_TMF_QUERY_TASK 0x80 +#define UAS_TMF_QUERY_TASK_SET 0x81 +#define UAS_TMF_QUERY_ASYNC_EVENT 0x82 + +#define UAS_PIPE_ID_COMMAND 0x01 +#define UAS_PIPE_ID_STATUS 0x02 +#define UAS_PIPE_ID_DATA_IN 0x03 +#define UAS_PIPE_ID_DATA_OUT 0x04 + +typedef struct { + uint8_t id; + uint8_t reserved; + uint16_t tag; +} QEMU_PACKED uas_iu_header; + +typedef struct { + uint8_t prio_taskattr; /* 6:3 priority, 2:0 task attribute */ + uint8_t reserved_1; + uint8_t add_cdb_length; /* 7:2 additional adb length (dwords) */ + uint8_t reserved_2; + uint64_t lun; + uint8_t cdb[16]; + uint8_t add_cdb[1]; /* not supported by QEMU */ +} QEMU_PACKED uas_iu_command; + +typedef struct { + uint16_t status_qualifier; + uint8_t status; + uint8_t reserved[7]; + uint16_t sense_length; + uint8_t sense_data[18]; +} QEMU_PACKED uas_iu_sense; + +typedef struct { + uint8_t add_response_info[3]; + uint8_t response_code; +} QEMU_PACKED uas_iu_response; + +typedef struct { + uint8_t function; + uint8_t reserved; + uint16_t task_tag; + uint64_t lun; +} QEMU_PACKED uas_iu_task_mgmt; + +typedef struct { + uas_iu_header hdr; + union { + uas_iu_command command; + uas_iu_sense sense; + uas_iu_task_mgmt task; + uas_iu_response response; + }; +} QEMU_PACKED uas_iu; + +/* --------------------------------------------------------------------- */ + +#define UAS_STREAM_BM_ATTR 4 +#define UAS_MAX_STREAMS (1 << UAS_STREAM_BM_ATTR) + +typedef struct UASDevice UASDevice; +typedef struct UASRequest UASRequest; +typedef struct UASStatus UASStatus; + +struct UASDevice { + USBDevice dev; + SCSIBus bus; + QEMUBH *status_bh; + QTAILQ_HEAD(, UASStatus) results; + QTAILQ_HEAD(, UASRequest) requests; + + /* properties */ + uint32_t requestlog; + + /* usb 2.0 only */ + USBPacket *status2; + UASRequest *datain2; + UASRequest *dataout2; + + /* usb 3.0 only */ + USBPacket *data3[UAS_MAX_STREAMS + 1]; + USBPacket *status3[UAS_MAX_STREAMS + 1]; +}; + +#define TYPE_USB_UAS "usb-uas" +OBJECT_DECLARE_SIMPLE_TYPE(UASDevice, USB_UAS) + +struct UASRequest { + uint16_t tag; + uint64_t lun; + UASDevice *uas; + SCSIDevice *dev; + SCSIRequest *req; + USBPacket *data; + bool data_async; + bool active; + bool complete; + uint32_t buf_off; + uint32_t buf_size; + uint32_t data_off; + uint32_t data_size; + QTAILQ_ENTRY(UASRequest) next; +}; + +struct UASStatus { + uint32_t stream; + uas_iu status; + uint32_t length; + QTAILQ_ENTRY(UASStatus) next; +}; + +/* --------------------------------------------------------------------- */ + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_CONFIG_HIGH, + STR_CONFIG_SUPER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [STR_PRODUCT] = "USB Attached SCSI HBA", + [STR_SERIALNUMBER] = "27842", + [STR_CONFIG_HIGH] = "High speed config (usb 2.0)", + [STR_CONFIG_SUPER] = "Super speed config (usb 3.0)", +}; + +static const USBDescIface desc_iface_high = { + .bInterfaceNumber = 0, + .bNumEndpoints = 4, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = 0x06, /* SCSI */ + .bInterfaceProtocol = 0x62, /* UAS */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_COMMAND, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_COMMAND, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_STATUS, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_STATUS, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_DATA_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_DATA_IN, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_DATA_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 512, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_DATA_OUT, + 0x00, /* u8 bReserved */ + }, + }, + } +}; + +static const USBDescIface desc_iface_super = { + .bInterfaceNumber = 0, + .bNumEndpoints = 4, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = 0x06, /* SCSI */ + .bInterfaceProtocol = 0x62, /* UAS */ + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_COMMAND, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 1024, + .bMaxBurst = 15, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_COMMAND, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_STATUS, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 1024, + .bMaxBurst = 15, + .bmAttributes_super = UAS_STREAM_BM_ATTR, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_STATUS, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_IN | UAS_PIPE_ID_DATA_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 1024, + .bMaxBurst = 15, + .bmAttributes_super = UAS_STREAM_BM_ATTR, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_DATA_IN, + 0x00, /* u8 bReserved */ + }, + },{ + .bEndpointAddress = USB_DIR_OUT | UAS_PIPE_ID_DATA_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 1024, + .bMaxBurst = 15, + .bmAttributes_super = UAS_STREAM_BM_ATTR, + .extra = (uint8_t[]) { + 0x04, /* u8 bLength */ + 0x24, /* u8 bDescriptorType */ + UAS_PIPE_ID_DATA_OUT, + 0x00, /* u8 bReserved */ + }, + }, + } +}; + +static const USBDescDevice desc_device_high = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_HIGH, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .nif = 1, + .ifs = &desc_iface_high, + }, + }, +}; + +static const USBDescDevice desc_device_super = { + .bcdUSB = 0x0300, + .bMaxPacketSize0 = 9, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_SUPER, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, + .nif = 1, + .ifs = &desc_iface_super, + }, + }, +}; + +static const USBDesc desc = { + .id = { + .idVendor = 0x46f4, /* CRC16() of "QEMU" */ + .idProduct = 0x0003, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .high = &desc_device_high, + .super = &desc_device_super, + .str = desc_strings, +}; + +/* --------------------------------------------------------------------- */ + +static bool uas_using_streams(UASDevice *uas) +{ + return uas->dev.speed == USB_SPEED_SUPER; +} + +/* --------------------------------------------------------------------- */ + +static UASStatus *usb_uas_alloc_status(UASDevice *uas, uint8_t id, uint16_t tag) +{ + UASStatus *st = g_new0(UASStatus, 1); + + st->status.hdr.id = id; + st->status.hdr.tag = cpu_to_be16(tag); + st->length = sizeof(uas_iu_header); + if (uas_using_streams(uas)) { + st->stream = tag; + } + return st; +} + +static void usb_uas_send_status_bh(void *opaque) +{ + UASDevice *uas = opaque; + UASStatus *st; + USBPacket *p; + + while ((st = QTAILQ_FIRST(&uas->results)) != NULL) { + if (uas_using_streams(uas)) { + p = uas->status3[st->stream]; + uas->status3[st->stream] = NULL; + } else { + p = uas->status2; + uas->status2 = NULL; + } + if (p == NULL) { + break; + } + + usb_packet_copy(p, &st->status, st->length); + QTAILQ_REMOVE(&uas->results, st, next); + g_free(st); + + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + usb_packet_complete(&uas->dev, p); + } +} + +static void usb_uas_queue_status(UASDevice *uas, UASStatus *st, int length) +{ + USBPacket *p = uas_using_streams(uas) ? + uas->status3[st->stream] : uas->status2; + + st->length += length; + QTAILQ_INSERT_TAIL(&uas->results, st, next); + if (p) { + /* + * Just schedule bh make sure any in-flight data transaction + * is finished before completing (sending) the status packet. + */ + qemu_bh_schedule(uas->status_bh); + } else { + USBEndpoint *ep = usb_ep_get(&uas->dev, USB_TOKEN_IN, + UAS_PIPE_ID_STATUS); + usb_wakeup(ep, st->stream); + } +} + +static void usb_uas_queue_response(UASDevice *uas, uint16_t tag, uint8_t code) +{ + UASStatus *st = usb_uas_alloc_status(uas, UAS_UI_RESPONSE, tag); + + trace_usb_uas_response(uas->dev.addr, tag, code); + st->status.response.response_code = code; + usb_uas_queue_status(uas, st, sizeof(uas_iu_response)); +} + +static void usb_uas_queue_sense(UASRequest *req, uint8_t status) +{ + UASStatus *st = usb_uas_alloc_status(req->uas, UAS_UI_SENSE, req->tag); + int len, slen = 0; + + trace_usb_uas_sense(req->uas->dev.addr, req->tag, status); + st->status.sense.status = status; + st->status.sense.status_qualifier = cpu_to_be16(0); + if (status != GOOD) { + slen = scsi_req_get_sense(req->req, st->status.sense.sense_data, + sizeof(st->status.sense.sense_data)); + st->status.sense.sense_length = cpu_to_be16(slen); + } + len = sizeof(uas_iu_sense) - sizeof(st->status.sense.sense_data) + slen; + usb_uas_queue_status(req->uas, st, len); +} + +static void usb_uas_queue_fake_sense(UASDevice *uas, uint16_t tag, + struct SCSISense sense) +{ + UASStatus *st = usb_uas_alloc_status(uas, UAS_UI_SENSE, tag); + int len, slen = 0; + + st->status.sense.status = CHECK_CONDITION; + st->status.sense.status_qualifier = cpu_to_be16(0); + st->status.sense.sense_data[0] = 0x70; + st->status.sense.sense_data[2] = sense.key; + st->status.sense.sense_data[7] = 10; + st->status.sense.sense_data[12] = sense.asc; + st->status.sense.sense_data[13] = sense.ascq; + slen = 18; + len = sizeof(uas_iu_sense) - sizeof(st->status.sense.sense_data) + slen; + usb_uas_queue_status(uas, st, len); +} + +static void usb_uas_queue_read_ready(UASRequest *req) +{ + UASStatus *st = usb_uas_alloc_status(req->uas, UAS_UI_READ_READY, + req->tag); + + trace_usb_uas_read_ready(req->uas->dev.addr, req->tag); + usb_uas_queue_status(req->uas, st, 0); +} + +static void usb_uas_queue_write_ready(UASRequest *req) +{ + UASStatus *st = usb_uas_alloc_status(req->uas, UAS_UI_WRITE_READY, + req->tag); + + trace_usb_uas_write_ready(req->uas->dev.addr, req->tag); + usb_uas_queue_status(req->uas, st, 0); +} + +/* --------------------------------------------------------------------- */ + +static int usb_uas_get_lun(uint64_t lun64) +{ + return (lun64 >> 48) & 0xff; +} + +static SCSIDevice *usb_uas_get_dev(UASDevice *uas, uint64_t lun64) +{ + if ((lun64 >> 56) != 0x00) { + return NULL; + } + return scsi_device_find(&uas->bus, 0, 0, usb_uas_get_lun(lun64)); +} + +static void usb_uas_complete_data_packet(UASRequest *req) +{ + USBPacket *p; + + if (!req->data_async) { + return; + } + p = req->data; + req->data = NULL; + req->data_async = false; + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + usb_packet_complete(&req->uas->dev, p); +} + +static void usb_uas_copy_data(UASRequest *req) +{ + uint32_t length; + + length = MIN(req->buf_size - req->buf_off, + req->data->iov.size - req->data->actual_length); + trace_usb_uas_xfer_data(req->uas->dev.addr, req->tag, length, + req->data->actual_length, req->data->iov.size, + req->buf_off, req->buf_size); + usb_packet_copy(req->data, scsi_req_get_buf(req->req) + req->buf_off, + length); + req->buf_off += length; + req->data_off += length; + + if (req->data->actual_length == req->data->iov.size) { + usb_uas_complete_data_packet(req); + } + if (req->buf_size && req->buf_off == req->buf_size) { + req->buf_off = 0; + req->buf_size = 0; + scsi_req_continue(req->req); + } +} + +static void usb_uas_start_next_transfer(UASDevice *uas) +{ + UASRequest *req; + + if (uas_using_streams(uas)) { + return; + } + + QTAILQ_FOREACH(req, &uas->requests, next) { + if (req->active || req->complete) { + continue; + } + if (req->req->cmd.mode == SCSI_XFER_FROM_DEV && uas->datain2 == NULL) { + uas->datain2 = req; + usb_uas_queue_read_ready(req); + req->active = true; + return; + } + if (req->req->cmd.mode == SCSI_XFER_TO_DEV && uas->dataout2 == NULL) { + uas->dataout2 = req; + usb_uas_queue_write_ready(req); + req->active = true; + return; + } + } +} + +static UASRequest *usb_uas_alloc_request(UASDevice *uas, uas_iu *iu) +{ + UASRequest *req; + + req = g_new0(UASRequest, 1); + req->uas = uas; + req->tag = be16_to_cpu(iu->hdr.tag); + req->lun = be64_to_cpu(iu->command.lun); + req->dev = usb_uas_get_dev(req->uas, req->lun); + return req; +} + +static void usb_uas_scsi_free_request(SCSIBus *bus, void *priv) +{ + UASRequest *req = priv; + UASDevice *uas = req->uas; + + if (req == uas->datain2) { + uas->datain2 = NULL; + } + if (req == uas->dataout2) { + uas->dataout2 = NULL; + } + QTAILQ_REMOVE(&uas->requests, req, next); + g_free(req); + usb_uas_start_next_transfer(uas); +} + +static UASRequest *usb_uas_find_request(UASDevice *uas, uint16_t tag) +{ + UASRequest *req; + + QTAILQ_FOREACH(req, &uas->requests, next) { + if (req->tag == tag) { + return req; + } + } + return NULL; +} + +static void usb_uas_scsi_transfer_data(SCSIRequest *r, uint32_t len) +{ + UASRequest *req = r->hba_private; + + trace_usb_uas_scsi_data(req->uas->dev.addr, req->tag, len); + req->buf_off = 0; + req->buf_size = len; + if (req->data) { + usb_uas_copy_data(req); + } else { + usb_uas_start_next_transfer(req->uas); + } +} + +static void usb_uas_scsi_command_complete(SCSIRequest *r, size_t resid) +{ + UASRequest *req = r->hba_private; + + trace_usb_uas_scsi_complete(req->uas->dev.addr, req->tag, r->status, resid); + req->complete = true; + if (req->data) { + usb_uas_complete_data_packet(req); + } + usb_uas_queue_sense(req, r->status); + scsi_req_unref(req->req); +} + +static void usb_uas_scsi_request_cancelled(SCSIRequest *r) +{ + UASRequest *req = r->hba_private; + + /* FIXME: queue notification to status pipe? */ + scsi_req_unref(req->req); +} + +static const struct SCSIBusInfo usb_uas_scsi_info = { + .tcq = true, + .max_target = 0, + .max_lun = 255, + + .transfer_data = usb_uas_scsi_transfer_data, + .complete = usb_uas_scsi_command_complete, + .cancel = usb_uas_scsi_request_cancelled, + .free_request = usb_uas_scsi_free_request, +}; + +/* --------------------------------------------------------------------- */ + +static void usb_uas_handle_reset(USBDevice *dev) +{ + UASDevice *uas = USB_UAS(dev); + UASRequest *req, *nreq; + UASStatus *st, *nst; + + trace_usb_uas_reset(dev->addr); + QTAILQ_FOREACH_SAFE(req, &uas->requests, next, nreq) { + scsi_req_cancel(req->req); + } + QTAILQ_FOREACH_SAFE(st, &uas->results, next, nst) { + QTAILQ_REMOVE(&uas->results, st, next); + g_free(st); + } +} + +static void usb_uas_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + error_report("%s: unhandled control request (req 0x%x, val 0x%x, idx 0x%x", + __func__, request, value, index); + p->status = USB_RET_STALL; +} + +static void usb_uas_cancel_io(USBDevice *dev, USBPacket *p) +{ + UASDevice *uas = USB_UAS(dev); + UASRequest *req, *nreq; + int i; + + if (uas->status2 == p) { + uas->status2 = NULL; + qemu_bh_cancel(uas->status_bh); + return; + } + if (uas_using_streams(uas)) { + for (i = 0; i <= UAS_MAX_STREAMS; i++) { + if (uas->status3[i] == p) { + uas->status3[i] = NULL; + return; + } + if (uas->data3[i] == p) { + uas->data3[i] = NULL; + return; + } + } + } + QTAILQ_FOREACH_SAFE(req, &uas->requests, next, nreq) { + if (req->data == p) { + req->data = NULL; + return; + } + } + assert(!"canceled usb packet not found"); +} + +static void usb_uas_command(UASDevice *uas, uas_iu *iu) +{ + UASRequest *req; + uint32_t len; + uint16_t tag = be16_to_cpu(iu->hdr.tag); + + if (iu->command.add_cdb_length > 0) { + qemu_log_mask(LOG_UNIMP, "additional adb length not yet supported\n"); + goto unsupported_len; + } + + if (uas_using_streams(uas) && tag > UAS_MAX_STREAMS) { + goto invalid_tag; + } + req = usb_uas_find_request(uas, tag); + if (req) { + goto overlapped_tag; + } + req = usb_uas_alloc_request(uas, iu); + if (req->dev == NULL) { + goto bad_target; + } + + trace_usb_uas_command(uas->dev.addr, req->tag, + usb_uas_get_lun(req->lun), + req->lun >> 32, req->lun & 0xffffffff); + QTAILQ_INSERT_TAIL(&uas->requests, req, next); + if (uas_using_streams(uas) && uas->data3[req->tag] != NULL) { + req->data = uas->data3[req->tag]; + req->data_async = true; + uas->data3[req->tag] = NULL; + } + + req->req = scsi_req_new(req->dev, req->tag, + usb_uas_get_lun(req->lun), + iu->command.cdb, req); + if (uas->requestlog) { + scsi_req_print(req->req); + } + len = scsi_req_enqueue(req->req); + if (len) { + req->data_size = len; + scsi_req_continue(req->req); + } + return; + +unsupported_len: + usb_uas_queue_fake_sense(uas, tag, sense_code_INVALID_PARAM_VALUE); + return; + +invalid_tag: + usb_uas_queue_fake_sense(uas, tag, sense_code_INVALID_TAG); + return; + +overlapped_tag: + usb_uas_queue_fake_sense(uas, tag, sense_code_OVERLAPPED_COMMANDS); + return; + +bad_target: + usb_uas_queue_fake_sense(uas, tag, sense_code_LUN_NOT_SUPPORTED); + g_free(req); +} + +static void usb_uas_task(UASDevice *uas, uas_iu *iu) +{ + uint16_t tag = be16_to_cpu(iu->hdr.tag); + uint64_t lun64 = be64_to_cpu(iu->task.lun); + SCSIDevice *dev = usb_uas_get_dev(uas, lun64); + int lun = usb_uas_get_lun(lun64); + UASRequest *req; + uint16_t task_tag; + + if (uas_using_streams(uas) && tag > UAS_MAX_STREAMS) { + goto invalid_tag; + } + req = usb_uas_find_request(uas, be16_to_cpu(iu->hdr.tag)); + if (req) { + goto overlapped_tag; + } + if (dev == NULL) { + goto incorrect_lun; + } + + switch (iu->task.function) { + case UAS_TMF_ABORT_TASK: + task_tag = be16_to_cpu(iu->task.task_tag); + trace_usb_uas_tmf_abort_task(uas->dev.addr, tag, task_tag); + req = usb_uas_find_request(uas, task_tag); + if (req && req->dev == dev) { + scsi_req_cancel(req->req); + } + usb_uas_queue_response(uas, tag, UAS_RC_TMF_COMPLETE); + break; + + case UAS_TMF_LOGICAL_UNIT_RESET: + trace_usb_uas_tmf_logical_unit_reset(uas->dev.addr, tag, lun); + qdev_reset_all(&dev->qdev); + usb_uas_queue_response(uas, tag, UAS_RC_TMF_COMPLETE); + break; + + default: + trace_usb_uas_tmf_unsupported(uas->dev.addr, tag, iu->task.function); + usb_uas_queue_response(uas, tag, UAS_RC_TMF_NOT_SUPPORTED); + break; + } + return; + +invalid_tag: + usb_uas_queue_response(uas, tag, UAS_RC_INVALID_INFO_UNIT); + return; + +overlapped_tag: + usb_uas_queue_response(uas, req->tag, UAS_RC_OVERLAPPED_TAG); + return; + +incorrect_lun: + usb_uas_queue_response(uas, tag, UAS_RC_INCORRECT_LUN); +} + +static void usb_uas_handle_data(USBDevice *dev, USBPacket *p) +{ + UASDevice *uas = USB_UAS(dev); + uas_iu iu; + UASStatus *st; + UASRequest *req; + int length; + + switch (p->ep->nr) { + case UAS_PIPE_ID_COMMAND: + length = MIN(sizeof(iu), p->iov.size); + usb_packet_copy(p, &iu, length); + switch (iu.hdr.id) { + case UAS_UI_COMMAND: + usb_uas_command(uas, &iu); + break; + case UAS_UI_TASK_MGMT: + usb_uas_task(uas, &iu); + break; + default: + error_report("%s: unknown command iu: id 0x%x", + __func__, iu.hdr.id); + p->status = USB_RET_STALL; + break; + } + break; + case UAS_PIPE_ID_STATUS: + if (p->stream > UAS_MAX_STREAMS) { + goto err_stream; + } + if (p->stream) { + QTAILQ_FOREACH(st, &uas->results, next) { + if (st->stream == p->stream) { + break; + } + } + if (st == NULL) { + assert(uas->status3[p->stream] == NULL); + uas->status3[p->stream] = p; + p->status = USB_RET_ASYNC; + break; + } + } else { + st = QTAILQ_FIRST(&uas->results); + if (st == NULL) { + assert(uas->status2 == NULL); + uas->status2 = p; + p->status = USB_RET_ASYNC; + break; + } + } + usb_packet_copy(p, &st->status, st->length); + QTAILQ_REMOVE(&uas->results, st, next); + g_free(st); + break; + case UAS_PIPE_ID_DATA_IN: + case UAS_PIPE_ID_DATA_OUT: + if (p->stream > UAS_MAX_STREAMS) { + goto err_stream; + } + if (p->stream) { + req = usb_uas_find_request(uas, p->stream); + } else { + req = (p->ep->nr == UAS_PIPE_ID_DATA_IN) + ? uas->datain2 : uas->dataout2; + } + if (req == NULL) { + if (p->stream) { + assert(uas->data3[p->stream] == NULL); + uas->data3[p->stream] = p; + p->status = USB_RET_ASYNC; + break; + } else { + error_report("%s: no inflight request", __func__); + p->status = USB_RET_STALL; + break; + } + } + scsi_req_ref(req->req); + req->data = p; + usb_uas_copy_data(req); + if (p->actual_length == p->iov.size || req->complete) { + req->data = NULL; + } else { + req->data_async = true; + p->status = USB_RET_ASYNC; + } + scsi_req_unref(req->req); + usb_uas_start_next_transfer(uas); + break; + default: + error_report("%s: invalid endpoint %d", __func__, p->ep->nr); + p->status = USB_RET_STALL; + break; + } + +err_stream: + error_report("%s: invalid stream %d", __func__, p->stream); + p->status = USB_RET_STALL; + return; +} + +static void usb_uas_unrealize(USBDevice *dev) +{ + UASDevice *uas = USB_UAS(dev); + + qemu_bh_delete(uas->status_bh); +} + +static void usb_uas_realize(USBDevice *dev, Error **errp) +{ + UASDevice *uas = USB_UAS(dev); + DeviceState *d = DEVICE(dev); + + usb_desc_create_serial(dev); + usb_desc_init(dev); + if (d->hotplugged) { + uas->dev.auto_attach = 0; + } + + QTAILQ_INIT(&uas->results); + QTAILQ_INIT(&uas->requests); + uas->status_bh = qemu_bh_new(usb_uas_send_status_bh, uas); + + dev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE); + scsi_bus_init(&uas->bus, sizeof(uas->bus), DEVICE(dev), &usb_uas_scsi_info); +} + +static const VMStateDescription vmstate_usb_uas = { + .name = "usb-uas", + .unmigratable = 1, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(dev, UASDevice), + VMSTATE_END_OF_LIST() + } +}; + +static Property uas_properties[] = { + DEFINE_PROP_UINT32("log-scsi-req", UASDevice, requestlog, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_uas_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_uas_realize; + uc->product_desc = desc_strings[STR_PRODUCT]; + uc->usb_desc = &desc; + uc->cancel_packet = usb_uas_cancel_io; + uc->handle_attach = usb_desc_attach; + uc->handle_reset = usb_uas_handle_reset; + uc->handle_control = usb_uas_handle_control; + uc->handle_data = usb_uas_handle_data; + uc->unrealize = usb_uas_unrealize; + uc->attached_settable = true; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->fw_name = "storage"; + dc->vmsd = &vmstate_usb_uas; + device_class_set_props(dc, uas_properties); +} + +static const TypeInfo uas_info = { + .name = TYPE_USB_UAS, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(UASDevice), + .class_init = usb_uas_class_initfn, +}; + +static void usb_uas_register_types(void) +{ + type_register_static(&uas_info); +} + +type_init(usb_uas_register_types) diff --git a/hw/usb/dev-wacom.c b/hw/usb/dev-wacom.c new file mode 100644 index 000000000..ed687bc9f --- /dev/null +++ b/hw/usb/dev-wacom.c @@ -0,0 +1,383 @@ +/* + * 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 "qemu/osdep.h" +#include "ui/console.h" +#include "hw/usb.h" +#include "hw/usb/hid.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "desc.h" +#include "qom/object.h" + +/* Interface requests */ +#define WACOM_GET_REPORT 0x2101 +#define WACOM_SET_REPORT 0x2109 + +struct USBWacomState { + USBDevice dev; + USBEndpoint *intr; + 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; +}; + +#define TYPE_USB_WACOM "usb-wacom-tablet" +OBJECT_DECLARE_SIMPLE_TYPE(USBWacomState, USB_WACOM) + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [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 */ + USB_DT_HID, /* u8 bDescriptorType */ + 0x01, 0x10, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + USB_DT_REPORT, /* 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 = USB_CFG_ATT_ONE, + .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; + usb_wakeup(s->intr, 0); +} + +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; + usb_wakeup(s->intr, 0); +} + +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 void 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; + } + + 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]; + break; + case WACOM_GET_REPORT: + data[0] = 0; + data[1] = s->mode; + p->actual_length = 2; + break; + /* USB HID requests */ + case HID_GET_REPORT: + if (s->mode == WACOM_MODE_HID) + p->actual_length = usb_mouse_poll(s, data, length); + else if (s->mode == WACOM_MODE_WACOM) + p->actual_length = usb_wacom_poll(s, data, length); + break; + case HID_GET_IDLE: + data[0] = s->idle; + p->actual_length = 1; + break; + case HID_SET_IDLE: + s->idle = (uint8_t) (value >> 8); + break; + default: + p->status = USB_RET_STALL; + break; + } +} + +static void usb_wacom_handle_data(USBDevice *dev, USBPacket *p) +{ + USBWacomState *s = (USBWacomState *) dev; + g_autofree uint8_t *buf = g_malloc(p->iov.size); + int len = 0; + + switch (p->pid) { + case USB_TOKEN_IN: + if (p->ep->nr == 1) { + if (!(s->changed || s->idle)) { + p->status = USB_RET_NAK; + return; + } + s->changed = 0; + if (s->mode == WACOM_MODE_HID) + len = usb_mouse_poll(s, buf, p->iov.size); + else if (s->mode == WACOM_MODE_WACOM) + len = usb_wacom_poll(s, buf, p->iov.size); + usb_packet_copy(p, buf, len); + break; + } + /* Fall through. */ + case USB_TOKEN_OUT: + default: + p->status = USB_RET_STALL; + } +} + +static void usb_wacom_unrealize(USBDevice *dev) +{ + USBWacomState *s = (USBWacomState *) dev; + + if (s->mouse_grabbed) { + qemu_remove_mouse_event_handler(s->eh_entry); + s->mouse_grabbed = 0; + } +} + +static void usb_wacom_realize(USBDevice *dev, Error **errp) +{ + USBWacomState *s = USB_WACOM(dev); + usb_desc_create_serial(dev); + usb_desc_init(dev); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); + s->changed = 1; +} + +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->realize = usb_wacom_realize; + uc->handle_reset = usb_wacom_handle_reset; + uc->handle_control = usb_wacom_handle_control; + uc->handle_data = usb_wacom_handle_data; + uc->unrealize = usb_wacom_unrealize; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + dc->desc = "QEMU PenPartner Tablet"; + dc->vmsd = &vmstate_usb_wacom; +} + +static const TypeInfo wacom_info = { + .name = TYPE_USB_WACOM, + .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(TYPE_USB_WACOM, "wacom-tablet", NULL); +} + +type_init(usb_wacom_register_types) diff --git a/hw/usb/hcd-dwc2.c b/hw/usb/hcd-dwc2.c new file mode 100644 index 000000000..e1d96acf7 --- /dev/null +++ b/hw/usb/hcd-dwc2.c @@ -0,0 +1,1478 @@ +/* + * dwc-hsotg (dwc2) USB host controller emulation + * + * Based on hw/usb/hcd-ehci.c and hw/usb/hcd-ohci.c + * + * Note that to use this emulation with the dwc-otg driver in the + * Raspbian kernel, you must pass the option "dwc_otg.fiq_fsm_enable=0" + * on the kernel command line. + * + * Some useful documentation used to develop this emulation can be + * found online (as of April 2020) at: + * + * http://www.capital-micro.com/PDF/CME-M7_Family_User_Guide_EN.pdf + * which has a pretty complete description of the controller starting + * on page 370. + * + * https://sourceforge.net/p/wive-ng/wive-ng-mt/ci/master/tree/docs/DataSheets/RT3050_5x_V2.0_081408_0902.pdf + * which has a description of the controller registers starting on + * page 130. + * + * Copyright (c) 2020 Paul Zimmerman <pauldzim@gmail.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 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "hw/usb/dwc2-regs.h" +#include "hw/usb/hcd-dwc2.h" +#include "migration/vmstate.h" +#include "trace.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "qemu/main-loop.h" +#include "hw/qdev-properties.h" + +#define USB_HZ_FS 12000000 +#define USB_HZ_HS 96000000 +#define USB_FRMINTVL 12000 + +/* nifty macros from Arnon's EHCI version */ +#define get_field(data, field) \ + (((data) & field##_MASK) >> field##_SHIFT) + +#define set_field(data, newval, field) do { \ + uint32_t val = *(data); \ + val &= ~field##_MASK; \ + val |= ((newval) << field##_SHIFT) & field##_MASK; \ + *(data) = val; \ +} while (0) + +#define get_bit(data, bitmask) \ + (!!((data) & (bitmask))) + +/* update irq line */ +static inline void dwc2_update_irq(DWC2State *s) +{ + static int oldlevel; + int level = 0; + + if ((s->gintsts & s->gintmsk) && (s->gahbcfg & GAHBCFG_GLBL_INTR_EN)) { + level = 1; + } + if (level != oldlevel) { + oldlevel = level; + trace_usb_dwc2_update_irq(level); + qemu_set_irq(s->irq, level); + } +} + +/* flag interrupt condition */ +static inline void dwc2_raise_global_irq(DWC2State *s, uint32_t intr) +{ + if (!(s->gintsts & intr)) { + s->gintsts |= intr; + trace_usb_dwc2_raise_global_irq(intr); + dwc2_update_irq(s); + } +} + +static inline void dwc2_lower_global_irq(DWC2State *s, uint32_t intr) +{ + if (s->gintsts & intr) { + s->gintsts &= ~intr; + trace_usb_dwc2_lower_global_irq(intr); + dwc2_update_irq(s); + } +} + +static inline void dwc2_raise_host_irq(DWC2State *s, uint32_t host_intr) +{ + if (!(s->haint & host_intr)) { + s->haint |= host_intr; + s->haint &= 0xffff; + trace_usb_dwc2_raise_host_irq(host_intr); + if (s->haint & s->haintmsk) { + dwc2_raise_global_irq(s, GINTSTS_HCHINT); + } + } +} + +static inline void dwc2_lower_host_irq(DWC2State *s, uint32_t host_intr) +{ + if (s->haint & host_intr) { + s->haint &= ~host_intr; + trace_usb_dwc2_lower_host_irq(host_intr); + if (!(s->haint & s->haintmsk)) { + dwc2_lower_global_irq(s, GINTSTS_HCHINT); + } + } +} + +static inline void dwc2_update_hc_irq(DWC2State *s, int index) +{ + uint32_t host_intr = 1 << (index >> 3); + + if (s->hreg1[index + 2] & s->hreg1[index + 3]) { + dwc2_raise_host_irq(s, host_intr); + } else { + dwc2_lower_host_irq(s, host_intr); + } +} + +/* set a timer for EOF */ +static void dwc2_eof_timer(DWC2State *s) +{ + timer_mod(s->eof_timer, s->sof_time + s->usb_frame_time); +} + +/* Set a timer for EOF and generate SOF event */ +static void dwc2_sof(DWC2State *s) +{ + s->sof_time += s->usb_frame_time; + trace_usb_dwc2_sof(s->sof_time); + dwc2_eof_timer(s); + dwc2_raise_global_irq(s, GINTSTS_SOF); +} + +/* Do frame processing on frame boundary */ +static void dwc2_frame_boundary(void *opaque) +{ + DWC2State *s = opaque; + int64_t now; + uint16_t frcnt; + + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + /* Frame boundary, so do EOF stuff here */ + + /* Increment frame number */ + frcnt = (uint16_t)((now - s->sof_time) / s->fi); + s->frame_number = (s->frame_number + frcnt) & 0xffff; + s->hfnum = s->frame_number & HFNUM_MAX_FRNUM; + + /* Do SOF stuff here */ + dwc2_sof(s); +} + +/* Start sending SOF tokens on the USB bus */ +static void dwc2_bus_start(DWC2State *s) +{ + trace_usb_dwc2_bus_start(); + s->sof_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + dwc2_eof_timer(s); +} + +/* Stop sending SOF tokens on the USB bus */ +static void dwc2_bus_stop(DWC2State *s) +{ + trace_usb_dwc2_bus_stop(); + timer_del(s->eof_timer); +} + +static USBDevice *dwc2_find_device(DWC2State *s, uint8_t addr) +{ + USBDevice *dev; + + trace_usb_dwc2_find_device(addr); + + if (!(s->hprt0 & HPRT0_ENA)) { + trace_usb_dwc2_port_disabled(0); + } else { + dev = usb_find_device(&s->uport, addr); + if (dev != NULL) { + trace_usb_dwc2_device_found(0); + return dev; + } + } + + trace_usb_dwc2_device_not_found(); + return NULL; +} + +static const char *pstatus[] = { + "USB_RET_SUCCESS", "USB_RET_NODEV", "USB_RET_NAK", "USB_RET_STALL", + "USB_RET_BABBLE", "USB_RET_IOERROR", "USB_RET_ASYNC", + "USB_RET_ADD_TO_QUEUE", "USB_RET_REMOVE_FROM_QUEUE" +}; + +static uint32_t pintr[] = { + HCINTMSK_XFERCOMPL, HCINTMSK_XACTERR, HCINTMSK_NAK, HCINTMSK_STALL, + HCINTMSK_BBLERR, HCINTMSK_XACTERR, HCINTMSK_XACTERR, HCINTMSK_XACTERR, + HCINTMSK_XACTERR +}; + +static const char *types[] = { + "Ctrl", "Isoc", "Bulk", "Intr" +}; + +static const char *dirs[] = { + "Out", "In" +}; + +static void dwc2_handle_packet(DWC2State *s, uint32_t devadr, USBDevice *dev, + USBEndpoint *ep, uint32_t index, bool send) +{ + DWC2Packet *p; + uint32_t hcchar = s->hreg1[index]; + uint32_t hctsiz = s->hreg1[index + 4]; + uint32_t hcdma = s->hreg1[index + 5]; + uint32_t chan, epnum, epdir, eptype, mps, pid, pcnt, len, tlen, intr = 0; + uint32_t tpcnt, stsidx, actual = 0; + bool do_intr = false, done = false; + + epnum = get_field(hcchar, HCCHAR_EPNUM); + epdir = get_bit(hcchar, HCCHAR_EPDIR); + eptype = get_field(hcchar, HCCHAR_EPTYPE); + mps = get_field(hcchar, HCCHAR_MPS); + pid = get_field(hctsiz, TSIZ_SC_MC_PID); + pcnt = get_field(hctsiz, TSIZ_PKTCNT); + len = get_field(hctsiz, TSIZ_XFERSIZE); + if (len > DWC2_MAX_XFER_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: HCTSIZ transfer size too large\n", __func__); + return; + } + + chan = index >> 3; + p = &s->packet[chan]; + + trace_usb_dwc2_handle_packet(chan, dev, &p->packet, epnum, types[eptype], + dirs[epdir], mps, len, pcnt); + + if (mps == 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad HCCHAR_MPS set to zero\n", __func__); + return; + } + + if (eptype == USB_ENDPOINT_XFER_CONTROL && pid == TSIZ_SC_MC_PID_SETUP) { + pid = USB_TOKEN_SETUP; + } else { + pid = epdir ? USB_TOKEN_IN : USB_TOKEN_OUT; + } + + if (send) { + tlen = len; + if (p->small) { + if (tlen > mps) { + tlen = mps; + } + } + + if (pid != USB_TOKEN_IN) { + trace_usb_dwc2_memory_read(hcdma, tlen); + if (dma_memory_read(&s->dma_as, hcdma, + s->usb_buf[chan], tlen) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: dma_memory_read failed\n", + __func__); + } + } + + usb_packet_init(&p->packet); + usb_packet_setup(&p->packet, pid, ep, 0, hcdma, + pid != USB_TOKEN_IN, true); + usb_packet_addbuf(&p->packet, s->usb_buf[chan], tlen); + p->async = DWC2_ASYNC_NONE; + usb_handle_packet(dev, &p->packet); + } else { + tlen = p->len; + } + + stsidx = -p->packet.status; + assert(stsidx < sizeof(pstatus) / sizeof(*pstatus)); + actual = p->packet.actual_length; + trace_usb_dwc2_packet_status(pstatus[stsidx], actual); + +babble: + if (p->packet.status != USB_RET_SUCCESS && + p->packet.status != USB_RET_NAK && + p->packet.status != USB_RET_STALL && + p->packet.status != USB_RET_ASYNC) { + trace_usb_dwc2_packet_error(pstatus[stsidx]); + } + + if (p->packet.status == USB_RET_ASYNC) { + trace_usb_dwc2_async_packet(&p->packet, chan, dev, epnum, + dirs[epdir], tlen); + usb_device_flush_ep_queue(dev, ep); + assert(p->async != DWC2_ASYNC_INFLIGHT); + p->devadr = devadr; + p->epnum = epnum; + p->epdir = epdir; + p->mps = mps; + p->pid = pid; + p->index = index; + p->pcnt = pcnt; + p->len = tlen; + p->async = DWC2_ASYNC_INFLIGHT; + p->needs_service = false; + return; + } + + if (p->packet.status == USB_RET_SUCCESS) { + if (actual > tlen) { + p->packet.status = USB_RET_BABBLE; + goto babble; + } + + if (pid == USB_TOKEN_IN) { + trace_usb_dwc2_memory_write(hcdma, actual); + if (dma_memory_write(&s->dma_as, hcdma, s->usb_buf[chan], + actual) != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: dma_memory_write failed\n", + __func__); + } + } + + tpcnt = actual / mps; + if (actual % mps) { + tpcnt++; + if (pid == USB_TOKEN_IN) { + done = true; + } + } + + pcnt -= tpcnt < pcnt ? tpcnt : pcnt; + set_field(&hctsiz, pcnt, TSIZ_PKTCNT); + len -= actual < len ? actual : len; + set_field(&hctsiz, len, TSIZ_XFERSIZE); + s->hreg1[index + 4] = hctsiz; + hcdma += actual; + s->hreg1[index + 5] = hcdma; + + if (!pcnt || len == 0 || actual == 0) { + done = true; + } + } else { + intr |= pintr[stsidx]; + if (p->packet.status == USB_RET_NAK && + (eptype == USB_ENDPOINT_XFER_CONTROL || + eptype == USB_ENDPOINT_XFER_BULK)) { + /* + * for ctrl/bulk, automatically retry on NAK, + * but send the interrupt anyway + */ + intr &= ~HCINTMSK_RESERVED14_31; + s->hreg1[index + 2] |= intr; + do_intr = true; + } else { + intr |= HCINTMSK_CHHLTD; + done = true; + } + } + + usb_packet_cleanup(&p->packet); + + if (done) { + hcchar &= ~HCCHAR_CHENA; + s->hreg1[index] = hcchar; + if (!(intr & HCINTMSK_CHHLTD)) { + intr |= HCINTMSK_CHHLTD | HCINTMSK_XFERCOMPL; + } + intr &= ~HCINTMSK_RESERVED14_31; + s->hreg1[index + 2] |= intr; + p->needs_service = false; + trace_usb_dwc2_packet_done(pstatus[stsidx], actual, len, pcnt); + dwc2_update_hc_irq(s, index); + return; + } + + p->devadr = devadr; + p->epnum = epnum; + p->epdir = epdir; + p->mps = mps; + p->pid = pid; + p->index = index; + p->pcnt = pcnt; + p->len = len; + p->needs_service = true; + trace_usb_dwc2_packet_next(pstatus[stsidx], len, pcnt); + if (do_intr) { + dwc2_update_hc_irq(s, index); + } +} + +/* Attach or detach a device on root hub */ + +static const char *speeds[] = { + "low", "full", "high" +}; + +static void dwc2_attach(USBPort *port) +{ + DWC2State *s = port->opaque; + int hispd = 0; + + trace_usb_dwc2_attach(port); + assert(port->index == 0); + + if (!port->dev || !port->dev->attached) { + return; + } + + assert(port->dev->speed <= USB_SPEED_HIGH); + trace_usb_dwc2_attach_speed(speeds[port->dev->speed]); + s->hprt0 &= ~HPRT0_SPD_MASK; + + switch (port->dev->speed) { + case USB_SPEED_LOW: + s->hprt0 |= HPRT0_SPD_LOW_SPEED << HPRT0_SPD_SHIFT; + break; + case USB_SPEED_FULL: + s->hprt0 |= HPRT0_SPD_FULL_SPEED << HPRT0_SPD_SHIFT; + break; + case USB_SPEED_HIGH: + s->hprt0 |= HPRT0_SPD_HIGH_SPEED << HPRT0_SPD_SHIFT; + hispd = 1; + break; + } + + if (hispd) { + s->usb_frame_time = NANOSECONDS_PER_SECOND / 8000; /* 125000 */ + if (NANOSECONDS_PER_SECOND >= USB_HZ_HS) { + s->usb_bit_time = NANOSECONDS_PER_SECOND / USB_HZ_HS; /* 10.4 */ + } else { + s->usb_bit_time = 1; + } + } else { + s->usb_frame_time = NANOSECONDS_PER_SECOND / 1000; /* 1000000 */ + if (NANOSECONDS_PER_SECOND >= USB_HZ_FS) { + s->usb_bit_time = NANOSECONDS_PER_SECOND / USB_HZ_FS; /* 83.3 */ + } else { + s->usb_bit_time = 1; + } + } + + s->fi = USB_FRMINTVL - 1; + s->hprt0 |= HPRT0_CONNDET | HPRT0_CONNSTS; + + dwc2_bus_start(s); + dwc2_raise_global_irq(s, GINTSTS_PRTINT); +} + +static void dwc2_detach(USBPort *port) +{ + DWC2State *s = port->opaque; + + trace_usb_dwc2_detach(port); + assert(port->index == 0); + + dwc2_bus_stop(s); + + s->hprt0 &= ~(HPRT0_SPD_MASK | HPRT0_SUSP | HPRT0_ENA | HPRT0_CONNSTS); + s->hprt0 |= HPRT0_CONNDET | HPRT0_ENACHG; + + dwc2_raise_global_irq(s, GINTSTS_PRTINT); +} + +static void dwc2_child_detach(USBPort *port, USBDevice *child) +{ + trace_usb_dwc2_child_detach(port, child); + assert(port->index == 0); +} + +static void dwc2_wakeup(USBPort *port) +{ + DWC2State *s = port->opaque; + + trace_usb_dwc2_wakeup(port); + assert(port->index == 0); + + if (s->hprt0 & HPRT0_SUSP) { + s->hprt0 |= HPRT0_RES; + dwc2_raise_global_irq(s, GINTSTS_PRTINT); + } + + qemu_bh_schedule(s->async_bh); +} + +static void dwc2_async_packet_complete(USBPort *port, USBPacket *packet) +{ + DWC2State *s = port->opaque; + DWC2Packet *p; + USBDevice *dev; + USBEndpoint *ep; + + assert(port->index == 0); + p = container_of(packet, DWC2Packet, packet); + dev = dwc2_find_device(s, p->devadr); + ep = usb_ep_get(dev, p->pid, p->epnum); + trace_usb_dwc2_async_packet_complete(port, packet, p->index >> 3, dev, + p->epnum, dirs[p->epdir], p->len); + assert(p->async == DWC2_ASYNC_INFLIGHT); + + if (packet->status == USB_RET_REMOVE_FROM_QUEUE) { + usb_cancel_packet(packet); + usb_packet_cleanup(packet); + return; + } + + dwc2_handle_packet(s, p->devadr, dev, ep, p->index, false); + + p->async = DWC2_ASYNC_FINISHED; + qemu_bh_schedule(s->async_bh); +} + +static USBPortOps dwc2_port_ops = { + .attach = dwc2_attach, + .detach = dwc2_detach, + .child_detach = dwc2_child_detach, + .wakeup = dwc2_wakeup, + .complete = dwc2_async_packet_complete, +}; + +static uint32_t dwc2_get_frame_remaining(DWC2State *s) +{ + uint32_t fr = 0; + int64_t tks; + + tks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->sof_time; + if (tks < 0) { + tks = 0; + } + + /* avoid muldiv if possible */ + if (tks >= s->usb_frame_time) { + goto out; + } + if (tks < s->usb_bit_time) { + fr = s->fi; + goto out; + } + + /* tks = number of ns since SOF, divided by 83 (fs) or 10 (hs) */ + tks = tks / s->usb_bit_time; + if (tks >= (int64_t)s->fi) { + goto out; + } + + /* remaining = frame interval minus tks */ + fr = (uint32_t)((int64_t)s->fi - tks); + +out: + return fr; +} + +static void dwc2_work_bh(void *opaque) +{ + DWC2State *s = opaque; + DWC2Packet *p; + USBDevice *dev; + USBEndpoint *ep; + int64_t t_now, expire_time; + int chan; + bool found = false; + + trace_usb_dwc2_work_bh(); + if (s->working) { + return; + } + s->working = true; + + t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + chan = s->next_chan; + + do { + p = &s->packet[chan]; + if (p->needs_service) { + dev = dwc2_find_device(s, p->devadr); + ep = usb_ep_get(dev, p->pid, p->epnum); + trace_usb_dwc2_work_bh_service(s->next_chan, chan, dev, p->epnum); + dwc2_handle_packet(s, p->devadr, dev, ep, p->index, true); + found = true; + } + if (++chan == DWC2_NB_CHAN) { + chan = 0; + } + if (found) { + s->next_chan = chan; + trace_usb_dwc2_work_bh_next(chan); + } + } while (chan != s->next_chan); + + if (found) { + expire_time = t_now + NANOSECONDS_PER_SECOND / 4000; + timer_mod(s->frame_timer, expire_time); + } + s->working = false; +} + +static void dwc2_enable_chan(DWC2State *s, uint32_t index) +{ + USBDevice *dev; + USBEndpoint *ep; + uint32_t hcchar; + uint32_t hctsiz; + uint32_t devadr, epnum, epdir, eptype, pid, len; + DWC2Packet *p; + + assert((index >> 3) < DWC2_NB_CHAN); + p = &s->packet[index >> 3]; + hcchar = s->hreg1[index]; + hctsiz = s->hreg1[index + 4]; + devadr = get_field(hcchar, HCCHAR_DEVADDR); + epnum = get_field(hcchar, HCCHAR_EPNUM); + epdir = get_bit(hcchar, HCCHAR_EPDIR); + eptype = get_field(hcchar, HCCHAR_EPTYPE); + pid = get_field(hctsiz, TSIZ_SC_MC_PID); + len = get_field(hctsiz, TSIZ_XFERSIZE); + + dev = dwc2_find_device(s, devadr); + + trace_usb_dwc2_enable_chan(index >> 3, dev, &p->packet, epnum); + if (dev == NULL) { + return; + } + + if (eptype == USB_ENDPOINT_XFER_CONTROL && pid == TSIZ_SC_MC_PID_SETUP) { + pid = USB_TOKEN_SETUP; + } else { + pid = epdir ? USB_TOKEN_IN : USB_TOKEN_OUT; + } + + ep = usb_ep_get(dev, pid, epnum); + + /* + * Hack: Networking doesn't like us delivering large transfers, it kind + * of works but the latency is horrible. So if the transfer is <= the mtu + * size, we take that as a hint that this might be a network transfer, + * and do the transfer packet-by-packet. + */ + if (len > 1536) { + p->small = false; + } else { + p->small = true; + } + + dwc2_handle_packet(s, devadr, dev, ep, index, true); + qemu_bh_schedule(s->async_bh); +} + +static const char *glbregnm[] = { + "GOTGCTL ", "GOTGINT ", "GAHBCFG ", "GUSBCFG ", "GRSTCTL ", + "GINTSTS ", "GINTMSK ", "GRXSTSR ", "GRXSTSP ", "GRXFSIZ ", + "GNPTXFSIZ", "GNPTXSTS ", "GI2CCTL ", "GPVNDCTL ", "GGPIO ", + "GUID ", "GSNPSID ", "GHWCFG1 ", "GHWCFG2 ", "GHWCFG3 ", + "GHWCFG4 ", "GLPMCFG ", "GPWRDN ", "GDFIFOCFG", "GADPCTL ", + "GREFCLK ", "GINTMSK2 ", "GINTSTS2 " +}; + +static uint64_t dwc2_glbreg_read(void *ptr, hwaddr addr, int index, + unsigned size) +{ + DWC2State *s = ptr; + uint32_t val; + + if (addr > GINTSTS2) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return 0; + } + + val = s->glbreg[index]; + + switch (addr) { + case GRSTCTL: + /* clear any self-clearing bits that were set */ + val &= ~(GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH | GRSTCTL_IN_TKNQ_FLSH | + GRSTCTL_FRMCNTRRST | GRSTCTL_HSFTRST | GRSTCTL_CSFTRST); + s->glbreg[index] = val; + break; + default: + break; + } + + trace_usb_dwc2_glbreg_read(addr, glbregnm[index], val); + return val; +} + +static void dwc2_glbreg_write(void *ptr, hwaddr addr, int index, uint64_t val, + unsigned size) +{ + DWC2State *s = ptr; + uint64_t orig = val; + uint32_t *mmio; + uint32_t old; + int iflg = 0; + + if (addr > GINTSTS2) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return; + } + + mmio = &s->glbreg[index]; + old = *mmio; + + switch (addr) { + case GOTGCTL: + /* don't allow setting of read-only bits */ + val &= ~(GOTGCTL_MULT_VALID_BC_MASK | GOTGCTL_BSESVLD | + GOTGCTL_ASESVLD | GOTGCTL_DBNC_SHORT | GOTGCTL_CONID_B | + GOTGCTL_HSTNEGSCS | GOTGCTL_SESREQSCS); + /* don't allow clearing of read-only bits */ + val |= old & (GOTGCTL_MULT_VALID_BC_MASK | GOTGCTL_BSESVLD | + GOTGCTL_ASESVLD | GOTGCTL_DBNC_SHORT | GOTGCTL_CONID_B | + GOTGCTL_HSTNEGSCS | GOTGCTL_SESREQSCS); + break; + case GAHBCFG: + if ((val & GAHBCFG_GLBL_INTR_EN) && !(old & GAHBCFG_GLBL_INTR_EN)) { + iflg = 1; + } + break; + case GRSTCTL: + val |= GRSTCTL_AHBIDLE; + val &= ~GRSTCTL_DMAREQ; + if (!(old & GRSTCTL_TXFFLSH) && (val & GRSTCTL_TXFFLSH)) { + /* TODO - TX fifo flush */ + qemu_log_mask(LOG_UNIMP, "%s: Tx FIFO flush not implemented\n", + __func__); + } + if (!(old & GRSTCTL_RXFFLSH) && (val & GRSTCTL_RXFFLSH)) { + /* TODO - RX fifo flush */ + qemu_log_mask(LOG_UNIMP, "%s: Rx FIFO flush not implemented\n", + __func__); + } + if (!(old & GRSTCTL_IN_TKNQ_FLSH) && (val & GRSTCTL_IN_TKNQ_FLSH)) { + /* TODO - device IN token queue flush */ + qemu_log_mask(LOG_UNIMP, "%s: Token queue flush not implemented\n", + __func__); + } + if (!(old & GRSTCTL_FRMCNTRRST) && (val & GRSTCTL_FRMCNTRRST)) { + /* TODO - host frame counter reset */ + qemu_log_mask(LOG_UNIMP, + "%s: Frame counter reset not implemented\n", + __func__); + } + if (!(old & GRSTCTL_HSFTRST) && (val & GRSTCTL_HSFTRST)) { + /* TODO - host soft reset */ + qemu_log_mask(LOG_UNIMP, "%s: Host soft reset not implemented\n", + __func__); + } + if (!(old & GRSTCTL_CSFTRST) && (val & GRSTCTL_CSFTRST)) { + /* TODO - core soft reset */ + qemu_log_mask(LOG_UNIMP, "%s: Core soft reset not implemented\n", + __func__); + } + /* don't allow clearing of self-clearing bits */ + val |= old & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH | + GRSTCTL_IN_TKNQ_FLSH | GRSTCTL_FRMCNTRRST | + GRSTCTL_HSFTRST | GRSTCTL_CSFTRST); + break; + case GINTSTS: + /* clear the write-1-to-clear bits */ + val |= ~old; + val = ~val; + /* don't allow clearing of read-only bits */ + val |= old & (GINTSTS_PTXFEMP | GINTSTS_HCHINT | GINTSTS_PRTINT | + GINTSTS_OEPINT | GINTSTS_IEPINT | GINTSTS_GOUTNAKEFF | + GINTSTS_GINNAKEFF | GINTSTS_NPTXFEMP | GINTSTS_RXFLVL | + GINTSTS_OTGINT | GINTSTS_CURMODE_HOST); + iflg = 1; + break; + case GINTMSK: + iflg = 1; + break; + default: + break; + } + + trace_usb_dwc2_glbreg_write(addr, glbregnm[index], orig, old, val); + *mmio = val; + + if (iflg) { + dwc2_update_irq(s); + } +} + +static uint64_t dwc2_fszreg_read(void *ptr, hwaddr addr, int index, + unsigned size) +{ + DWC2State *s = ptr; + uint32_t val; + + if (addr != HPTXFSIZ) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return 0; + } + + val = s->fszreg[index]; + + trace_usb_dwc2_fszreg_read(addr, val); + return val; +} + +static void dwc2_fszreg_write(void *ptr, hwaddr addr, int index, uint64_t val, + unsigned size) +{ + DWC2State *s = ptr; + uint64_t orig = val; + uint32_t *mmio; + uint32_t old; + + if (addr != HPTXFSIZ) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return; + } + + mmio = &s->fszreg[index]; + old = *mmio; + + trace_usb_dwc2_fszreg_write(addr, orig, old, val); + *mmio = val; +} + +static const char *hreg0nm[] = { + "HCFG ", "HFIR ", "HFNUM ", "<rsvd> ", "HPTXSTS ", + "HAINT ", "HAINTMSK ", "HFLBADDR ", "<rsvd> ", "<rsvd> ", + "<rsvd> ", "<rsvd> ", "<rsvd> ", "<rsvd> ", "<rsvd> ", + "<rsvd> ", "HPRT0 " +}; + +static uint64_t dwc2_hreg0_read(void *ptr, hwaddr addr, int index, + unsigned size) +{ + DWC2State *s = ptr; + uint32_t val; + + if (addr < HCFG || addr > HPRT0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return 0; + } + + val = s->hreg0[index]; + + switch (addr) { + case HFNUM: + val = (dwc2_get_frame_remaining(s) << HFNUM_FRREM_SHIFT) | + (s->hfnum << HFNUM_FRNUM_SHIFT); + break; + default: + break; + } + + trace_usb_dwc2_hreg0_read(addr, hreg0nm[index], val); + return val; +} + +static void dwc2_hreg0_write(void *ptr, hwaddr addr, int index, uint64_t val, + unsigned size) +{ + DWC2State *s = ptr; + USBDevice *dev = s->uport.dev; + uint64_t orig = val; + uint32_t *mmio; + uint32_t tval, told, old; + int prst = 0; + int iflg = 0; + + if (addr < HCFG || addr > HPRT0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return; + } + + mmio = &s->hreg0[index]; + old = *mmio; + + switch (addr) { + case HFIR: + break; + case HFNUM: + case HPTXSTS: + case HAINT: + qemu_log_mask(LOG_GUEST_ERROR, "%s: write to read-only register\n", + __func__); + return; + case HAINTMSK: + val &= 0xffff; + break; + case HPRT0: + /* don't allow clearing of read-only bits */ + val |= old & (HPRT0_SPD_MASK | HPRT0_LNSTS_MASK | HPRT0_OVRCURRACT | + HPRT0_CONNSTS); + /* don't allow clearing of self-clearing bits */ + val |= old & (HPRT0_SUSP | HPRT0_RES); + /* don't allow setting of self-setting bits */ + if (!(old & HPRT0_ENA) && (val & HPRT0_ENA)) { + val &= ~HPRT0_ENA; + } + /* clear the write-1-to-clear bits */ + tval = val & (HPRT0_OVRCURRCHG | HPRT0_ENACHG | HPRT0_ENA | + HPRT0_CONNDET); + told = old & (HPRT0_OVRCURRCHG | HPRT0_ENACHG | HPRT0_ENA | + HPRT0_CONNDET); + tval |= ~told; + tval = ~tval; + tval &= (HPRT0_OVRCURRCHG | HPRT0_ENACHG | HPRT0_ENA | + HPRT0_CONNDET); + val &= ~(HPRT0_OVRCURRCHG | HPRT0_ENACHG | HPRT0_ENA | + HPRT0_CONNDET); + val |= tval; + if (!(val & HPRT0_RST) && (old & HPRT0_RST)) { + if (dev && dev->attached) { + val |= HPRT0_ENA | HPRT0_ENACHG; + prst = 1; + } + } + if (val & (HPRT0_OVRCURRCHG | HPRT0_ENACHG | HPRT0_CONNDET)) { + iflg = 1; + } else { + iflg = -1; + } + break; + default: + break; + } + + if (prst) { + trace_usb_dwc2_hreg0_write(addr, hreg0nm[index], orig, old, + val & ~HPRT0_CONNDET); + trace_usb_dwc2_hreg0_action("call usb_port_reset"); + usb_port_reset(&s->uport); + val &= ~HPRT0_CONNDET; + } else { + trace_usb_dwc2_hreg0_write(addr, hreg0nm[index], orig, old, val); + } + + *mmio = val; + + if (iflg > 0) { + trace_usb_dwc2_hreg0_action("enable PRTINT"); + dwc2_raise_global_irq(s, GINTSTS_PRTINT); + } else if (iflg < 0) { + trace_usb_dwc2_hreg0_action("disable PRTINT"); + dwc2_lower_global_irq(s, GINTSTS_PRTINT); + } +} + +static const char *hreg1nm[] = { + "HCCHAR ", "HCSPLT ", "HCINT ", "HCINTMSK", "HCTSIZ ", "HCDMA ", + "<rsvd> ", "HCDMAB " +}; + +static uint64_t dwc2_hreg1_read(void *ptr, hwaddr addr, int index, + unsigned size) +{ + DWC2State *s = ptr; + uint32_t val; + + if (addr < HCCHAR(0) || addr > HCDMAB(DWC2_NB_CHAN - 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return 0; + } + + val = s->hreg1[index]; + + trace_usb_dwc2_hreg1_read(addr, hreg1nm[index & 7], addr >> 5, val); + return val; +} + +static void dwc2_hreg1_write(void *ptr, hwaddr addr, int index, uint64_t val, + unsigned size) +{ + DWC2State *s = ptr; + uint64_t orig = val; + uint32_t *mmio; + uint32_t old; + int iflg = 0; + int enflg = 0; + int disflg = 0; + + if (addr < HCCHAR(0) || addr > HCDMAB(DWC2_NB_CHAN - 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return; + } + + mmio = &s->hreg1[index]; + old = *mmio; + + switch (HSOTG_REG(0x500) + (addr & 0x1c)) { + case HCCHAR(0): + if ((val & HCCHAR_CHDIS) && !(old & HCCHAR_CHDIS)) { + val &= ~(HCCHAR_CHENA | HCCHAR_CHDIS); + disflg = 1; + } else { + val |= old & HCCHAR_CHDIS; + if ((val & HCCHAR_CHENA) && !(old & HCCHAR_CHENA)) { + val &= ~HCCHAR_CHDIS; + enflg = 1; + } else { + val |= old & HCCHAR_CHENA; + } + } + break; + case HCINT(0): + /* clear the write-1-to-clear bits */ + val |= ~old; + val = ~val; + val &= ~HCINTMSK_RESERVED14_31; + iflg = 1; + break; + case HCINTMSK(0): + val &= ~HCINTMSK_RESERVED14_31; + iflg = 1; + break; + case HCDMAB(0): + qemu_log_mask(LOG_GUEST_ERROR, "%s: write to read-only register\n", + __func__); + return; + default: + break; + } + + trace_usb_dwc2_hreg1_write(addr, hreg1nm[index & 7], index >> 3, orig, + old, val); + *mmio = val; + + if (disflg) { + /* set ChHltd in HCINT */ + s->hreg1[(index & ~7) + 2] |= HCINTMSK_CHHLTD; + iflg = 1; + } + + if (enflg) { + dwc2_enable_chan(s, index & ~7); + } + + if (iflg) { + dwc2_update_hc_irq(s, index & ~7); + } +} + +static const char *pcgregnm[] = { + "PCGCTL ", "PCGCCTL1 " +}; + +static uint64_t dwc2_pcgreg_read(void *ptr, hwaddr addr, int index, + unsigned size) +{ + DWC2State *s = ptr; + uint32_t val; + + if (addr < PCGCTL || addr > PCGCCTL1) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return 0; + } + + val = s->pcgreg[index]; + + trace_usb_dwc2_pcgreg_read(addr, pcgregnm[index], val); + return val; +} + +static void dwc2_pcgreg_write(void *ptr, hwaddr addr, int index, + uint64_t val, unsigned size) +{ + DWC2State *s = ptr; + uint64_t orig = val; + uint32_t *mmio; + uint32_t old; + + if (addr < PCGCTL || addr > PCGCCTL1) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return; + } + + mmio = &s->pcgreg[index]; + old = *mmio; + + trace_usb_dwc2_pcgreg_write(addr, pcgregnm[index], orig, old, val); + *mmio = val; +} + +static uint64_t dwc2_hsotg_read(void *ptr, hwaddr addr, unsigned size) +{ + uint64_t val; + + switch (addr) { + case HSOTG_REG(0x000) ... HSOTG_REG(0x0fc): + val = dwc2_glbreg_read(ptr, addr, (addr - HSOTG_REG(0x000)) >> 2, size); + break; + case HSOTG_REG(0x100): + val = dwc2_fszreg_read(ptr, addr, (addr - HSOTG_REG(0x100)) >> 2, size); + break; + case HSOTG_REG(0x104) ... HSOTG_REG(0x3fc): + /* Gadget-mode registers, just return 0 for now */ + val = 0; + break; + case HSOTG_REG(0x400) ... HSOTG_REG(0x4fc): + val = dwc2_hreg0_read(ptr, addr, (addr - HSOTG_REG(0x400)) >> 2, size); + break; + case HSOTG_REG(0x500) ... HSOTG_REG(0x7fc): + val = dwc2_hreg1_read(ptr, addr, (addr - HSOTG_REG(0x500)) >> 2, size); + break; + case HSOTG_REG(0x800) ... HSOTG_REG(0xdfc): + /* Gadget-mode registers, just return 0 for now */ + val = 0; + break; + case HSOTG_REG(0xe00) ... HSOTG_REG(0xffc): + val = dwc2_pcgreg_read(ptr, addr, (addr - HSOTG_REG(0xe00)) >> 2, size); + break; + default: + g_assert_not_reached(); + } + + return val; +} + +static void dwc2_hsotg_write(void *ptr, hwaddr addr, uint64_t val, + unsigned size) +{ + switch (addr) { + case HSOTG_REG(0x000) ... HSOTG_REG(0x0fc): + dwc2_glbreg_write(ptr, addr, (addr - HSOTG_REG(0x000)) >> 2, val, size); + break; + case HSOTG_REG(0x100): + dwc2_fszreg_write(ptr, addr, (addr - HSOTG_REG(0x100)) >> 2, val, size); + break; + case HSOTG_REG(0x104) ... HSOTG_REG(0x3fc): + /* Gadget-mode registers, do nothing for now */ + break; + case HSOTG_REG(0x400) ... HSOTG_REG(0x4fc): + dwc2_hreg0_write(ptr, addr, (addr - HSOTG_REG(0x400)) >> 2, val, size); + break; + case HSOTG_REG(0x500) ... HSOTG_REG(0x7fc): + dwc2_hreg1_write(ptr, addr, (addr - HSOTG_REG(0x500)) >> 2, val, size); + break; + case HSOTG_REG(0x800) ... HSOTG_REG(0xdfc): + /* Gadget-mode registers, do nothing for now */ + break; + case HSOTG_REG(0xe00) ... HSOTG_REG(0xffc): + dwc2_pcgreg_write(ptr, addr, (addr - HSOTG_REG(0xe00)) >> 2, val, size); + break; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps dwc2_mmio_hsotg_ops = { + .read = dwc2_hsotg_read, + .write = dwc2_hsotg_write, + .impl.min_access_size = 4, + .impl.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static uint64_t dwc2_hreg2_read(void *ptr, hwaddr addr, unsigned size) +{ + /* TODO - implement FIFOs to support slave mode */ + trace_usb_dwc2_hreg2_read(addr, addr >> 12, 0); + qemu_log_mask(LOG_UNIMP, "%s: FIFO read not implemented\n", __func__); + return 0; +} + +static void dwc2_hreg2_write(void *ptr, hwaddr addr, uint64_t val, + unsigned size) +{ + uint64_t orig = val; + + /* TODO - implement FIFOs to support slave mode */ + trace_usb_dwc2_hreg2_write(addr, addr >> 12, orig, 0, val); + qemu_log_mask(LOG_UNIMP, "%s: FIFO write not implemented\n", __func__); +} + +static const MemoryRegionOps dwc2_mmio_hreg2_ops = { + .read = dwc2_hreg2_read, + .write = dwc2_hreg2_write, + .impl.min_access_size = 4, + .impl.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void dwc2_wakeup_endpoint(USBBus *bus, USBEndpoint *ep, + unsigned int stream) +{ + DWC2State *s = container_of(bus, DWC2State, bus); + + trace_usb_dwc2_wakeup_endpoint(ep, stream); + + /* TODO - do something here? */ + qemu_bh_schedule(s->async_bh); +} + +static USBBusOps dwc2_bus_ops = { + .wakeup_endpoint = dwc2_wakeup_endpoint, +}; + +static void dwc2_work_timer(void *opaque) +{ + DWC2State *s = opaque; + + trace_usb_dwc2_work_timer(); + qemu_bh_schedule(s->async_bh); +} + +static void dwc2_reset_enter(Object *obj, ResetType type) +{ + DWC2Class *c = DWC2_USB_GET_CLASS(obj); + DWC2State *s = DWC2_USB(obj); + int i; + + trace_usb_dwc2_reset_enter(); + + if (c->parent_phases.enter) { + c->parent_phases.enter(obj, type); + } + + timer_del(s->frame_timer); + qemu_bh_cancel(s->async_bh); + + if (s->uport.dev && s->uport.dev->attached) { + usb_detach(&s->uport); + } + + dwc2_bus_stop(s); + + s->gotgctl = GOTGCTL_BSESVLD | GOTGCTL_ASESVLD | GOTGCTL_CONID_B; + s->gotgint = 0; + s->gahbcfg = 0; + s->gusbcfg = 5 << GUSBCFG_USBTRDTIM_SHIFT; + s->grstctl = GRSTCTL_AHBIDLE; + s->gintsts = GINTSTS_CONIDSTSCHNG | GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP | + GINTSTS_CURMODE_HOST; + s->gintmsk = 0; + s->grxstsr = 0; + s->grxstsp = 0; + s->grxfsiz = 1024; + s->gnptxfsiz = 1024 << FIFOSIZE_DEPTH_SHIFT; + s->gnptxsts = (4 << FIFOSIZE_DEPTH_SHIFT) | 1024; + s->gi2cctl = GI2CCTL_I2CDATSE0 | GI2CCTL_ACK; + s->gpvndctl = 0; + s->ggpio = 0; + s->guid = 0; + s->gsnpsid = 0x4f54294a; + s->ghwcfg1 = 0; + s->ghwcfg2 = (8 << GHWCFG2_DEV_TOKEN_Q_DEPTH_SHIFT) | + (4 << GHWCFG2_HOST_PERIO_TX_Q_DEPTH_SHIFT) | + (4 << GHWCFG2_NONPERIO_TX_Q_DEPTH_SHIFT) | + GHWCFG2_DYNAMIC_FIFO | + GHWCFG2_PERIO_EP_SUPPORTED | + ((DWC2_NB_CHAN - 1) << GHWCFG2_NUM_HOST_CHAN_SHIFT) | + (GHWCFG2_INT_DMA_ARCH << GHWCFG2_ARCHITECTURE_SHIFT) | + (GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST << GHWCFG2_OP_MODE_SHIFT); + s->ghwcfg3 = (4096 << GHWCFG3_DFIFO_DEPTH_SHIFT) | + (4 << GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT) | + (4 << GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT); + s->ghwcfg4 = 0; + s->glpmcfg = 0; + s->gpwrdn = GPWRDN_PWRDNRSTN; + s->gdfifocfg = 0; + s->gadpctl = 0; + s->grefclk = 0; + s->gintmsk2 = 0; + s->gintsts2 = 0; + + s->hptxfsiz = 500 << FIFOSIZE_DEPTH_SHIFT; + + s->hcfg = 2 << HCFG_RESVALID_SHIFT; + s->hfir = 60000; + s->hfnum = 0x3fff; + s->hptxsts = (16 << TXSTS_QSPCAVAIL_SHIFT) | 32768; + s->haint = 0; + s->haintmsk = 0; + s->hprt0 = 0; + + memset(s->hreg1, 0, sizeof(s->hreg1)); + memset(s->pcgreg, 0, sizeof(s->pcgreg)); + + s->sof_time = 0; + s->frame_number = 0; + s->fi = USB_FRMINTVL - 1; + s->next_chan = 0; + s->working = false; + + for (i = 0; i < DWC2_NB_CHAN; i++) { + s->packet[i].needs_service = false; + } +} + +static void dwc2_reset_hold(Object *obj) +{ + DWC2Class *c = DWC2_USB_GET_CLASS(obj); + DWC2State *s = DWC2_USB(obj); + + trace_usb_dwc2_reset_hold(); + + if (c->parent_phases.hold) { + c->parent_phases.hold(obj); + } + + dwc2_update_irq(s); +} + +static void dwc2_reset_exit(Object *obj) +{ + DWC2Class *c = DWC2_USB_GET_CLASS(obj); + DWC2State *s = DWC2_USB(obj); + + trace_usb_dwc2_reset_exit(); + + if (c->parent_phases.exit) { + c->parent_phases.exit(obj); + } + + s->hprt0 = HPRT0_PWR; + if (s->uport.dev && s->uport.dev->attached) { + usb_attach(&s->uport); + usb_device_reset(s->uport.dev); + } +} + +static void dwc2_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + DWC2State *s = DWC2_USB(dev); + Object *obj; + + obj = object_property_get_link(OBJECT(dev), "dma-mr", &error_abort); + + s->dma_mr = MEMORY_REGION(obj); + address_space_init(&s->dma_as, s->dma_mr, "dwc2"); + + usb_bus_new(&s->bus, sizeof(s->bus), &dwc2_bus_ops, dev); + usb_register_port(&s->bus, &s->uport, s, 0, &dwc2_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL | + (s->usb_version == 2 ? USB_SPEED_MASK_HIGH : 0)); + s->uport.dev = 0; + + s->usb_frame_time = NANOSECONDS_PER_SECOND / 1000; /* 1000000 */ + if (NANOSECONDS_PER_SECOND >= USB_HZ_FS) { + s->usb_bit_time = NANOSECONDS_PER_SECOND / USB_HZ_FS; /* 83.3 */ + } else { + s->usb_bit_time = 1; + } + + s->fi = USB_FRMINTVL - 1; + s->eof_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, dwc2_frame_boundary, s); + s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, dwc2_work_timer, s); + s->async_bh = qemu_bh_new(dwc2_work_bh, s); + + sysbus_init_irq(sbd, &s->irq); +} + +static void dwc2_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + DWC2State *s = DWC2_USB(obj); + + memory_region_init(&s->container, obj, "dwc2", DWC2_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->container); + + memory_region_init_io(&s->hsotg, obj, &dwc2_mmio_hsotg_ops, s, + "dwc2-io", 4 * KiB); + memory_region_add_subregion(&s->container, 0x0000, &s->hsotg); + + memory_region_init_io(&s->fifos, obj, &dwc2_mmio_hreg2_ops, s, + "dwc2-fifo", 64 * KiB); + memory_region_add_subregion(&s->container, 0x1000, &s->fifos); +} + +static const VMStateDescription vmstate_dwc2_state_packet = { + .name = "dwc2/packet", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(devadr, DWC2Packet), + VMSTATE_UINT32(epnum, DWC2Packet), + VMSTATE_UINT32(epdir, DWC2Packet), + VMSTATE_UINT32(mps, DWC2Packet), + VMSTATE_UINT32(pid, DWC2Packet), + VMSTATE_UINT32(index, DWC2Packet), + VMSTATE_UINT32(pcnt, DWC2Packet), + VMSTATE_UINT32(len, DWC2Packet), + VMSTATE_INT32(async, DWC2Packet), + VMSTATE_BOOL(small, DWC2Packet), + VMSTATE_BOOL(needs_service, DWC2Packet), + VMSTATE_END_OF_LIST() + }, +}; + +const VMStateDescription vmstate_dwc2_state = { + .name = "dwc2", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(glbreg, DWC2State, + DWC2_GLBREG_SIZE / sizeof(uint32_t)), + VMSTATE_UINT32_ARRAY(fszreg, DWC2State, + DWC2_FSZREG_SIZE / sizeof(uint32_t)), + VMSTATE_UINT32_ARRAY(hreg0, DWC2State, + DWC2_HREG0_SIZE / sizeof(uint32_t)), + VMSTATE_UINT32_ARRAY(hreg1, DWC2State, + DWC2_HREG1_SIZE / sizeof(uint32_t)), + VMSTATE_UINT32_ARRAY(pcgreg, DWC2State, + DWC2_PCGREG_SIZE / sizeof(uint32_t)), + + VMSTATE_TIMER_PTR(eof_timer, DWC2State), + VMSTATE_TIMER_PTR(frame_timer, DWC2State), + VMSTATE_INT64(sof_time, DWC2State), + VMSTATE_INT64(usb_frame_time, DWC2State), + VMSTATE_INT64(usb_bit_time, DWC2State), + VMSTATE_UINT32(usb_version, DWC2State), + VMSTATE_UINT16(frame_number, DWC2State), + VMSTATE_UINT16(fi, DWC2State), + VMSTATE_UINT16(next_chan, DWC2State), + VMSTATE_BOOL(working, DWC2State), + + VMSTATE_STRUCT_ARRAY(packet, DWC2State, DWC2_NB_CHAN, 1, + vmstate_dwc2_state_packet, DWC2Packet), + VMSTATE_UINT8_2DARRAY(usb_buf, DWC2State, DWC2_NB_CHAN, + DWC2_MAX_XFER_SIZE), + + VMSTATE_END_OF_LIST() + } +}; + +static Property dwc2_usb_properties[] = { + DEFINE_PROP_UINT32("usb_version", DWC2State, usb_version, 2), + DEFINE_PROP_END_OF_LIST(), +}; + +static void dwc2_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + DWC2Class *c = DWC2_USB_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = dwc2_realize; + dc->vmsd = &vmstate_dwc2_state; + set_bit(DEVICE_CATEGORY_USB, dc->categories); + device_class_set_props(dc, dwc2_usb_properties); + resettable_class_set_parent_phases(rc, dwc2_reset_enter, dwc2_reset_hold, + dwc2_reset_exit, &c->parent_phases); +} + +static const TypeInfo dwc2_usb_type_info = { + .name = TYPE_DWC2_USB, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DWC2State), + .instance_init = dwc2_init, + .class_size = sizeof(DWC2Class), + .class_init = dwc2_class_init, +}; + +static void dwc2_usb_register_types(void) +{ + type_register_static(&dwc2_usb_type_info); +} + +type_init(dwc2_usb_register_types) diff --git a/hw/usb/hcd-dwc2.h b/hw/usb/hcd-dwc2.h new file mode 100644 index 000000000..6998b0470 --- /dev/null +++ b/hw/usb/hcd-dwc2.h @@ -0,0 +1,186 @@ +/* + * dwc-hsotg (dwc2) USB host controller state definitions + * + * Based on hw/usb/hcd-ehci.h + * + * Copyright (c) 2020 Paul Zimmerman <pauldzim@gmail.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 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef HW_USB_DWC2_H +#define HW_USB_DWC2_H + +#include "qemu/timer.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "hw/usb.h" +#include "sysemu/dma.h" +#include "qom/object.h" + +#define DWC2_MMIO_SIZE 0x11000 + +#define DWC2_NB_CHAN 8 /* Number of host channels */ +#define DWC2_MAX_XFER_SIZE 65536 /* Max transfer size expected in HCTSIZ */ + +typedef struct DWC2Packet DWC2Packet; +typedef struct DWC2State DWC2State; +typedef struct DWC2Class DWC2Class; + +enum async_state { + DWC2_ASYNC_NONE = 0, + DWC2_ASYNC_INITIALIZED, + DWC2_ASYNC_INFLIGHT, + DWC2_ASYNC_FINISHED, +}; + +struct DWC2Packet { + USBPacket packet; + uint32_t devadr; + uint32_t epnum; + uint32_t epdir; + uint32_t mps; + uint32_t pid; + uint32_t index; + uint32_t pcnt; + uint32_t len; + int32_t async; + bool small; + bool needs_service; +}; + +struct DWC2State { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + USBBus bus; + qemu_irq irq; + MemoryRegion *dma_mr; + AddressSpace dma_as; + MemoryRegion container; + MemoryRegion hsotg; + MemoryRegion fifos; + + union { +#define DWC2_GLBREG_SIZE 0x70 + uint32_t glbreg[DWC2_GLBREG_SIZE / sizeof(uint32_t)]; + struct { + uint32_t gotgctl; /* 00 */ + uint32_t gotgint; /* 04 */ + uint32_t gahbcfg; /* 08 */ + uint32_t gusbcfg; /* 0c */ + uint32_t grstctl; /* 10 */ + uint32_t gintsts; /* 14 */ + uint32_t gintmsk; /* 18 */ + uint32_t grxstsr; /* 1c */ + uint32_t grxstsp; /* 20 */ + uint32_t grxfsiz; /* 24 */ + uint32_t gnptxfsiz; /* 28 */ + uint32_t gnptxsts; /* 2c */ + uint32_t gi2cctl; /* 30 */ + uint32_t gpvndctl; /* 34 */ + uint32_t ggpio; /* 38 */ + uint32_t guid; /* 3c */ + uint32_t gsnpsid; /* 40 */ + uint32_t ghwcfg1; /* 44 */ + uint32_t ghwcfg2; /* 48 */ + uint32_t ghwcfg3; /* 4c */ + uint32_t ghwcfg4; /* 50 */ + uint32_t glpmcfg; /* 54 */ + uint32_t gpwrdn; /* 58 */ + uint32_t gdfifocfg; /* 5c */ + uint32_t gadpctl; /* 60 */ + uint32_t grefclk; /* 64 */ + uint32_t gintmsk2; /* 68 */ + uint32_t gintsts2; /* 6c */ + }; + }; + + union { +#define DWC2_FSZREG_SIZE 0x04 + uint32_t fszreg[DWC2_FSZREG_SIZE / sizeof(uint32_t)]; + struct { + uint32_t hptxfsiz; /* 100 */ + }; + }; + + union { +#define DWC2_HREG0_SIZE 0x44 + uint32_t hreg0[DWC2_HREG0_SIZE / sizeof(uint32_t)]; + struct { + uint32_t hcfg; /* 400 */ + uint32_t hfir; /* 404 */ + uint32_t hfnum; /* 408 */ + uint32_t rsvd0; /* 40c */ + uint32_t hptxsts; /* 410 */ + uint32_t haint; /* 414 */ + uint32_t haintmsk; /* 418 */ + uint32_t hflbaddr; /* 41c */ + uint32_t rsvd1[8]; /* 420-43c */ + uint32_t hprt0; /* 440 */ + }; + }; + +#define DWC2_HREG1_SIZE (0x20 * DWC2_NB_CHAN) + uint32_t hreg1[DWC2_HREG1_SIZE / sizeof(uint32_t)]; + +#define hcchar(_ch) hreg1[((_ch) << 3) + 0] /* 500, 520, ... */ +#define hcsplt(_ch) hreg1[((_ch) << 3) + 1] /* 504, 524, ... */ +#define hcint(_ch) hreg1[((_ch) << 3) + 2] /* 508, 528, ... */ +#define hcintmsk(_ch) hreg1[((_ch) << 3) + 3] /* 50c, 52c, ... */ +#define hctsiz(_ch) hreg1[((_ch) << 3) + 4] /* 510, 530, ... */ +#define hcdma(_ch) hreg1[((_ch) << 3) + 5] /* 514, 534, ... */ +#define hcdmab(_ch) hreg1[((_ch) << 3) + 7] /* 51c, 53c, ... */ + + union { +#define DWC2_PCGREG_SIZE 0x08 + uint32_t pcgreg[DWC2_PCGREG_SIZE / sizeof(uint32_t)]; + struct { + uint32_t pcgctl; /* e00 */ + uint32_t pcgcctl1; /* e04 */ + }; + }; + + /* TODO - implement FIFO registers for slave mode */ +#define DWC2_HFIFO_SIZE (0x1000 * DWC2_NB_CHAN) + + /* + * Internal state + */ + QEMUTimer *eof_timer; + QEMUTimer *frame_timer; + QEMUBH *async_bh; + int64_t sof_time; + int64_t usb_frame_time; + int64_t usb_bit_time; + uint32_t usb_version; + uint16_t frame_number; + uint16_t fi; + uint16_t next_chan; + bool working; + USBPort uport; + DWC2Packet packet[DWC2_NB_CHAN]; /* one packet per chan */ + uint8_t usb_buf[DWC2_NB_CHAN][DWC2_MAX_XFER_SIZE]; /* one buffer per chan */ +}; + +struct DWC2Class { + /*< private >*/ + SysBusDeviceClass parent_class; + ResettablePhases parent_phases; + + /*< public >*/ +}; + +#define TYPE_DWC2_USB "dwc2-usb" +OBJECT_DECLARE_TYPE(DWC2State, DWC2Class, DWC2_USB) + +#endif diff --git a/hw/usb/hcd-dwc3.c b/hw/usb/hcd-dwc3.c new file mode 100644 index 000000000..279263489 --- /dev/null +++ b/hw/usb/hcd-dwc3.c @@ -0,0 +1,688 @@ +/* + * QEMU model of the USB DWC3 host controller emulation. + * + * This model defines global register space of DWC3 controller. Global + * registers control the AXI/AHB interfaces properties, external FIFO support + * and event count support. All of which are unimplemented at present. We are + * only supporting core reset and read of ID register. + * + * Copyright (c) 2020 Xilinx Inc. Vikram Garhwal<fnu.vikram@xilinx.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/osdep.h" +#include "hw/sysbus.h" +#include "hw/register.h" +#include "qemu/bitops.h" +#include "qom/object.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "hw/usb/hcd-dwc3.h" +#include "qapi/error.h" + +#ifndef USB_DWC3_ERR_DEBUG +#define USB_DWC3_ERR_DEBUG 0 +#endif + +#define HOST_MODE 1 +#define FIFO_LEN 0x1000 + +REG32(GSBUSCFG0, 0x00) + FIELD(GSBUSCFG0, DATRDREQINFO, 28, 4) + FIELD(GSBUSCFG0, DESRDREQINFO, 24, 4) + FIELD(GSBUSCFG0, DATWRREQINFO, 20, 4) + FIELD(GSBUSCFG0, DESWRREQINFO, 16, 4) + FIELD(GSBUSCFG0, RESERVED_15_12, 12, 4) + FIELD(GSBUSCFG0, DATBIGEND, 11, 1) + FIELD(GSBUSCFG0, DESBIGEND, 10, 1) + FIELD(GSBUSCFG0, RESERVED_9_8, 8, 2) + FIELD(GSBUSCFG0, INCR256BRSTENA, 7, 1) + FIELD(GSBUSCFG0, INCR128BRSTENA, 6, 1) + FIELD(GSBUSCFG0, INCR64BRSTENA, 5, 1) + FIELD(GSBUSCFG0, INCR32BRSTENA, 4, 1) + FIELD(GSBUSCFG0, INCR16BRSTENA, 3, 1) + FIELD(GSBUSCFG0, INCR8BRSTENA, 2, 1) + FIELD(GSBUSCFG0, INCR4BRSTENA, 1, 1) + FIELD(GSBUSCFG0, INCRBRSTENA, 0, 1) +REG32(GSBUSCFG1, 0x04) + FIELD(GSBUSCFG1, RESERVED_31_13, 13, 19) + FIELD(GSBUSCFG1, EN1KPAGE, 12, 1) + FIELD(GSBUSCFG1, PIPETRANSLIMIT, 8, 4) + FIELD(GSBUSCFG1, RESERVED_7_0, 0, 8) +REG32(GTXTHRCFG, 0x08) + FIELD(GTXTHRCFG, RESERVED_31, 31, 1) + FIELD(GTXTHRCFG, RESERVED_30, 30, 1) + FIELD(GTXTHRCFG, USBTXPKTCNTSEL, 29, 1) + FIELD(GTXTHRCFG, RESERVED_28, 28, 1) + FIELD(GTXTHRCFG, USBTXPKTCNT, 24, 4) + FIELD(GTXTHRCFG, USBMAXTXBURSTSIZE, 16, 8) + FIELD(GTXTHRCFG, RESERVED_15, 15, 1) + FIELD(GTXTHRCFG, RESERVED_14, 14, 1) + FIELD(GTXTHRCFG, RESERVED_13_11, 11, 3) + FIELD(GTXTHRCFG, RESERVED_10_0, 0, 11) +REG32(GRXTHRCFG, 0x0c) + FIELD(GRXTHRCFG, RESERVED_31_30, 30, 2) + FIELD(GRXTHRCFG, USBRXPKTCNTSEL, 29, 1) + FIELD(GRXTHRCFG, RESERVED_28, 28, 1) + FIELD(GRXTHRCFG, USBRXPKTCNT, 24, 4) + FIELD(GRXTHRCFG, USBMAXRXBURSTSIZE, 19, 5) + FIELD(GRXTHRCFG, RESERVED_18_16, 16, 3) + FIELD(GRXTHRCFG, RESERVED_15, 15, 1) + FIELD(GRXTHRCFG, RESERVED_14_13, 13, 2) + FIELD(GRXTHRCFG, RESVISOCOUTSPC, 0, 13) +REG32(GCTL, 0x10) + FIELD(GCTL, PWRDNSCALE, 19, 13) + FIELD(GCTL, MASTERFILTBYPASS, 18, 1) + FIELD(GCTL, BYPSSETADDR, 17, 1) + FIELD(GCTL, U2RSTECN, 16, 1) + FIELD(GCTL, FRMSCLDWN, 14, 2) + FIELD(GCTL, PRTCAPDIR, 12, 2) + FIELD(GCTL, CORESOFTRESET, 11, 1) + FIELD(GCTL, U1U2TIMERSCALE, 9, 1) + FIELD(GCTL, DEBUGATTACH, 8, 1) + FIELD(GCTL, RAMCLKSEL, 6, 2) + FIELD(GCTL, SCALEDOWN, 4, 2) + FIELD(GCTL, DISSCRAMBLE, 3, 1) + FIELD(GCTL, U2EXIT_LFPS, 2, 1) + FIELD(GCTL, GBLHIBERNATIONEN, 1, 1) + FIELD(GCTL, DSBLCLKGTNG, 0, 1) +REG32(GPMSTS, 0x14) +REG32(GSTS, 0x18) + FIELD(GSTS, CBELT, 20, 12) + FIELD(GSTS, RESERVED_19_12, 12, 8) + FIELD(GSTS, SSIC_IP, 11, 1) + FIELD(GSTS, OTG_IP, 10, 1) + FIELD(GSTS, BC_IP, 9, 1) + FIELD(GSTS, ADP_IP, 8, 1) + FIELD(GSTS, HOST_IP, 7, 1) + FIELD(GSTS, DEVICE_IP, 6, 1) + FIELD(GSTS, CSRTIMEOUT, 5, 1) + FIELD(GSTS, BUSERRADDRVLD, 4, 1) + FIELD(GSTS, RESERVED_3_2, 2, 2) + FIELD(GSTS, CURMOD, 0, 2) +REG32(GUCTL1, 0x1c) + FIELD(GUCTL1, RESUME_OPMODE_HS_HOST, 10, 1) +REG32(GSNPSID, 0x20) +REG32(GGPIO, 0x24) + FIELD(GGPIO, GPO, 16, 16) + FIELD(GGPIO, GPI, 0, 16) +REG32(GUID, 0x28) +REG32(GUCTL, 0x2c) + FIELD(GUCTL, REFCLKPER, 22, 10) + FIELD(GUCTL, NOEXTRDL, 21, 1) + FIELD(GUCTL, RESERVED_20_18, 18, 3) + FIELD(GUCTL, SPRSCTRLTRANSEN, 17, 1) + FIELD(GUCTL, RESBWHSEPS, 16, 1) + FIELD(GUCTL, RESERVED_15, 15, 1) + FIELD(GUCTL, USBHSTINAUTORETRYEN, 14, 1) + FIELD(GUCTL, ENOVERLAPCHK, 13, 1) + FIELD(GUCTL, EXTCAPSUPPTEN, 12, 1) + FIELD(GUCTL, INSRTEXTRFSBODI, 11, 1) + FIELD(GUCTL, DTCT, 9, 2) + FIELD(GUCTL, DTFT, 0, 9) +REG32(GBUSERRADDRLO, 0x30) +REG32(GBUSERRADDRHI, 0x34) +REG32(GHWPARAMS0, 0x40) + FIELD(GHWPARAMS0, GHWPARAMS0_31_24, 24, 8) + FIELD(GHWPARAMS0, GHWPARAMS0_23_16, 16, 8) + FIELD(GHWPARAMS0, GHWPARAMS0_15_8, 8, 8) + FIELD(GHWPARAMS0, GHWPARAMS0_7_6, 6, 2) + FIELD(GHWPARAMS0, GHWPARAMS0_5_3, 3, 3) + FIELD(GHWPARAMS0, GHWPARAMS0_2_0, 0, 3) +REG32(GHWPARAMS1, 0x44) + FIELD(GHWPARAMS1, GHWPARAMS1_31, 31, 1) + FIELD(GHWPARAMS1, GHWPARAMS1_30, 30, 1) + FIELD(GHWPARAMS1, GHWPARAMS1_29, 29, 1) + FIELD(GHWPARAMS1, GHWPARAMS1_28, 28, 1) + FIELD(GHWPARAMS1, GHWPARAMS1_27, 27, 1) + FIELD(GHWPARAMS1, GHWPARAMS1_26, 26, 1) + FIELD(GHWPARAMS1, GHWPARAMS1_25_24, 24, 2) + FIELD(GHWPARAMS1, GHWPARAMS1_23, 23, 1) + FIELD(GHWPARAMS1, GHWPARAMS1_22_21, 21, 2) + FIELD(GHWPARAMS1, GHWPARAMS1_20_15, 15, 6) + FIELD(GHWPARAMS1, GHWPARAMS1_14_12, 12, 3) + FIELD(GHWPARAMS1, GHWPARAMS1_11_9, 9, 3) + FIELD(GHWPARAMS1, GHWPARAMS1_8_6, 6, 3) + FIELD(GHWPARAMS1, GHWPARAMS1_5_3, 3, 3) + FIELD(GHWPARAMS1, GHWPARAMS1_2_0, 0, 3) +REG32(GHWPARAMS2, 0x48) +REG32(GHWPARAMS3, 0x4c) + FIELD(GHWPARAMS3, GHWPARAMS3_31, 31, 1) + FIELD(GHWPARAMS3, GHWPARAMS3_30_23, 23, 8) + FIELD(GHWPARAMS3, GHWPARAMS3_22_18, 18, 5) + FIELD(GHWPARAMS3, GHWPARAMS3_17_12, 12, 6) + FIELD(GHWPARAMS3, GHWPARAMS3_11, 11, 1) + FIELD(GHWPARAMS3, GHWPARAMS3_10, 10, 1) + FIELD(GHWPARAMS3, GHWPARAMS3_9_8, 8, 2) + FIELD(GHWPARAMS3, GHWPARAMS3_7_6, 6, 2) + FIELD(GHWPARAMS3, GHWPARAMS3_5_4, 4, 2) + FIELD(GHWPARAMS3, GHWPARAMS3_3_2, 2, 2) + FIELD(GHWPARAMS3, GHWPARAMS3_1_0, 0, 2) +REG32(GHWPARAMS4, 0x50) + FIELD(GHWPARAMS4, GHWPARAMS4_31_28, 28, 4) + FIELD(GHWPARAMS4, GHWPARAMS4_27_24, 24, 4) + FIELD(GHWPARAMS4, GHWPARAMS4_23, 23, 1) + FIELD(GHWPARAMS4, GHWPARAMS4_22, 22, 1) + FIELD(GHWPARAMS4, GHWPARAMS4_21, 21, 1) + FIELD(GHWPARAMS4, GHWPARAMS4_20_17, 17, 4) + FIELD(GHWPARAMS4, GHWPARAMS4_16_13, 13, 4) + FIELD(GHWPARAMS4, GHWPARAMS4_12, 12, 1) + FIELD(GHWPARAMS4, GHWPARAMS4_11, 11, 1) + FIELD(GHWPARAMS4, GHWPARAMS4_10_9, 9, 2) + FIELD(GHWPARAMS4, GHWPARAMS4_8_7, 7, 2) + FIELD(GHWPARAMS4, GHWPARAMS4_6, 6, 1) + FIELD(GHWPARAMS4, GHWPARAMS4_5_0, 0, 6) +REG32(GHWPARAMS5, 0x54) + FIELD(GHWPARAMS5, GHWPARAMS5_31_28, 28, 4) + FIELD(GHWPARAMS5, GHWPARAMS5_27_22, 22, 6) + FIELD(GHWPARAMS5, GHWPARAMS5_21_16, 16, 6) + FIELD(GHWPARAMS5, GHWPARAMS5_15_10, 10, 6) + FIELD(GHWPARAMS5, GHWPARAMS5_9_4, 4, 6) + FIELD(GHWPARAMS5, GHWPARAMS5_3_0, 0, 4) +REG32(GHWPARAMS6, 0x58) + FIELD(GHWPARAMS6, GHWPARAMS6_31_16, 16, 16) + FIELD(GHWPARAMS6, BUSFLTRSSUPPORT, 15, 1) + FIELD(GHWPARAMS6, BCSUPPORT, 14, 1) + FIELD(GHWPARAMS6, OTG_SS_SUPPORT, 13, 1) + FIELD(GHWPARAMS6, ADPSUPPORT, 12, 1) + FIELD(GHWPARAMS6, HNPSUPPORT, 11, 1) + FIELD(GHWPARAMS6, SRPSUPPORT, 10, 1) + FIELD(GHWPARAMS6, GHWPARAMS6_9_8, 8, 2) + FIELD(GHWPARAMS6, GHWPARAMS6_7, 7, 1) + FIELD(GHWPARAMS6, GHWPARAMS6_6, 6, 1) + FIELD(GHWPARAMS6, GHWPARAMS6_5_0, 0, 6) +REG32(GHWPARAMS7, 0x5c) + FIELD(GHWPARAMS7, GHWPARAMS7_31_16, 16, 16) + FIELD(GHWPARAMS7, GHWPARAMS7_15_0, 0, 16) +REG32(GDBGFIFOSPACE, 0x60) + FIELD(GDBGFIFOSPACE, SPACE_AVAILABLE, 16, 16) + FIELD(GDBGFIFOSPACE, RESERVED_15_9, 9, 7) + FIELD(GDBGFIFOSPACE, FIFO_QUEUE_SELECT, 0, 9) +REG32(GUCTL2, 0x9c) + FIELD(GUCTL2, RESERVED_31_26, 26, 6) + FIELD(GUCTL2, EN_HP_PM_TIMER, 19, 7) + FIELD(GUCTL2, NOLOWPWRDUR, 15, 4) + FIELD(GUCTL2, RST_ACTBITLATER, 14, 1) + FIELD(GUCTL2, RESERVED_13, 13, 1) + FIELD(GUCTL2, DISABLECFC, 11, 1) +REG32(GUSB2PHYCFG, 0x100) + FIELD(GUSB2PHYCFG, U2_FREECLK_EXISTS, 30, 1) + FIELD(GUSB2PHYCFG, ULPI_LPM_WITH_OPMODE_CHK, 29, 1) + FIELD(GUSB2PHYCFG, RESERVED_25, 25, 1) + FIELD(GUSB2PHYCFG, LSTRD, 22, 3) + FIELD(GUSB2PHYCFG, LSIPD, 19, 3) + FIELD(GUSB2PHYCFG, ULPIEXTVBUSINDIACTOR, 18, 1) + FIELD(GUSB2PHYCFG, ULPIEXTVBUSDRV, 17, 1) + FIELD(GUSB2PHYCFG, RESERVED_16, 16, 1) + FIELD(GUSB2PHYCFG, ULPIAUTORES, 15, 1) + FIELD(GUSB2PHYCFG, RESERVED_14, 14, 1) + FIELD(GUSB2PHYCFG, USBTRDTIM, 10, 4) + FIELD(GUSB2PHYCFG, XCVRDLY, 9, 1) + FIELD(GUSB2PHYCFG, ENBLSLPM, 8, 1) + FIELD(GUSB2PHYCFG, PHYSEL, 7, 1) + FIELD(GUSB2PHYCFG, SUSPENDUSB20, 6, 1) + FIELD(GUSB2PHYCFG, FSINTF, 5, 1) + FIELD(GUSB2PHYCFG, ULPI_UTMI_SEL, 4, 1) + FIELD(GUSB2PHYCFG, PHYIF, 3, 1) + FIELD(GUSB2PHYCFG, TOUTCAL, 0, 3) +REG32(GUSB2I2CCTL, 0x140) +REG32(GUSB2PHYACC_ULPI, 0x180) + FIELD(GUSB2PHYACC_ULPI, RESERVED_31_27, 27, 5) + FIELD(GUSB2PHYACC_ULPI, DISUIPIDRVR, 26, 1) + FIELD(GUSB2PHYACC_ULPI, NEWREGREQ, 25, 1) + FIELD(GUSB2PHYACC_ULPI, VSTSDONE, 24, 1) + FIELD(GUSB2PHYACC_ULPI, VSTSBSY, 23, 1) + FIELD(GUSB2PHYACC_ULPI, REGWR, 22, 1) + FIELD(GUSB2PHYACC_ULPI, REGADDR, 16, 6) + FIELD(GUSB2PHYACC_ULPI, EXTREGADDR, 8, 8) + FIELD(GUSB2PHYACC_ULPI, REGDATA, 0, 8) +REG32(GTXFIFOSIZ0, 0x200) + FIELD(GTXFIFOSIZ0, TXFSTADDR_N, 16, 16) + FIELD(GTXFIFOSIZ0, TXFDEP_N, 0, 16) +REG32(GTXFIFOSIZ1, 0x204) + FIELD(GTXFIFOSIZ1, TXFSTADDR_N, 16, 16) + FIELD(GTXFIFOSIZ1, TXFDEP_N, 0, 16) +REG32(GTXFIFOSIZ2, 0x208) + FIELD(GTXFIFOSIZ2, TXFSTADDR_N, 16, 16) + FIELD(GTXFIFOSIZ2, TXFDEP_N, 0, 16) +REG32(GTXFIFOSIZ3, 0x20c) + FIELD(GTXFIFOSIZ3, TXFSTADDR_N, 16, 16) + FIELD(GTXFIFOSIZ3, TXFDEP_N, 0, 16) +REG32(GTXFIFOSIZ4, 0x210) + FIELD(GTXFIFOSIZ4, TXFSTADDR_N, 16, 16) + FIELD(GTXFIFOSIZ4, TXFDEP_N, 0, 16) +REG32(GTXFIFOSIZ5, 0x214) + FIELD(GTXFIFOSIZ5, TXFSTADDR_N, 16, 16) + FIELD(GTXFIFOSIZ5, TXFDEP_N, 0, 16) +REG32(GRXFIFOSIZ0, 0x280) + FIELD(GRXFIFOSIZ0, RXFSTADDR_N, 16, 16) + FIELD(GRXFIFOSIZ0, RXFDEP_N, 0, 16) +REG32(GRXFIFOSIZ1, 0x284) + FIELD(GRXFIFOSIZ1, RXFSTADDR_N, 16, 16) + FIELD(GRXFIFOSIZ1, RXFDEP_N, 0, 16) +REG32(GRXFIFOSIZ2, 0x288) + FIELD(GRXFIFOSIZ2, RXFSTADDR_N, 16, 16) + FIELD(GRXFIFOSIZ2, RXFDEP_N, 0, 16) +REG32(GEVNTADRLO_0, 0x300) +REG32(GEVNTADRHI_0, 0x304) +REG32(GEVNTSIZ_0, 0x308) + FIELD(GEVNTSIZ_0, EVNTINTRPTMASK, 31, 1) + FIELD(GEVNTSIZ_0, RESERVED_30_16, 16, 15) + FIELD(GEVNTSIZ_0, EVENTSIZ, 0, 16) +REG32(GEVNTCOUNT_0, 0x30c) + FIELD(GEVNTCOUNT_0, EVNT_HANDLER_BUSY, 31, 1) + FIELD(GEVNTCOUNT_0, RESERVED_30_16, 16, 15) + FIELD(GEVNTCOUNT_0, EVNTCOUNT, 0, 16) +REG32(GEVNTADRLO_1, 0x310) +REG32(GEVNTADRHI_1, 0x314) +REG32(GEVNTSIZ_1, 0x318) + FIELD(GEVNTSIZ_1, EVNTINTRPTMASK, 31, 1) + FIELD(GEVNTSIZ_1, RESERVED_30_16, 16, 15) + FIELD(GEVNTSIZ_1, EVENTSIZ, 0, 16) +REG32(GEVNTCOUNT_1, 0x31c) + FIELD(GEVNTCOUNT_1, EVNT_HANDLER_BUSY, 31, 1) + FIELD(GEVNTCOUNT_1, RESERVED_30_16, 16, 15) + FIELD(GEVNTCOUNT_1, EVNTCOUNT, 0, 16) +REG32(GEVNTADRLO_2, 0x320) +REG32(GEVNTADRHI_2, 0x324) +REG32(GEVNTSIZ_2, 0x328) + FIELD(GEVNTSIZ_2, EVNTINTRPTMASK, 31, 1) + FIELD(GEVNTSIZ_2, RESERVED_30_16, 16, 15) + FIELD(GEVNTSIZ_2, EVENTSIZ, 0, 16) +REG32(GEVNTCOUNT_2, 0x32c) + FIELD(GEVNTCOUNT_2, EVNT_HANDLER_BUSY, 31, 1) + FIELD(GEVNTCOUNT_2, RESERVED_30_16, 16, 15) + FIELD(GEVNTCOUNT_2, EVNTCOUNT, 0, 16) +REG32(GEVNTADRLO_3, 0x330) +REG32(GEVNTADRHI_3, 0x334) +REG32(GEVNTSIZ_3, 0x338) + FIELD(GEVNTSIZ_3, EVNTINTRPTMASK, 31, 1) + FIELD(GEVNTSIZ_3, RESERVED_30_16, 16, 15) + FIELD(GEVNTSIZ_3, EVENTSIZ, 0, 16) +REG32(GEVNTCOUNT_3, 0x33c) + FIELD(GEVNTCOUNT_3, EVNT_HANDLER_BUSY, 31, 1) + FIELD(GEVNTCOUNT_3, RESERVED_30_16, 16, 15) + FIELD(GEVNTCOUNT_3, EVNTCOUNT, 0, 16) +REG32(GHWPARAMS8, 0x500) +REG32(GTXFIFOPRIDEV, 0x510) + FIELD(GTXFIFOPRIDEV, RESERVED_31_N, 6, 26) + FIELD(GTXFIFOPRIDEV, GTXFIFOPRIDEV, 0, 6) +REG32(GTXFIFOPRIHST, 0x518) + FIELD(GTXFIFOPRIHST, RESERVED_31_16, 3, 29) + FIELD(GTXFIFOPRIHST, GTXFIFOPRIHST, 0, 3) +REG32(GRXFIFOPRIHST, 0x51c) + FIELD(GRXFIFOPRIHST, RESERVED_31_16, 3, 29) + FIELD(GRXFIFOPRIHST, GRXFIFOPRIHST, 0, 3) +REG32(GDMAHLRATIO, 0x524) + FIELD(GDMAHLRATIO, RESERVED_31_13, 13, 19) + FIELD(GDMAHLRATIO, HSTRXFIFO, 8, 5) + FIELD(GDMAHLRATIO, RESERVED_7_5, 5, 3) + FIELD(GDMAHLRATIO, HSTTXFIFO, 0, 5) +REG32(GFLADJ, 0x530) + FIELD(GFLADJ, GFLADJ_REFCLK_240MHZDECR_PLS1, 31, 1) + FIELD(GFLADJ, GFLADJ_REFCLK_240MHZ_DECR, 24, 7) + FIELD(GFLADJ, GFLADJ_REFCLK_LPM_SEL, 23, 1) + FIELD(GFLADJ, RESERVED_22, 22, 1) + FIELD(GFLADJ, GFLADJ_REFCLK_FLADJ, 8, 14) + FIELD(GFLADJ, GFLADJ_30MHZ_SDBND_SEL, 7, 1) + FIELD(GFLADJ, GFLADJ_30MHZ, 0, 6) + +#define DWC3_GLOBAL_OFFSET 0xC100 +static void reset_csr(USBDWC3 * s) +{ + int i = 0; + /* + * We reset all CSR regs except GCTL, GUCTL, GSTS, GSNPSID, GGPIO, GUID, + * GUSB2PHYCFGn registers and GUSB3PIPECTLn registers. We will skip PHY + * register as we don't implement them. + */ + for (i = 0; i < USB_DWC3_R_MAX; i++) { + switch (i) { + case R_GCTL: + break; + case R_GSTS: + break; + case R_GSNPSID: + break; + case R_GGPIO: + break; + case R_GUID: + break; + case R_GUCTL: + break; + case R_GHWPARAMS0...R_GHWPARAMS7: + break; + case R_GHWPARAMS8: + break; + default: + register_reset(&s->regs_info[i]); + break; + } + } + + xhci_sysbus_reset(DEVICE(&s->sysbus_xhci)); +} + +static void usb_dwc3_gctl_postw(RegisterInfo *reg, uint64_t val64) +{ + USBDWC3 *s = USB_DWC3(reg->opaque); + + if (ARRAY_FIELD_EX32(s->regs, GCTL, CORESOFTRESET)) { + reset_csr(s); + } +} + +static void usb_dwc3_guid_postw(RegisterInfo *reg, uint64_t val64) +{ + USBDWC3 *s = USB_DWC3(reg->opaque); + + s->regs[R_GUID] = s->cfg.dwc_usb3_user; +} + +static const RegisterAccessInfo usb_dwc3_regs_info[] = { + { .name = "GSBUSCFG0", .addr = A_GSBUSCFG0, + .ro = 0xf300, + .unimp = 0xffffffff, + },{ .name = "GSBUSCFG1", .addr = A_GSBUSCFG1, + .reset = 0x300, + .ro = 0xffffe0ff, + .unimp = 0xffffffff, + },{ .name = "GTXTHRCFG", .addr = A_GTXTHRCFG, + .ro = 0xd000ffff, + .unimp = 0xffffffff, + },{ .name = "GRXTHRCFG", .addr = A_GRXTHRCFG, + .ro = 0xd007e000, + .unimp = 0xffffffff, + },{ .name = "GCTL", .addr = A_GCTL, + .reset = 0x30c13004, .post_write = usb_dwc3_gctl_postw, + },{ .name = "GPMSTS", .addr = A_GPMSTS, + .ro = 0xfffffff, + .unimp = 0xffffffff, + },{ .name = "GSTS", .addr = A_GSTS, + .reset = 0x7e800000, + .ro = 0xffffffcf, + .w1c = 0x30, + .unimp = 0xffffffff, + },{ .name = "GUCTL1", .addr = A_GUCTL1, + .reset = 0x198a, + .ro = 0x7800, + .unimp = 0xffffffff, + },{ .name = "GSNPSID", .addr = A_GSNPSID, + .reset = 0x5533330a, + .ro = 0xffffffff, + },{ .name = "GGPIO", .addr = A_GGPIO, + .ro = 0xffff, + .unimp = 0xffffffff, + },{ .name = "GUID", .addr = A_GUID, + .reset = 0x12345678, .post_write = usb_dwc3_guid_postw, + },{ .name = "GUCTL", .addr = A_GUCTL, + .reset = 0x0c808010, + .ro = 0x1c8000, + .unimp = 0xffffffff, + },{ .name = "GBUSERRADDRLO", .addr = A_GBUSERRADDRLO, + .ro = 0xffffffff, + },{ .name = "GBUSERRADDRHI", .addr = A_GBUSERRADDRHI, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS0", .addr = A_GHWPARAMS0, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS1", .addr = A_GHWPARAMS1, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS2", .addr = A_GHWPARAMS2, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS3", .addr = A_GHWPARAMS3, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS4", .addr = A_GHWPARAMS4, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS5", .addr = A_GHWPARAMS5, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS6", .addr = A_GHWPARAMS6, + .ro = 0xffffffff, + },{ .name = "GHWPARAMS7", .addr = A_GHWPARAMS7, + .ro = 0xffffffff, + },{ .name = "GDBGFIFOSPACE", .addr = A_GDBGFIFOSPACE, + .reset = 0xa0000, + .ro = 0xfffffe00, + .unimp = 0xffffffff, + },{ .name = "GUCTL2", .addr = A_GUCTL2, + .reset = 0x40d, + .ro = 0x2000, + .unimp = 0xffffffff, + },{ .name = "GUSB2PHYCFG", .addr = A_GUSB2PHYCFG, + .reset = 0x40102410, + .ro = 0x1e014030, + .unimp = 0xffffffff, + },{ .name = "GUSB2I2CCTL", .addr = A_GUSB2I2CCTL, + .ro = 0xffffffff, + .unimp = 0xffffffff, + },{ .name = "GUSB2PHYACC_ULPI", .addr = A_GUSB2PHYACC_ULPI, + .ro = 0xfd000000, + .unimp = 0xffffffff, + },{ .name = "GTXFIFOSIZ0", .addr = A_GTXFIFOSIZ0, + .reset = 0x2c7000a, + .unimp = 0xffffffff, + },{ .name = "GTXFIFOSIZ1", .addr = A_GTXFIFOSIZ1, + .reset = 0x2d10103, + .unimp = 0xffffffff, + },{ .name = "GTXFIFOSIZ2", .addr = A_GTXFIFOSIZ2, + .reset = 0x3d40103, + .unimp = 0xffffffff, + },{ .name = "GTXFIFOSIZ3", .addr = A_GTXFIFOSIZ3, + .reset = 0x4d70083, + .unimp = 0xffffffff, + },{ .name = "GTXFIFOSIZ4", .addr = A_GTXFIFOSIZ4, + .reset = 0x55a0083, + .unimp = 0xffffffff, + },{ .name = "GTXFIFOSIZ5", .addr = A_GTXFIFOSIZ5, + .reset = 0x5dd0083, + .unimp = 0xffffffff, + },{ .name = "GRXFIFOSIZ0", .addr = A_GRXFIFOSIZ0, + .reset = 0x1c20105, + .unimp = 0xffffffff, + },{ .name = "GRXFIFOSIZ1", .addr = A_GRXFIFOSIZ1, + .reset = 0x2c70000, + .unimp = 0xffffffff, + },{ .name = "GRXFIFOSIZ2", .addr = A_GRXFIFOSIZ2, + .reset = 0x2c70000, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRLO_0", .addr = A_GEVNTADRLO_0, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRHI_0", .addr = A_GEVNTADRHI_0, + .unimp = 0xffffffff, + },{ .name = "GEVNTSIZ_0", .addr = A_GEVNTSIZ_0, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GEVNTCOUNT_0", .addr = A_GEVNTCOUNT_0, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRLO_1", .addr = A_GEVNTADRLO_1, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRHI_1", .addr = A_GEVNTADRHI_1, + .unimp = 0xffffffff, + },{ .name = "GEVNTSIZ_1", .addr = A_GEVNTSIZ_1, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GEVNTCOUNT_1", .addr = A_GEVNTCOUNT_1, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRLO_2", .addr = A_GEVNTADRLO_2, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRHI_2", .addr = A_GEVNTADRHI_2, + .unimp = 0xffffffff, + },{ .name = "GEVNTSIZ_2", .addr = A_GEVNTSIZ_2, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GEVNTCOUNT_2", .addr = A_GEVNTCOUNT_2, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRLO_3", .addr = A_GEVNTADRLO_3, + .unimp = 0xffffffff, + },{ .name = "GEVNTADRHI_3", .addr = A_GEVNTADRHI_3, + .unimp = 0xffffffff, + },{ .name = "GEVNTSIZ_3", .addr = A_GEVNTSIZ_3, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GEVNTCOUNT_3", .addr = A_GEVNTCOUNT_3, + .ro = 0x7fff0000, + .unimp = 0xffffffff, + },{ .name = "GHWPARAMS8", .addr = A_GHWPARAMS8, + .ro = 0xffffffff, + },{ .name = "GTXFIFOPRIDEV", .addr = A_GTXFIFOPRIDEV, + .ro = 0xffffffc0, + .unimp = 0xffffffff, + },{ .name = "GTXFIFOPRIHST", .addr = A_GTXFIFOPRIHST, + .ro = 0xfffffff8, + .unimp = 0xffffffff, + },{ .name = "GRXFIFOPRIHST", .addr = A_GRXFIFOPRIHST, + .ro = 0xfffffff8, + .unimp = 0xffffffff, + },{ .name = "GDMAHLRATIO", .addr = A_GDMAHLRATIO, + .ro = 0xffffe0e0, + .unimp = 0xffffffff, + },{ .name = "GFLADJ", .addr = A_GFLADJ, + .reset = 0xc83f020, + .rsvd = 0x40, + .ro = 0x400040, + .unimp = 0xffffffff, + } +}; + +static void usb_dwc3_reset(DeviceState *dev) +{ + USBDWC3 *s = USB_DWC3(dev); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) { + switch (i) { + case R_GHWPARAMS0...R_GHWPARAMS7: + break; + case R_GHWPARAMS8: + break; + default: + register_reset(&s->regs_info[i]); + }; + } + + xhci_sysbus_reset(DEVICE(&s->sysbus_xhci)); +} + +static const MemoryRegionOps usb_dwc3_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void usb_dwc3_realize(DeviceState *dev, Error **errp) +{ + USBDWC3 *s = USB_DWC3(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Error *err = NULL; + + sysbus_realize(SYS_BUS_DEVICE(&s->sysbus_xhci), &err); + if (err) { + error_propagate(errp, err); + return; + } + + memory_region_add_subregion(&s->iomem, 0, + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->sysbus_xhci), 0)); + sysbus_init_mmio(sbd, &s->iomem); + + /* + * Device Configuration + */ + s->regs[R_GHWPARAMS0] = 0x40204048 | s->cfg.mode; + s->regs[R_GHWPARAMS1] = 0x222493b; + s->regs[R_GHWPARAMS2] = 0x12345678; + s->regs[R_GHWPARAMS3] = 0x618c088; + s->regs[R_GHWPARAMS4] = 0x47822004; + s->regs[R_GHWPARAMS5] = 0x4202088; + s->regs[R_GHWPARAMS6] = 0x7850c20; + s->regs[R_GHWPARAMS7] = 0x0; + s->regs[R_GHWPARAMS8] = 0x478; +} + +static void usb_dwc3_init(Object *obj) +{ + USBDWC3 *s = USB_DWC3(obj); + RegisterInfoArray *reg_array; + + memory_region_init(&s->iomem, obj, TYPE_USB_DWC3, DWC3_SIZE); + reg_array = + register_init_block32(DEVICE(obj), usb_dwc3_regs_info, + ARRAY_SIZE(usb_dwc3_regs_info), + s->regs_info, s->regs, + &usb_dwc3_ops, + USB_DWC3_ERR_DEBUG, + USB_DWC3_R_MAX * 4); + memory_region_add_subregion(&s->iomem, + DWC3_GLOBAL_OFFSET, + ®_array->mem); + object_initialize_child(obj, "dwc3-xhci", &s->sysbus_xhci, + TYPE_XHCI_SYSBUS); + qdev_alias_all_properties(DEVICE(&s->sysbus_xhci), obj); + + s->cfg.mode = HOST_MODE; +} + +static const VMStateDescription vmstate_usb_dwc3 = { + .name = "usb-dwc3", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, USBDWC3, USB_DWC3_R_MAX), + VMSTATE_UINT8(cfg.mode, USBDWC3), + VMSTATE_UINT32(cfg.dwc_usb3_user, USBDWC3), + VMSTATE_END_OF_LIST() + } +}; + +static Property usb_dwc3_properties[] = { + DEFINE_PROP_UINT32("DWC_USB3_USERID", USBDWC3, cfg.dwc_usb3_user, + 0x12345678), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_dwc3_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = usb_dwc3_reset; + dc->realize = usb_dwc3_realize; + dc->vmsd = &vmstate_usb_dwc3; + device_class_set_props(dc, usb_dwc3_properties); +} + +static const TypeInfo usb_dwc3_info = { + .name = TYPE_USB_DWC3, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(USBDWC3), + .class_init = usb_dwc3_class_init, + .instance_init = usb_dwc3_init, +}; + +static void usb_dwc3_register_types(void) +{ + type_register_static(&usb_dwc3_info); +} + +type_init(usb_dwc3_register_types) diff --git a/hw/usb/hcd-ehci-pci.c b/hw/usb/hcd-ehci-pci.c new file mode 100644 index 000000000..4c37c8e22 --- /dev/null +++ b/hw/usb/hcd-ehci-pci.c @@ -0,0 +1,235 @@ +/* + * QEMU USB EHCI Emulation + * + * 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.1 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 program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "hw/usb/hcd-ehci.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "qemu/range.h" + +typedef struct EHCIPCIInfo { + const char *name; + uint16_t vendor_id; + uint16_t device_id; + uint8_t revision; + bool companion; +} EHCIPCIInfo; + +static void usb_ehci_pci_realize(PCIDevice *dev, Error **errp) +{ + EHCIPCIState *i = PCI_EHCI(dev); + EHCIState *s = &i->ehci; + uint8_t *pci_conf = dev->config; + + 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 # (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 */ + + s->irq = pci_allocate_irq(dev); + s->as = pci_get_address_space(dev); + + usb_ehci_realize(s, DEVICE(dev), NULL); + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem); +} + +static void usb_ehci_pci_init(Object *obj) +{ + DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE); + EHCIPCIState *i = PCI_EHCI(obj); + EHCIState *s = &i->ehci; + + s->caps[0x09] = 0x68; /* EECP */ + + s->capsbase = 0x00; + s->opregbase = 0x20; + s->portscbase = 0x44; + s->portnr = NB_PORTS; + + if (!dc->hotpluggable) { + s->companion_enable = true; + } + + usb_ehci_init(s, DEVICE(obj)); +} + +static void usb_ehci_pci_finalize(Object *obj) +{ + EHCIPCIState *i = PCI_EHCI(obj); + EHCIState *s = &i->ehci; + + usb_ehci_finalize(s); +} + +static void usb_ehci_pci_exit(PCIDevice *dev) +{ + EHCIPCIState *i = PCI_EHCI(dev); + EHCIState *s = &i->ehci; + + usb_ehci_unrealize(s, DEVICE(dev)); + + g_free(s->irq); + s->irq = NULL; +} + +static void usb_ehci_pci_reset(DeviceState *dev) +{ + PCIDevice *pci_dev = PCI_DEVICE(dev); + EHCIPCIState *i = PCI_EHCI(pci_dev); + EHCIState *s = &i->ehci; + + ehci_reset(s); +} + +static void usb_ehci_pci_write_config(PCIDevice *dev, uint32_t addr, + uint32_t val, int l) +{ + EHCIPCIState *i = PCI_EHCI(dev); + bool busmaster; + + pci_default_write_config(dev, addr, val, l); + + if (!range_covers_byte(addr, l, PCI_COMMAND)) { + return; + } + busmaster = pci_get_word(dev->config + PCI_COMMAND) & PCI_COMMAND_MASTER; + i->ehci.as = busmaster ? pci_get_address_space(dev) : &address_space_memory; +} + +static Property ehci_pci_properties[] = { + DEFINE_PROP_UINT32("maxframes", EHCIPCIState, ehci.maxframes, 128), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription vmstate_ehci_pci = { + .name = "ehci", + .version_id = 2, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(pcidev, EHCIPCIState), + VMSTATE_STRUCT(ehci, EHCIPCIState, 2, vmstate_ehci, EHCIState), + VMSTATE_END_OF_LIST() + } +}; + +static void ehci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->realize = usb_ehci_pci_realize; + k->exit = usb_ehci_pci_exit; + k->class_id = PCI_CLASS_SERIAL_USB; + k->config_write = usb_ehci_pci_write_config; + dc->vmsd = &vmstate_ehci_pci; + device_class_set_props(dc, ehci_pci_properties); + dc->reset = usb_ehci_pci_reset; +} + +static const TypeInfo ehci_pci_type_info = { + .name = TYPE_PCI_EHCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(EHCIPCIState), + .instance_init = usb_ehci_pci_init, + .instance_finalize = usb_ehci_pci_finalize, + .abstract = true, + .class_init = ehci_class_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void ehci_data_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + EHCIPCIInfo *i = data; + + k->vendor_id = i->vendor_id; + k->device_id = i->device_id; + k->revision = i->revision; + set_bit(DEVICE_CATEGORY_USB, dc->categories); + if (i->companion) { + dc->hotpluggable = false; + } +} + +static struct EHCIPCIInfo ehci_pci_info[] = { + { + .name = "usb-ehci", + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801D, /* ich4 */ + .revision = 0x10, + },{ + .name = "ich9-usb-ehci1", /* 00:1d.7 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI1, + .revision = 0x03, + .companion = true, + },{ + .name = "ich9-usb-ehci2", /* 00:1a.7 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI2, + .revision = 0x03, + .companion = true, + } +}; + +static void ehci_pci_register_types(void) +{ + TypeInfo ehci_type_info = { + .parent = TYPE_PCI_EHCI, + .class_init = ehci_data_class_init, + }; + int i; + + type_register_static(&ehci_pci_type_info); + + for (i = 0; i < ARRAY_SIZE(ehci_pci_info); i++) { + ehci_type_info.name = ehci_pci_info[i].name; + ehci_type_info.class_data = ehci_pci_info + i; + type_register(&ehci_type_info); + } +} + +type_init(ehci_pci_register_types) diff --git a/hw/usb/hcd-ehci-sysbus.c b/hw/usb/hcd-ehci-sysbus.c new file mode 100644 index 000000000..a12e21884 --- /dev/null +++ b/hw/usb/hcd-ehci-sysbus.c @@ -0,0 +1,305 @@ +/* + * QEMU USB EHCI Emulation + * + * 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.1 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 program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "hw/usb/hcd-ehci.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +static const VMStateDescription vmstate_ehci_sysbus = { + .name = "ehci-sysbus", + .version_id = 2, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(ehci, EHCISysBusState, 2, vmstate_ehci, EHCIState), + VMSTATE_END_OF_LIST() + } +}; + +static Property ehci_sysbus_properties[] = { + DEFINE_PROP_UINT32("maxframes", EHCISysBusState, ehci.maxframes, 128), + DEFINE_PROP_BOOL("companion-enable", EHCISysBusState, ehci.companion_enable, + false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_ehci_sysbus_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *d = SYS_BUS_DEVICE(dev); + EHCISysBusState *i = SYS_BUS_EHCI(dev); + EHCIState *s = &i->ehci; + + usb_ehci_realize(s, dev, errp); + sysbus_init_irq(d, &s->irq); +} + +static void usb_ehci_sysbus_reset(DeviceState *dev) +{ + SysBusDevice *d = SYS_BUS_DEVICE(dev); + EHCISysBusState *i = SYS_BUS_EHCI(d); + EHCIState *s = &i->ehci; + + ehci_reset(s); +} + +static void ehci_sysbus_init(Object *obj) +{ + SysBusDevice *d = SYS_BUS_DEVICE(obj); + EHCISysBusState *i = SYS_BUS_EHCI(obj); + SysBusEHCIClass *sec = SYS_BUS_EHCI_GET_CLASS(obj); + EHCIState *s = &i->ehci; + + s->capsbase = sec->capsbase; + s->opregbase = sec->opregbase; + s->portscbase = sec->portscbase; + s->portnr = sec->portnr; + s->as = &address_space_memory; + + usb_ehci_init(s, DEVICE(obj)); + sysbus_init_mmio(d, &s->mem); +} + +static void ehci_sysbus_finalize(Object *obj) +{ + EHCISysBusState *i = SYS_BUS_EHCI(obj); + EHCIState *s = &i->ehci; + + usb_ehci_finalize(s); +} + +static void ehci_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(klass); + + sec->portscbase = 0x44; + sec->portnr = NB_PORTS; + + dc->realize = usb_ehci_sysbus_realize; + dc->vmsd = &vmstate_ehci_sysbus; + device_class_set_props(dc, ehci_sysbus_properties); + dc->reset = usb_ehci_sysbus_reset; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_type_info = { + .name = TYPE_SYS_BUS_EHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(EHCISysBusState), + .instance_init = ehci_sysbus_init, + .instance_finalize = ehci_sysbus_finalize, + .abstract = true, + .class_init = ehci_sysbus_class_init, + .class_size = sizeof(SysBusEHCIClass), +}; + +static void ehci_platform_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x0; + sec->opregbase = 0x20; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_platform_type_info = { + .name = TYPE_PLATFORM_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .class_init = ehci_platform_class_init, +}; + +static void ehci_exynos4210_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x0; + sec->opregbase = 0x10; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_exynos4210_type_info = { + .name = TYPE_EXYNOS4210_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .class_init = ehci_exynos4210_class_init, +}; + +static void ehci_aw_h3_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x0; + sec->opregbase = 0x10; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_aw_h3_type_info = { + .name = TYPE_AW_H3_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .class_init = ehci_aw_h3_class_init, +}; + +static void ehci_npcm7xx_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x0; + sec->opregbase = 0x10; + sec->portscbase = 0x44; + sec->portnr = 1; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_npcm7xx_type_info = { + .name = TYPE_NPCM7XX_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .class_init = ehci_npcm7xx_class_init, +}; + +static void ehci_tegra2_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x100; + sec->opregbase = 0x140; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_tegra2_type_info = { + .name = TYPE_TEGRA2_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .class_init = ehci_tegra2_class_init, +}; + +static void ehci_ppc4xx_init(Object *o) +{ + EHCISysBusState *s = SYS_BUS_EHCI(o); + + s->ehci.companion_enable = true; +} + +static void ehci_ppc4xx_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x0; + sec->opregbase = 0x10; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_ppc4xx_type_info = { + .name = TYPE_PPC4xx_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .class_init = ehci_ppc4xx_class_init, + .instance_init = ehci_ppc4xx_init, +}; + +/* + * Faraday FUSBH200 USB 2.0 EHCI + */ + +/** + * FUSBH200EHCIRegs: + * @FUSBH200_REG_EOF_ASTR: EOF/Async. Sleep Timer Register + * @FUSBH200_REG_BMCSR: Bus Monitor Control/Status Register + */ +enum FUSBH200EHCIRegs { + FUSBH200_REG_EOF_ASTR = 0x34, + FUSBH200_REG_BMCSR = 0x40, +}; + +static uint64_t fusbh200_ehci_read(void *opaque, hwaddr addr, unsigned size) +{ + EHCIState *s = opaque; + hwaddr off = s->opregbase + s->portscbase + 4 * s->portnr + addr; + + switch (off) { + case FUSBH200_REG_EOF_ASTR: + return 0x00000041; + case FUSBH200_REG_BMCSR: + /* High-Speed, VBUS valid, interrupt level-high active */ + return (2 << 9) | (1 << 8) | (1 << 3); + } + + return 0; +} + +static void fusbh200_ehci_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ +} + +static const MemoryRegionOps fusbh200_ehci_mmio_ops = { + .read = fusbh200_ehci_read, + .write = fusbh200_ehci_write, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void fusbh200_ehci_init(Object *obj) +{ + EHCISysBusState *i = SYS_BUS_EHCI(obj); + FUSBH200EHCIState *f = FUSBH200_EHCI(obj); + EHCIState *s = &i->ehci; + + memory_region_init_io(&f->mem_vendor, OBJECT(f), &fusbh200_ehci_mmio_ops, s, + "fusbh200", 0x4c); + memory_region_add_subregion(&s->mem, + s->opregbase + s->portscbase + 4 * s->portnr, + &f->mem_vendor); +} + +static void fusbh200_ehci_class_init(ObjectClass *oc, void *data) +{ + SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); + + sec->capsbase = 0x0; + sec->opregbase = 0x10; + sec->portscbase = 0x20; + sec->portnr = 1; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo ehci_fusbh200_type_info = { + .name = TYPE_FUSBH200_EHCI, + .parent = TYPE_SYS_BUS_EHCI, + .instance_size = sizeof(FUSBH200EHCIState), + .instance_init = fusbh200_ehci_init, + .class_init = fusbh200_ehci_class_init, +}; + +static void ehci_sysbus_register_types(void) +{ + type_register_static(&ehci_type_info); + type_register_static(&ehci_platform_type_info); + type_register_static(&ehci_exynos4210_type_info); + type_register_static(&ehci_aw_h3_type_info); + type_register_static(&ehci_npcm7xx_type_info); + type_register_static(&ehci_tegra2_type_info); + type_register_static(&ehci_ppc4xx_type_info); + type_register_static(&ehci_fusbh200_type_info); +} + +type_init(ehci_sysbus_register_types) diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c new file mode 100644 index 000000000..6caa7ac6c --- /dev/null +++ b/hw/usb/hcd-ehci.c @@ -0,0 +1,2598 @@ +/* + * QEMU USB EHCI Emulation + * + * Copyright(c) 2008 Emutex Ltd. (address@hidden) + * Copyright(c) 2011-2012 Red Hat, Inc. + * + * Red Hat Authors: + * Gerd Hoffmann <kraxel@redhat.com> + * Hans de Goede <hdegoede@redhat.com> + * + * 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.1 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 program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/irq.h" +#include "hw/usb/ehci-regs.h" +#include "hw/usb/hcd-ehci.h" +#include "migration/vmstate.h" +#include "trace.h" +#include "qemu/error-report.h" +#include "qemu/main-loop.h" +#include "sysemu/runstate.h" + +#define FRAME_TIMER_FREQ 1000 +#define FRAME_TIMER_NS (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ) +#define UFRAME_TIMER_NS (FRAME_TIMER_NS / 8) + +#define NB_MAXINTRATE 8 // Max rate at which controller issues ints +#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction +#define MAX_QH 100 // Max allowable queue heads in a chain +#define MIN_UFR_PER_TICK 24 /* Min frames to process when catching up */ +#define PERIODIC_ACTIVE 512 /* Micro-frames */ + +/* 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 + +#define SET_LAST_RUN_CLOCK(s) \ + (s)->last_run_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + +/* 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[] = { + [USBCMD] = "USBCMD", + [USBSTS] = "USBSTS", + [USBINTR] = "USBINTR", + [FRINDEX] = "FRINDEX", + [PERIODICLISTBASE] = "P-LIST BASE", + [ASYNCLISTADDR] = "A-LIST ADDR", + [CONFIGFLAG] = "CONFIGFLAG", +}; + +static int ehci_state_executing(EHCIQueue *q); +static int ehci_state_writeback(EHCIQueue *q); +static int ehci_state_advqueue(EHCIQueue *q); +static int ehci_fill_queue(EHCIPacket *p); +static void ehci_free_packet(EHCIPacket *p); + +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(hwaddr 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; +} + +/* update irq line */ +static inline void ehci_update_irq(EHCIState *s) +{ + int level = 0; + + if ((s->usbsts & USBINTR_MASK) & s->usbintr) { + level = 1; + } + + trace_usb_ehci_irq(level, s->frindex, s->usbsts, s->usbintr); + qemu_set_irq(s->irq, level); +} + +/* flag interrupt condition */ +static inline void ehci_raise_irq(EHCIState *s, int intr) +{ + if (intr & (USBSTS_PCD | USBSTS_FLR | USBSTS_HSE)) { + s->usbsts |= intr; + ehci_update_irq(s); + } else { + s->usbsts_pending |= intr; + } +} + +/* + * Commit pending interrupts (added via ehci_raise_irq), + * at the rate allowed by "Interrupt Threshold Control". + */ +static inline void ehci_commit_irq(EHCIState *s) +{ + uint32_t itc; + + if (!s->usbsts_pending) { + return; + } + if (s->usbsts_frindex > s->frindex) { + return; + } + + itc = (s->usbcmd >> 16) & 0xff; + s->usbsts |= s->usbsts_pending; + s->usbsts_pending = 0; + s->usbsts_frindex = s->frindex + itc; + ehci_update_irq(s); +} + +static void ehci_update_halt(EHCIState *s) +{ + if (s->usbcmd & USBCMD_RUNSTOP) { + ehci_clear_usbsts(s, USBSTS_HALT); + } else { + if (s->astate == EST_INACTIVE && s->pstate == EST_INACTIVE) { + ehci_set_usbsts(s, USBSTS_HALT); + } + } +} + +static void ehci_set_state(EHCIState *s, int async, int state) +{ + if (async) { + trace_usb_ehci_state("async", state2str(state)); + s->astate = state; + if (s->astate == EST_INACTIVE) { + ehci_clear_usbsts(s, USBSTS_ASS); + ehci_update_halt(s); + } else { + ehci_set_usbsts(s, USBSTS_ASS); + } + } else { + trace_usb_ehci_state("periodic", state2str(state)); + s->pstate = state; + if (s->pstate == EST_INACTIVE) { + ehci_clear_usbsts(s, USBSTS_PSS); + ehci_update_halt(s); + } else { + ehci_set_usbsts(s, USBSTS_PSS); + } + } +} + +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, hwaddr 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, hwaddr 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, hwaddr 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, hwaddr addr, + EHCIsitd *sitd) +{ + trace_usb_ehci_sitd(addr, sitd->next, + (bool)(sitd->results & SITD_RESULTS_ACTIVE)); +} + +static void ehci_trace_guest_bug(EHCIState *s, const char *message) +{ + trace_usb_ehci_guest_bug(message); +} + +static inline bool ehci_enabled(EHCIState *s) +{ + return s->usbcmd & USBCMD_RUNSTOP; +} + +static inline bool ehci_async_enabled(EHCIState *s) +{ + return ehci_enabled(s) && (s->usbcmd & USBCMD_ASE); +} + +static inline bool ehci_periodic_enabled(EHCIState *s) +{ + return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE); +} + +/* 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; + + if (!ehci->as) { + ehci_raise_irq(ehci, USBSTS_HSE); + ehci->usbcmd &= ~USBCMD_RUNSTOP; + trace_usb_ehci_dma_error(); + return -1; + } + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + dma_memory_read(ehci->as, addr, buf, sizeof(*buf)); + *buf = le32_to_cpu(*buf); + } + + return num; +} + +/* 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; + + if (!ehci->as) { + ehci_raise_irq(ehci, USBSTS_HSE); + ehci->usbcmd &= ~USBCMD_RUNSTOP; + trace_usb_ehci_dma_error(); + return -1; + } + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + uint32_t tmp = cpu_to_le32(*buf); + dma_memory_write(ehci->as, addr, &tmp, sizeof(tmp)); + } + + return num; +} + +static int ehci_get_pid(EHCIqtd *qtd) +{ + switch (get_field(qtd->token, QTD_TOKEN_PID)) { + case 0: + return USB_TOKEN_OUT; + case 1: + return USB_TOKEN_IN; + case 2: + return USB_TOKEN_SETUP; + default: + fprintf(stderr, "bad token\n"); + return 0; + } +} + +static bool ehci_verify_qh(EHCIQueue *q, EHCIqh *qh) +{ + uint32_t devaddr = get_field(qh->epchar, QH_EPCHAR_DEVADDR); + uint32_t endp = get_field(qh->epchar, QH_EPCHAR_EP); + if ((devaddr != get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)) || + (endp != get_field(q->qh.epchar, QH_EPCHAR_EP)) || + (qh->current_qtd != q->qh.current_qtd) || + (q->async && qh->next_qtd != q->qh.next_qtd) || + (memcmp(&qh->altnext_qtd, &q->qh.altnext_qtd, + 7 * sizeof(uint32_t)) != 0) || + (q->dev != NULL && q->dev->addr != devaddr)) { + return false; + } else { + return true; + } +} + +static bool ehci_verify_qtd(EHCIPacket *p, EHCIqtd *qtd) +{ + if (p->qtdaddr != p->queue->qtdaddr || + (p->queue->async && !NLPTR_TBIT(p->qtd.next) && + (p->qtd.next != qtd->next)) || + (!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd->altnext)) || + p->qtd.token != qtd->token || + p->qtd.bufptr[0] != qtd->bufptr[0]) { + return false; + } else { + return true; + } +} + +static bool ehci_verify_pid(EHCIQueue *q, EHCIqtd *qtd) +{ + int ep = get_field(q->qh.epchar, QH_EPCHAR_EP); + int pid = ehci_get_pid(qtd); + + /* Note the pid changing is normal for ep 0 (the control ep) */ + if (q->last_pid && ep != 0 && pid != q->last_pid) { + return false; + } else { + return true; + } +} + +/* Finish executing and writeback a packet outside of the regular + fetchqh -> fetchqtd -> execute -> writeback cycle */ +static void ehci_writeback_async_complete_packet(EHCIPacket *p) +{ + EHCIQueue *q = p->queue; + EHCIqtd qtd; + EHCIqh qh; + int state; + + /* Verify the qh + qtd, like we do when going through fetchqh & fetchqtd */ + get_dwords(q->ehci, NLPTR_GET(q->qhaddr), + (uint32_t *) &qh, sizeof(EHCIqh) >> 2); + get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), + (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2); + if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) { + p->async = EHCI_ASYNC_INITIALIZED; + ehci_free_packet(p); + return; + } + + state = ehci_get_state(q->ehci, q->async); + ehci_state_executing(q); + ehci_state_writeback(q); /* Frees the packet! */ + if (!(q->qh.token & QTD_TOKEN_HALT)) { + ehci_state_advqueue(q); + } + ehci_set_state(q->ehci, q->async, state); +} + +/* packet management */ + +static EHCIPacket *ehci_alloc_packet(EHCIQueue *q) +{ + EHCIPacket *p; + + p = g_new0(EHCIPacket, 1); + p->queue = q; + usb_packet_init(&p->packet); + QTAILQ_INSERT_TAIL(&q->packets, p, next); + trace_usb_ehci_packet_action(p->queue, p, "alloc"); + return p; +} + +static void ehci_free_packet(EHCIPacket *p) +{ + if (p->async == EHCI_ASYNC_FINISHED && + !(p->queue->qh.token & QTD_TOKEN_HALT)) { + ehci_writeback_async_complete_packet(p); + return; + } + trace_usb_ehci_packet_action(p->queue, p, "free"); + if (p->async == EHCI_ASYNC_INFLIGHT) { + usb_cancel_packet(&p->packet); + } + if (p->async == EHCI_ASYNC_FINISHED && + p->packet.status == USB_RET_SUCCESS) { + fprintf(stderr, + "EHCI: Dropping completed packet from halted %s ep %02X\n", + (p->pid == USB_TOKEN_IN) ? "in" : "out", + get_field(p->queue->qh.epchar, QH_EPCHAR_EP)); + } + if (p->async != EHCI_ASYNC_NONE) { + usb_packet_unmap(&p->packet, &p->sgl); + qemu_sglist_destroy(&p->sgl); + } + QTAILQ_REMOVE(&p->queue->packets, p, next); + usb_packet_cleanup(&p->packet); + g_free(p); +} + +/* queue management */ + +static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + EHCIQueue *q; + + q = g_malloc0(sizeof(*q)); + q->ehci = ehci; + q->qhaddr = addr; + q->async = async; + QTAILQ_INIT(&q->packets); + QTAILQ_INSERT_HEAD(head, q, next); + trace_usb_ehci_queue_action(q, "alloc"); + return q; +} + +static void ehci_queue_stopped(EHCIQueue *q) +{ + int endp = get_field(q->qh.epchar, QH_EPCHAR_EP); + + if (!q->last_pid || !q->dev) { + return; + } + + usb_device_ep_stopped(q->dev, usb_ep_get(q->dev, q->last_pid, endp)); +} + +static int ehci_cancel_queue(EHCIQueue *q) +{ + EHCIPacket *p; + int packets = 0; + + p = QTAILQ_FIRST(&q->packets); + if (p == NULL) { + goto leave; + } + + trace_usb_ehci_queue_action(q, "cancel"); + do { + ehci_free_packet(p); + packets++; + } while ((p = QTAILQ_FIRST(&q->packets)) != NULL); + +leave: + ehci_queue_stopped(q); + return packets; +} + +static int ehci_reset_queue(EHCIQueue *q) +{ + int packets; + + trace_usb_ehci_queue_action(q, "reset"); + packets = ehci_cancel_queue(q); + q->dev = NULL; + q->qtdaddr = 0; + q->last_pid = 0; + return packets; +} + +static void ehci_free_queue(EHCIQueue *q, const char *warn) +{ + EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues; + int cancelled; + + trace_usb_ehci_queue_action(q, "free"); + cancelled = ehci_cancel_queue(q); + if (warn && cancelled > 0) { + ehci_trace_guest_bug(q->ehci, warn); + } + 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) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + const char *warn = async ? "guest unlinked busy QH" : NULL; + uint64_t maxage = FRAME_TIMER_NS * ehci->maxframes * 4; + EHCIQueue *q, *tmp; + + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { + if (q->seen) { + q->seen = 0; + q->ts = ehci->last_run_ns; + continue; + } + if (ehci->last_run_ns < q->ts + maxage) { + continue; + } + ehci_free_queue(q, warn); + } +} + +static void ehci_queues_rip_unseen(EHCIState *ehci, int async) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + EHCIQueue *q, *tmp; + + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { + if (!q->seen) { + ehci_free_queue(q, NULL); + } + } +} + +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 (q->dev != dev) { + continue; + } + ehci_free_queue(q, NULL); + } +} + +static void ehci_queues_rip_all(EHCIState *ehci, int async) +{ + EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues; + const char *warn = async ? "guest stopped busy async schedule" : NULL; + EHCIQueue *q, *tmp; + + QTAILQ_FOREACH_SAFE(q, head, next, tmp) { + ehci_free_queue(q, warn); + } +} + +/* 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]; + const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci"; + + trace_usb_ehci_port_attach(port->index, owner, 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_raise_irq(s, USBSTS_PCD); +} + +static void ehci_detach(USBPort *port) +{ + EHCIState *s = port->opaque; + uint32_t *portsc = &s->portsc[port->index]; + const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci"; + + trace_usb_ehci_port_detach(port->index, owner); + + 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_SUSPEND); + *portsc |= PORTSC_CSC; + + ehci_raise_irq(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); + 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); + } + return; + } + + if (*portsc & PORTSC_SUSPEND) { + trace_usb_ehci_port_wakeup(port->index); + *portsc |= PORTSC_FPRES; + ehci_raise_irq(s, USBSTS_PCD); + } + + qemu_bh_schedule(s->async_bh); +} + +static void ehci_register_companion(USBBus *bus, USBPort *ports[], + uint32_t portcount, uint32_t firstport, + Error **errp) +{ + EHCIState *s = container_of(bus, EHCIState, bus); + uint32_t i; + + if (firstport + portcount > NB_PORTS) { + error_setg(errp, "firstport must be between 0 and %u", + NB_PORTS - portcount); + return; + } + + for (i = 0; i < portcount; i++) { + if (s->companion_ports[firstport + i]) { + error_setg(errp, "firstport %u asks for ports %u-%u," + " but port %u has a companion assigned already", + firstport, firstport, firstport + portcount - 1, + firstport + i); + return; + } + } + + 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->caps[0x05] = (s->companion_count << 4) | portcount; +} + +static void ehci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep, + unsigned int stream) +{ + EHCIState *s = container_of(bus, EHCIState, bus); + uint32_t portsc = s->portsc[ep->dev->port->index]; + + if (portsc & PORTSC_POWNER) { + return; + } + + s->periodic_sched_active = PERIODIC_ACTIVE; + qemu_bh_schedule(s->async_bh); +} + +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 */ +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->opreg, 0x00, sizeof(s->opreg)); + memset(&s->portsc, 0x00, sizeof(s->portsc)); + + s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH; + s->usbsts = USBSTS_HALT; + s->usbsts_pending = 0; + s->usbsts_frindex = 0; + ehci_update_irq(s); + + s->astate = EST_INACTIVE; + s->pstate = EST_INACTIVE; + + 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); + timer_del(s->frame_timer); + qemu_bh_cancel(s->async_bh); +} + +static uint64_t ehci_caps_read(void *ptr, hwaddr addr, + unsigned size) +{ + EHCIState *s = ptr; + return s->caps[addr]; +} + +static void ehci_caps_write(void *ptr, hwaddr addr, + uint64_t val, unsigned size) +{ +} + +static uint64_t ehci_opreg_read(void *ptr, hwaddr addr, + unsigned size) +{ + EHCIState *s = ptr; + uint32_t val; + + switch (addr) { + case FRINDEX: + /* Round down to mult of 8, else it can go backwards on migration */ + val = s->frindex & ~7; + break; + default: + val = s->opreg[addr >> 2]; + } + + trace_usb_ehci_opreg_read(addr + s->opregbase, addr2str(addr), val); + return val; +} + +static uint64_t ehci_port_read(void *ptr, hwaddr addr, + unsigned size) +{ + EHCIState *s = ptr; + uint32_t val; + + val = s->portsc[addr >> 2]; + trace_usb_ehci_portsc_read(addr + s->portscbase, addr >> 2, val); + return val; +} + +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 ehci_port_write(void *ptr, hwaddr addr, + uint64_t val, unsigned size) +{ + EHCIState *s = ptr; + int port = addr >> 2; + uint32_t *portsc = &s->portsc[port]; + uint32_t old = *portsc; + USBDevice *dev = s->ports[port].dev; + + trace_usb_ehci_portsc_write(addr + s->portscbase, addr >> 2, val); + + /* 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; + } + } + + if ((val & PORTSC_SUSPEND) && !(*portsc & PORTSC_SUSPEND)) { + trace_usb_ehci_port_suspend(port); + } + if (!(val & PORTSC_FPRES) && (*portsc & PORTSC_FPRES)) { + trace_usb_ehci_port_resume(port); + val &= ~PORTSC_SUSPEND; + } + + *portsc &= ~PORTSC_RO_MASK; + *portsc |= val; + trace_usb_ehci_portsc_change(addr + s->portscbase, addr >> 2, *portsc, old); +} + +static void ehci_opreg_write(void *ptr, hwaddr addr, + uint64_t val, unsigned size) +{ + EHCIState *s = ptr; + uint32_t *mmio = s->opreg + (addr >> 2); + uint32_t old = *mmio; + int i; + + trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val); + + switch (addr) { + case USBCMD: + if (val & USBCMD_HCRESET) { + ehci_reset(s); + val = s->usbcmd; + break; + } + + /* 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", + (int)val & USBCMD_FLS); + val &= ~USBCMD_FLS; + } + + if (val & USBCMD_IAAD) { + /* + * Process IAAD immediately, otherwise the Linux IAAD watchdog may + * trigger and re-use a qh without us seeing the unlink. + */ + s->async_stepdown = 0; + qemu_bh_schedule(s->async_bh); + trace_usb_ehci_doorbell_ring(); + } + + if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) != + ((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd)) { + if (s->pstate == EST_INACTIVE) { + SET_LAST_RUN_CLOCK(s); + } + s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */ + ehci_update_halt(s); + s->async_stepdown = 0; + qemu_bh_schedule(s->async_bh); + } + break; + + case USBSTS: + val &= USBSTS_RO_MASK; // bits 6 through 31 are RO + ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC + val = s->usbsts; + ehci_update_irq(s); + break; + + case USBINTR: + val &= USBINTR_MASK; + if (ehci_enabled(s) && (USBSTS_FLR & val)) { + qemu_bh_schedule(s->async_bh); + } + break; + + case FRINDEX: + val &= 0x00003fff; /* frindex is 14bits */ + s->usbsts_frindex = val; + 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 (ehci_periodic_enabled(s)) { + fprintf(stderr, + "ehci: PERIODIC list base register set while periodic schedule\n" + " is enabled and HC is enabled\n"); + } + break; + + case ASYNCLISTADDR: + if (ehci_async_enabled(s)) { + 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_opreg_change(addr + s->opregbase, addr2str(addr), + *mmio, old); +} + +/* + * 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); +} + +// 4.10.2 + +static int ehci_qh_do_overlay(EHCIQueue *q) +{ + EHCIPacket *p = QTAILQ_FIRST(&q->packets); + int i; + int dtoggle; + int ping; + int eps; + int reload; + + assert(p != NULL); + assert(p->qtdaddr == q->qtdaddr); + + // 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 = p->qtdaddr; + q->qh.next_qtd = p->qtd.next; + q->qh.altnext_qtd = p->qtd.altnext; + q->qh.token = p->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] = p->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; + + ehci_flush_qh(q); + + return 0; +} + +static int ehci_init_transfer(EHCIPacket *p) +{ + uint32_t cpage, offset, bytes, plen; + dma_addr_t page; + + cpage = get_field(p->qtd.token, QTD_TOKEN_CPAGE); + bytes = get_field(p->qtd.token, QTD_TOKEN_TBYTES); + offset = p->qtd.bufptr[0] & ~QTD_BUFPTR_MASK; + qemu_sglist_init(&p->sgl, p->queue->ehci->device, 5, p->queue->ehci->as); + + while (bytes > 0) { + if (cpage > 4) { + fprintf(stderr, "cpage out of range (%u)\n", cpage); + qemu_sglist_destroy(&p->sgl); + return -1; + } + + page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK; + page += offset; + plen = bytes; + if (plen > 4096 - offset) { + plen = 4096 - offset; + offset = 0; + cpage++; + } + + qemu_sglist_add(&p->sgl, page, plen); + bytes -= plen; + } + return 0; +} + +static void ehci_finish_transfer(EHCIQueue *q, int len) +{ + uint32_t cpage, offset; + + if (len > 0) { + /* update cpage & offset */ + cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE); + offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK; + + offset += len; + 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) +{ + EHCIPacket *p; + 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; + } + + p = container_of(packet, EHCIPacket, packet); + assert(p->async == EHCI_ASYNC_INFLIGHT); + + if (packet->status == USB_RET_REMOVE_FROM_QUEUE) { + trace_usb_ehci_packet_action(p->queue, p, "remove"); + ehci_free_packet(p); + return; + } + + trace_usb_ehci_packet_action(p->queue, p, "wakeup"); + p->async = EHCI_ASYNC_FINISHED; + + if (!p->queue->async) { + s->periodic_sched_active = PERIODIC_ACTIVE; + } + qemu_bh_schedule(s->async_bh); +} + +static void ehci_execute_complete(EHCIQueue *q) +{ + EHCIPacket *p = QTAILQ_FIRST(&q->packets); + uint32_t tbytes; + + assert(p != NULL); + assert(p->qtdaddr == q->qtdaddr); + assert(p->async == EHCI_ASYNC_INITIALIZED || + p->async == EHCI_ASYNC_FINISHED); + + DPRINTF("execute_complete: qhaddr 0x%x, next 0x%x, qtdaddr 0x%x, " + "status %d, actual_length %d\n", + q->qhaddr, q->qh.next, q->qtdaddr, + p->packet.status, p->packet.actual_length); + + switch (p->packet.status) { + case USB_RET_SUCCESS: + break; + 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_raise_irq(q->ehci, USBSTS_ERRINT); + break; + case USB_RET_STALL: + q->qh.token |= QTD_TOKEN_HALT; + ehci_raise_irq(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_raise_irq(q->ehci, USBSTS_ERRINT); + break; + default: + /* should not be triggerable */ + fprintf(stderr, "USB invalid response %d\n", p->packet.status); + g_assert_not_reached(); + } + + /* TODO check 4.12 for splits */ + tbytes = get_field(q->qh.token, QTD_TOKEN_TBYTES); + if (tbytes && p->pid == USB_TOKEN_IN) { + tbytes -= p->packet.actual_length; + if (tbytes) { + /* 4.15.1.2 must raise int on a short input packet */ + ehci_raise_irq(q->ehci, USBSTS_INT); + if (q->async) { + q->ehci->int_req_by_async = true; + } + } + } else { + tbytes = 0; + } + DPRINTF("updating tbytes to %d\n", tbytes); + set_field(&q->qh.token, tbytes, QTD_TOKEN_TBYTES); + + ehci_finish_transfer(q, p->packet.actual_length); + usb_packet_unmap(&p->packet, &p->sgl); + qemu_sglist_destroy(&p->sgl); + p->async = EHCI_ASYNC_NONE; + + q->qh.token ^= QTD_TOKEN_DTOGGLE; + q->qh.token &= ~QTD_TOKEN_ACTIVE; + + if (q->qh.token & QTD_TOKEN_IOC) { + ehci_raise_irq(q->ehci, USBSTS_INT); + if (q->async) { + q->ehci->int_req_by_async = true; + } + } +} + +/* 4.10.3 returns "again" */ +static int ehci_execute(EHCIPacket *p, const char *action) +{ + USBEndpoint *ep; + int endp; + bool spd; + + assert(p->async == EHCI_ASYNC_NONE || + p->async == EHCI_ASYNC_INITIALIZED); + + if (!(p->qtd.token & QTD_TOKEN_ACTIVE)) { + fprintf(stderr, "Attempting to execute inactive qtd\n"); + return -1; + } + + if (get_field(p->qtd.token, QTD_TOKEN_TBYTES) > BUFF_SIZE) { + ehci_trace_guest_bug(p->queue->ehci, + "guest requested more bytes than allowed"); + return -1; + } + + if (!ehci_verify_pid(p->queue, &p->qtd)) { + ehci_queue_stopped(p->queue); /* Mark the ep in the prev dir stopped */ + } + p->pid = ehci_get_pid(&p->qtd); + p->queue->last_pid = p->pid; + endp = get_field(p->queue->qh.epchar, QH_EPCHAR_EP); + ep = usb_ep_get(p->queue->dev, p->pid, endp); + + if (p->async == EHCI_ASYNC_NONE) { + if (ehci_init_transfer(p) != 0) { + return -1; + } + + spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0); + usb_packet_setup(&p->packet, p->pid, ep, 0, p->qtdaddr, spd, + (p->qtd.token & QTD_TOKEN_IOC) != 0); + if (usb_packet_map(&p->packet, &p->sgl)) { + qemu_sglist_destroy(&p->sgl); + return -1; + } + p->async = EHCI_ASYNC_INITIALIZED; + } + + trace_usb_ehci_packet_action(p->queue, p, action); + usb_handle_packet(p->queue->dev, &p->packet); + DPRINTF("submit: qh 0x%x next 0x%x qtd 0x%x pid 0x%x len %zd endp 0x%x " + "status %d actual_length %d\n", p->queue->qhaddr, p->qtd.next, + p->qtdaddr, p->pid, p->packet.iov.size, endp, p->packet.status, + p->packet.actual_length); + + if (p->packet.actual_length > BUFF_SIZE) { + fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n"); + return -1; + } + + return 1; +} + +/* 4.7.2 + */ + +static int ehci_process_itd(EHCIState *ehci, + EHCIitd *itd, + uint32_t addr) +{ + USBDevice *dev; + USBEndpoint *ep; + uint32_t i, len, pid, dir, devaddr, endp; + uint32_t pg, off, ptr1, ptr2, max, mult; + + ehci->periodic_sched_active = PERIODIC_ACTIVE; + + 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; + len = get_field(itd->transact[i], ITD_XACT_LENGTH); + + if (len > max * mult) { + len = max * mult; + } + if (len > BUFF_SIZE || pg > 6) { + return -1; + } + + ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK); + qemu_sglist_init(&ehci->isgl, ehci->device, 2, ehci->as); + if (off + len > 4096) { + /* transfer crosses page border */ + if (pg == 6) { + qemu_sglist_destroy(&ehci->isgl); + return -1; /* avoid page pg + 1 */ + } + ptr2 = (itd->bufptr[pg + 1] & ITD_BUFPTR_MASK); + 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); + } + + dev = ehci_find_device(ehci, devaddr); + if (dev == NULL) { + ehci_trace_guest_bug(ehci, "no device found"); + ehci->ipacket.status = USB_RET_NODEV; + ehci->ipacket.actual_length = 0; + } else { + pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT; + ep = usb_ep_get(dev, pid, endp); + if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) { + usb_packet_setup(&ehci->ipacket, pid, ep, 0, addr, false, + (itd->transact[i] & ITD_XACT_IOC) != 0); + if (usb_packet_map(&ehci->ipacket, &ehci->isgl)) { + qemu_sglist_destroy(&ehci->isgl); + return -1; + } + usb_handle_packet(dev, &ehci->ipacket); + usb_packet_unmap(&ehci->ipacket, &ehci->isgl); + } else { + DPRINTF("ISOCH: attempt to addess non-iso endpoint\n"); + ehci->ipacket.status = USB_RET_NAK; + ehci->ipacket.actual_length = 0; + } + } + qemu_sglist_destroy(&ehci->isgl); + + switch (ehci->ipacket.status) { + case USB_RET_SUCCESS: + break; + default: + fprintf(stderr, "Unexpected iso usb result: %d\n", + ehci->ipacket.status); + /* 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_raise_irq(ehci, USBSTS_ERRINT); + } + break; + case USB_RET_BABBLE: + itd->transact[i] |= ITD_XACT_BABBLE; + ehci_raise_irq(ehci, USBSTS_ERRINT); + break; + case USB_RET_NAK: + /* no data for us, so do a zero-length transfer */ + ehci->ipacket.actual_length = 0; + break; + } + if (!dir) { + set_field(&itd->transact[i], len - ehci->ipacket.actual_length, + ITD_XACT_LENGTH); /* OUT */ + } else { + set_field(&itd->transact[i], ehci->ipacket.actual_length, + ITD_XACT_LENGTH); /* IN */ + } + if (itd->transact[i] & ITD_XACT_IOC) { + ehci_raise_irq(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); + + /* Find the head of the list (4.9.1.1) */ + for(i = 0; i < MAX_QH; i++) { + if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh, + sizeof(EHCIqh) >> 2) < 0) { + return 0; + } + 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 %u " + "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; + EHCIqh qh; + + entry = ehci_get_fetch_addr(ehci, async); + q = ehci_find_queue_by_qh(ehci, entry, async); + if (q == NULL) { + q = ehci_alloc_queue(ehci, entry, async); + } + + q->seen++; + if (q->seen > 1) { + /* we are going in circles -- stop processing */ + ehci_set_state(ehci, async, EST_ACTIVE); + q = NULL; + goto out; + } + + if (get_dwords(ehci, NLPTR_GET(q->qhaddr), + (uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) { + q = NULL; + goto out; + } + ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh); + + /* + * The overlay area of the qh should never be changed by the guest, + * except when idle, in which case the reset is a nop. + */ + if (!ehci_verify_qh(q, &qh)) { + if (ehci_reset_queue(q) > 0) { + ehci_trace_guest_bug(ehci, "guest updated active QH"); + } + } + q->qh = qh; + + q->transact_ctr = get_field(q->qh.epcap, QH_EPCAP_MULT); + if (q->transact_ctr == 0) { /* Guest bug in some versions of windows */ + q->transact_ctr = 4; + } + + if (q->dev == NULL) { + q->dev = ehci_find_device(q->ehci, + get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)); + } + + 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->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); + + if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, + sizeof(EHCIitd) >> 2) < 0) { + return -1; + } + ehci_trace_itd(ehci, entry, &itd); + + if (ehci_process_itd(ehci, &itd, entry) != 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); + + if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd, + sizeof(EHCIsitd) >> 2) < 0) { + return 0; + } + 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 */ + warn_report("Skipping active siTD"); + } + + 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) +{ +#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, q->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, q->async, EST_FETCHQTD); + + /* + * no valid qTD, try next QH + */ + } else { + ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); + } + + return 1; +} + +/* Section 4.10.2 - paragraph 4 */ +static int ehci_state_fetchqtd(EHCIQueue *q) +{ + EHCIqtd qtd; + EHCIPacket *p; + int again = 1; + uint32_t addr; + + addr = NLPTR_GET(q->qtdaddr); + if (get_dwords(q->ehci, addr + 8, &qtd.token, 1) < 0) { + return 0; + } + barrier(); + if (get_dwords(q->ehci, addr + 0, &qtd.next, 1) < 0 || + get_dwords(q->ehci, addr + 4, &qtd.altnext, 1) < 0 || + get_dwords(q->ehci, addr + 12, qtd.bufptr, + ARRAY_SIZE(qtd.bufptr)) < 0) { + return 0; + } + ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd); + + p = QTAILQ_FIRST(&q->packets); + if (p != NULL) { + if (!ehci_verify_qtd(p, &qtd)) { + ehci_cancel_queue(q); + if (qtd.token & QTD_TOKEN_ACTIVE) { + ehci_trace_guest_bug(q->ehci, "guest updated active qTD"); + } + p = NULL; + } else { + p->qtd = qtd; + ehci_qh_do_overlay(q); + } + } + + if (!(qtd.token & QTD_TOKEN_ACTIVE)) { + ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); + } else if (p != NULL) { + switch (p->async) { + case EHCI_ASYNC_NONE: + case EHCI_ASYNC_INITIALIZED: + /* Not yet executed (MULT), or previously nacked (int) packet */ + ehci_set_state(q->ehci, q->async, EST_EXECUTE); + break; + case EHCI_ASYNC_INFLIGHT: + /* Check if the guest has added new tds to the queue */ + again = ehci_fill_queue(QTAILQ_LAST(&q->packets)); + /* Unfinished async handled packet, go horizontal */ + ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); + break; + case EHCI_ASYNC_FINISHED: + /* Complete executing of the packet */ + ehci_set_state(q->ehci, q->async, EST_EXECUTING); + break; + } + } else if (q->dev == NULL) { + ehci_trace_guest_bug(q->ehci, "no device attached to queue"); + ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); + } else { + p = ehci_alloc_packet(q); + p->qtdaddr = q->qtdaddr; + p->qtd = qtd; + ehci_set_state(q->ehci, q->async, EST_EXECUTE); + } + + return again; +} + +static int ehci_state_horizqh(EHCIQueue *q) +{ + int again = 0; + + if (ehci_get_fetch_addr(q->ehci, q->async) != q->qh.next) { + ehci_set_fetch_addr(q->ehci, q->async, q->qh.next); + ehci_set_state(q->ehci, q->async, EST_FETCHENTRY); + again = 1; + } else { + ehci_set_state(q->ehci, q->async, EST_ACTIVE); + } + + return again; +} + +/* Returns "again" */ +static int ehci_fill_queue(EHCIPacket *p) +{ + USBEndpoint *ep = p->packet.ep; + EHCIQueue *q = p->queue; + EHCIqtd qtd = p->qtd; + uint32_t qtdaddr; + + for (;;) { + if (NLPTR_TBIT(qtd.next) != 0) { + break; + } + qtdaddr = qtd.next; + /* + * Detect circular td lists, Windows creates these, counting on the + * active bit going low after execution to make the queue stop. + */ + QTAILQ_FOREACH(p, &q->packets, next) { + if (p->qtdaddr == qtdaddr) { + goto leave; + } + } + if (get_dwords(q->ehci, NLPTR_GET(qtdaddr), + (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2) < 0) { + return -1; + } + ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd); + if (!(qtd.token & QTD_TOKEN_ACTIVE)) { + break; + } + if (!ehci_verify_pid(q, &qtd)) { + ehci_trace_guest_bug(q->ehci, "guest queued token with wrong pid"); + break; + } + p = ehci_alloc_packet(q); + p->qtdaddr = qtdaddr; + p->qtd = qtd; + if (ehci_execute(p, "queue") == -1) { + return -1; + } + assert(p->packet.status == USB_RET_ASYNC); + p->async = EHCI_ASYNC_INFLIGHT; + } +leave: + usb_device_flush_ep_queue(ep->dev, ep); + return 1; +} + +static int ehci_state_execute(EHCIQueue *q) +{ + EHCIPacket *p = QTAILQ_FIRST(&q->packets); + int again = 0; + + assert(p != NULL); + assert(p->qtdaddr == q->qtdaddr); + + 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 + + /* 4.10.3, bottom of page 82, go horizontal on transaction counter == 0 */ + if (!q->async && q->transact_ctr == 0) { + ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); + again = 1; + goto out; + } + + if (q->async) { + ehci_set_usbsts(q->ehci, USBSTS_REC); + } + + again = ehci_execute(p, "process"); + if (again == -1) { + goto out; + } + if (p->packet.status == USB_RET_ASYNC) { + ehci_flush_qh(q); + trace_usb_ehci_packet_action(p->queue, p, "async"); + p->async = EHCI_ASYNC_INFLIGHT; + ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); + if (q->async) { + again = ehci_fill_queue(p); + } else { + again = 1; + } + goto out; + } + + ehci_set_state(q->ehci, q->async, EST_EXECUTING); + again = 1; + +out: + return again; +} + +static int ehci_state_executing(EHCIQueue *q) +{ + EHCIPacket *p = QTAILQ_FIRST(&q->packets); + + assert(p != NULL); + assert(p->qtdaddr == q->qtdaddr); + + ehci_execute_complete(q); + + /* 4.10.3 */ + if (!q->async && q->transact_ctr > 0) { + q->transact_ctr--; + } + + /* 4.10.5 */ + if (p->packet.status == USB_RET_NAK) { + ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); + } else { + ehci_set_state(q->ehci, q->async, EST_WRITEBACK); + } + + ehci_flush_qh(q); + return 1; +} + + +static int ehci_state_writeback(EHCIQueue *q) +{ + EHCIPacket *p = QTAILQ_FIRST(&q->packets); + uint32_t *qtd, addr; + int again = 0; + + /* Write back the QTD from the QH area */ + assert(p != NULL); + assert(p->qtdaddr == q->qtdaddr); + + ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd); + qtd = (uint32_t *) &q->qh.next_qtd; + addr = NLPTR_GET(p->qtdaddr); + put_dwords(q->ehci, addr + 2 * sizeof(uint32_t), qtd + 2, 2); + ehci_free_packet(p); + + /* + * 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, q->async, EST_HORIZONTALQH); + again = 1; + } else { + ehci_set_state(q->ehci, q->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 itd_count = 0; + int again; + + do { + 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); + if (q != NULL) { + assert(q->async == async); + again = 1; + } else { + again = 0; + } + break; + + case EST_FETCHITD: + again = ehci_state_fetchitd(ehci, async); + itd_count++; + break; + + case EST_FETCHSITD: + again = ehci_state_fetchsitd(ehci, async); + itd_count++; + break; + + case EST_ADVANCEQUEUE: + assert(q != NULL); + again = ehci_state_advqueue(q); + break; + + case EST_FETCHQTD: + assert(q != NULL); + again = ehci_state_fetchqtd(q); + break; + + case EST_HORIZONTALQH: + assert(q != NULL); + again = ehci_state_horizqh(q); + break; + + case EST_EXECUTE: + assert(q != NULL); + again = ehci_state_execute(q); + if (async) { + ehci->async_stepdown = 0; + } + break; + + case EST_EXECUTING: + assert(q != NULL); + if (async) { + ehci->async_stepdown = 0; + } + again = ehci_state_executing(q); + break; + + case EST_WRITEBACK: + assert(q != NULL); + again = ehci_state_writeback(q); + if (!async) { + ehci->periodic_sched_active = PERIODIC_ACTIVE; + } + break; + + default: + fprintf(stderr, "Bad state!\n"); + g_assert_not_reached(); + } + + if (again < 0 || itd_count > 16) { + /* TODO: notify guest (raise HSE irq?) */ + fprintf(stderr, "processing error - resetting ehci HC\n"); + ehci_reset(ehci); + again = 0; + } + } + while (again); +} + +static void ehci_advance_async_state(EHCIState *ehci) +{ + const int async = 1; + + switch(ehci_get_state(ehci, async)) { + case EST_INACTIVE: + if (!ehci_async_enabled(ehci)) { + break; + } + ehci_set_state(ehci, async, EST_ACTIVE); + // No break, fall through to ACTIVE + + case EST_ACTIVE: + if (!ehci_async_enabled(ehci)) { + ehci_queues_rip_all(ehci, async); + 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_unseen(ehci, async); + trace_usb_ehci_doorbell_ack(); + ehci->usbcmd &= ~USBCMD_IAAD; + ehci_raise_irq(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); + g_assert_not_reached(); + } +} + +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_periodic_enabled(ehci)) { + ehci_set_state(ehci, async, EST_ACTIVE); + // No break, fall through to ACTIVE + } else + break; + + case EST_ACTIVE: + if (!(ehci->frindex & 7) && !ehci_periodic_enabled(ehci)) { + ehci_queues_rip_all(ehci, async); + 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); + + if (get_dwords(ehci, list, &entry, 1) < 0) { + break; + } + + 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); + break; + + default: + /* this should only be due to a developer mistake */ + fprintf(stderr, "ehci: Bad periodic state %d. " + "Resetting to active\n", ehci->pstate); + g_assert_not_reached(); + } +} + +static void ehci_update_frindex(EHCIState *ehci, int uframes) +{ + if (!ehci_enabled(ehci) && ehci->pstate == EST_INACTIVE) { + return; + } + + /* Generate FLR interrupt if frame index rolls over 0x2000 */ + if ((ehci->frindex % 0x2000) + uframes >= 0x2000) { + ehci_raise_irq(ehci, USBSTS_FLR); + } + + /* How many times will frindex roll over 0x4000 with this frame count? + * usbsts_frindex is decremented by 0x4000 on rollover until it reaches 0 + */ + int rollovers = (ehci->frindex + uframes) / 0x4000; + if (rollovers > 0) { + if (ehci->usbsts_frindex >= (rollovers * 0x4000)) { + ehci->usbsts_frindex -= 0x4000 * rollovers; + } else { + ehci->usbsts_frindex = 0; + } + } + + ehci->frindex = (ehci->frindex + uframes) % 0x4000; +} + +static void ehci_work_bh(void *opaque) +{ + EHCIState *ehci = opaque; + int need_timer = 0; + int64_t expire_time, t_now; + uint64_t ns_elapsed; + uint64_t uframes, skipped_uframes; + int i; + + if (ehci->working) { + return; + } + ehci->working = true; + + t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ns_elapsed = t_now - ehci->last_run_ns; + uframes = ns_elapsed / UFRAME_TIMER_NS; + + if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) { + need_timer++; + + if (uframes > (ehci->maxframes * 8)) { + skipped_uframes = uframes - (ehci->maxframes * 8); + ehci_update_frindex(ehci, skipped_uframes); + ehci->last_run_ns += UFRAME_TIMER_NS * skipped_uframes; + uframes -= skipped_uframes; + DPRINTF("WARNING - EHCI skipped %d uframes\n", skipped_uframes); + } + + for (i = 0; i < uframes; i++) { + /* + * If we're running behind schedule, we should not catch up + * too fast, as that will make some guests unhappy: + * 1) We must process a minimum of MIN_UFR_PER_TICK frames, + * otherwise we will never catch up + * 2) Process frames until the guest has requested an irq (IOC) + */ + if (i >= MIN_UFR_PER_TICK) { + ehci_commit_irq(ehci); + if ((ehci->usbsts & USBINTR_MASK) & ehci->usbintr) { + break; + } + } + if (ehci->periodic_sched_active) { + ehci->periodic_sched_active--; + } + ehci_update_frindex(ehci, 1); + if ((ehci->frindex & 7) == 0) { + ehci_advance_periodic_state(ehci); + } + ehci->last_run_ns += UFRAME_TIMER_NS; + } + } else { + ehci->periodic_sched_active = 0; + ehci_update_frindex(ehci, uframes); + ehci->last_run_ns += UFRAME_TIMER_NS * uframes; + } + + if (ehci->periodic_sched_active) { + ehci->async_stepdown = 0; + } else if (ehci->async_stepdown < ehci->maxframes / 2) { + ehci->async_stepdown++; + } + + /* Async is not inside loop since it executes everything it can once + * called + */ + if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) { + need_timer++; + ehci_advance_async_state(ehci); + } + + ehci_commit_irq(ehci); + if (ehci->usbsts_pending) { + need_timer++; + ehci->async_stepdown = 0; + } + + if (ehci_enabled(ehci) && (ehci->usbintr & USBSTS_FLR)) { + need_timer++; + } + + if (need_timer) { + /* If we've raised int, we speed up the timer, so that we quickly + * notice any new packets queued up in response */ + if (ehci->int_req_by_async && (ehci->usbsts & USBSTS_INT)) { + expire_time = t_now + + NANOSECONDS_PER_SECOND / (FRAME_TIMER_FREQ * 4); + ehci->int_req_by_async = false; + } else { + expire_time = t_now + (NANOSECONDS_PER_SECOND + * (ehci->async_stepdown+1) / FRAME_TIMER_FREQ); + } + timer_mod(ehci->frame_timer, expire_time); + } + + ehci->working = false; +} + +static void ehci_work_timer(void *opaque) +{ + EHCIState *ehci = opaque; + + qemu_bh_schedule(ehci->async_bh); +} + +static const MemoryRegionOps ehci_mmio_caps_ops = { + .read = ehci_caps_read, + .write = ehci_caps_write, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .impl.min_access_size = 1, + .impl.max_access_size = 1, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const MemoryRegionOps ehci_mmio_opreg_ops = { + .read = ehci_opreg_read, + .write = ehci_opreg_write, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const MemoryRegionOps ehci_mmio_port_ops = { + .read = ehci_port_read, + .write = ehci_port_write, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +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_companion = { + .register_companion = ehci_register_companion, + .wakeup_endpoint = ehci_wakeup_endpoint, +}; +static USBBusOps ehci_bus_ops_standalone = { + .wakeup_endpoint = ehci_wakeup_endpoint, +}; + +static int usb_ehci_pre_save(void *opaque) +{ + EHCIState *ehci = opaque; + uint32_t new_frindex; + + /* Round down frindex to a multiple of 8 for migration compatibility */ + new_frindex = ehci->frindex & ~7; + ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS; + ehci->frindex = new_frindex; + + return 0; +} + +static int usb_ehci_post_load(void *opaque, int version_id) +{ + EHCIState *s = opaque; + int i; + + for (i = 0; i < NB_PORTS; i++) { + USBPort *companion = s->companion_ports[i]; + if (companion == NULL) { + continue; + } + if (s->portsc[i] & PORTSC_POWNER) { + companion->dev = s->ports[i].dev; + } else { + companion->dev = NULL; + } + } + + return 0; +} + +static void usb_ehci_vm_state_change(void *opaque, bool running, RunState state) +{ + EHCIState *ehci = opaque; + + /* + * We don't migrate the EHCIQueue-s, instead we rebuild them for the + * schedule in guest memory. We must do the rebuilt ASAP, so that + * USB-devices which have async handled packages have a packet in the + * ep queue to match the completion with. + */ + if (state == RUN_STATE_RUNNING) { + ehci_advance_async_state(ehci); + } + + /* + * The schedule rebuilt from guest memory could cause the migration dest + * to miss a QH unlink, and fail to cancel packets, since the unlinked QH + * will never have existed on the destination. Therefor we must flush the + * async schedule on savevm to catch any not yet noticed unlinks. + */ + if (state == RUN_STATE_SAVE_VM) { + ehci_advance_async_state(ehci); + ehci_queues_rip_unseen(ehci, 1); + } +} + +const VMStateDescription vmstate_ehci = { + .name = "ehci-core", + .version_id = 2, + .minimum_version_id = 1, + .pre_save = usb_ehci_pre_save, + .post_load = usb_ehci_post_load, + .fields = (VMStateField[]) { + /* mmio registers */ + VMSTATE_UINT32(usbcmd, EHCIState), + VMSTATE_UINT32(usbsts, EHCIState), + VMSTATE_UINT32_V(usbsts_pending, EHCIState, 2), + VMSTATE_UINT32_V(usbsts_frindex, EHCIState, 2), + VMSTATE_UINT32(usbintr, EHCIState), + VMSTATE_UINT32(frindex, EHCIState), + VMSTATE_UINT32(ctrldssegment, EHCIState), + VMSTATE_UINT32(periodiclistbase, EHCIState), + VMSTATE_UINT32(asynclistaddr, EHCIState), + VMSTATE_UINT32(configflag, EHCIState), + VMSTATE_UINT32(portsc[0], EHCIState), + VMSTATE_UINT32(portsc[1], EHCIState), + VMSTATE_UINT32(portsc[2], EHCIState), + VMSTATE_UINT32(portsc[3], EHCIState), + VMSTATE_UINT32(portsc[4], EHCIState), + VMSTATE_UINT32(portsc[5], EHCIState), + /* frame timer */ + VMSTATE_TIMER_PTR(frame_timer, EHCIState), + VMSTATE_UINT64(last_run_ns, EHCIState), + VMSTATE_UINT32(async_stepdown, EHCIState), + /* schedule state */ + VMSTATE_UINT32(astate, EHCIState), + VMSTATE_UINT32(pstate, EHCIState), + VMSTATE_UINT32(a_fetch_addr, EHCIState), + VMSTATE_UINT32(p_fetch_addr, EHCIState), + VMSTATE_END_OF_LIST() + } +}; + +void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp) +{ + int i; + + if (s->portnr > NB_PORTS) { + error_setg(errp, "Too many ports! Max. port number is %d.", + NB_PORTS); + return; + } + if (s->maxframes < 8 || s->maxframes > 512) { + error_setg(errp, "maxframes %d out if range (8 .. 512)", + s->maxframes); + return; + } + + memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps); + memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg); + memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase, + &s->mem_ports); + + usb_bus_new(&s->bus, sizeof(s->bus), s->companion_enable ? + &ehci_bus_ops_companion : &ehci_bus_ops_standalone, dev); + for (i = 0; i < s->portnr; 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 = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_work_timer, s); + s->async_bh = qemu_bh_new(ehci_work_bh, s); + s->device = dev; + + s->vmstate = qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s); +} + +void usb_ehci_unrealize(EHCIState *s, DeviceState *dev) +{ + trace_usb_ehci_unrealize(); + + if (s->frame_timer) { + timer_free(s->frame_timer); + s->frame_timer = NULL; + } + if (s->async_bh) { + qemu_bh_delete(s->async_bh); + } + + ehci_queues_rip_all(s, 0); + ehci_queues_rip_all(s, 1); + + memory_region_del_subregion(&s->mem, &s->mem_caps); + memory_region_del_subregion(&s->mem, &s->mem_opreg); + memory_region_del_subregion(&s->mem, &s->mem_ports); + + usb_bus_release(&s->bus); + + if (s->vmstate) { + qemu_del_vm_change_state_handler(s->vmstate); + } +} + +void usb_ehci_init(EHCIState *s, DeviceState *dev) +{ + /* 2.2 host controller interface version */ + s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase); + s->caps[0x01] = 0x00; + s->caps[0x02] = 0x00; + s->caps[0x03] = 0x01; /* HC version */ + s->caps[0x04] = s->portnr; /* Number of downstream ports */ + s->caps[0x05] = 0x00; /* No companion ports at present */ + s->caps[0x06] = 0x00; + s->caps[0x07] = 0x00; + s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */ + s->caps[0x0a] = 0x00; + s->caps[0x0b] = 0x00; + + QTAILQ_INIT(&s->aqueues); + QTAILQ_INIT(&s->pqueues); + usb_packet_init(&s->ipacket); + + memory_region_init(&s->mem, OBJECT(dev), "ehci", MMIO_SIZE); + memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s, + "capabilities", CAPA_SIZE); + memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s, + "operational", s->portscbase); + memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s, + "ports", 4 * s->portnr); +} + +void usb_ehci_finalize(EHCIState *s) +{ + usb_packet_cleanup(&s->ipacket); +} + +/* + * vim: expandtab ts=4 + */ diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h new file mode 100644 index 000000000..a173707d9 --- /dev/null +++ b/hw/usb/hcd-ehci.h @@ -0,0 +1,383 @@ +/* + * QEMU USB EHCI Emulation + * + * 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.1 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 program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef HW_USB_HCD_EHCI_H +#define HW_USB_HCD_EHCI_H + +#include "qemu/timer.h" +#include "hw/usb.h" +#include "sysemu/dma.h" +#include "hw/pci/pci.h" +#include "hw/sysbus.h" +#include "qom/object.h" + +#ifndef EHCI_DEBUG +#define EHCI_DEBUG 0 +#endif + +#if EHCI_DEBUG +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +#define MMIO_SIZE 0x1000 +#define CAPA_SIZE 0x10 + +#define NB_PORTS 6 /* Max. Number of downstream ports */ + +typedef struct EHCIPacket EHCIPacket; +typedef struct EHCIQueue EHCIQueue; +typedef struct EHCIState EHCIState; + +/* 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; + +enum async_state { + EHCI_ASYNC_NONE = 0, + EHCI_ASYNC_INITIALIZED, + EHCI_ASYNC_INFLIGHT, + EHCI_ASYNC_FINISHED, +}; + +struct EHCIPacket { + EHCIQueue *queue; + QTAILQ_ENTRY(EHCIPacket) next; + + EHCIqtd qtd; /* copy of current QTD (being worked on) */ + uint32_t qtdaddr; /* address QTD read from */ + + USBPacket packet; + QEMUSGList sgl; + int pid; + enum async_state async; +}; + +struct EHCIQueue { + EHCIState *ehci; + QTAILQ_ENTRY(EHCIQueue) next; + uint32_t seen; + uint64_t ts; + int async; + int transact_ctr; + + /* 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 */ + uint32_t qtdaddr; /* address QTD read from */ + int last_pid; /* pid of last packet executed */ + USBDevice *dev; + QTAILQ_HEAD(, EHCIPacket) packets; +}; + +typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead; + +struct EHCIState { + USBBus bus; + DeviceState *device; + qemu_irq irq; + MemoryRegion mem; + AddressSpace *as; + MemoryRegion mem_caps; + MemoryRegion mem_opreg; + MemoryRegion mem_ports; + int companion_count; + bool companion_enable; + uint16_t capsbase; + uint16_t opregbase; + uint16_t portscbase; + uint16_t portnr; + + /* properties */ + uint32_t maxframes; + + /* + * EHCI spec version 1.0 Section 2.3 + * Host Controller Operational Registers + */ + uint8_t caps[CAPA_SIZE]; + union { + uint32_t opreg[0x44/sizeof(uint32_t)]; + struct { + 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 + */ + QEMUTimer *frame_timer; + QEMUBH *async_bh; + bool working; + uint32_t astate; /* Current state in asynchronous schedule */ + uint32_t pstate; /* Current state in periodic schedule */ + USBPort ports[NB_PORTS]; + USBPort *companion_ports[NB_PORTS]; + uint32_t usbsts_pending; + uint32_t usbsts_frindex; + EHCIQueueHead aqueues; + EHCIQueueHead pqueues; + + /* which address to look at next */ + uint32_t a_fetch_addr; + uint32_t p_fetch_addr; + + USBPacket ipacket; + QEMUSGList isgl; + + uint64_t last_run_ns; + uint32_t async_stepdown; + uint32_t periodic_sched_active; + bool int_req_by_async; + VMChangeStateEntry *vmstate; +}; + +extern const VMStateDescription vmstate_ehci; + +void usb_ehci_init(EHCIState *s, DeviceState *dev); +void usb_ehci_finalize(EHCIState *s); +void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp); +void usb_ehci_unrealize(EHCIState *s, DeviceState *dev); +void ehci_reset(void *opaque); + +#define TYPE_PCI_EHCI "pci-ehci-usb" +OBJECT_DECLARE_SIMPLE_TYPE(EHCIPCIState, PCI_EHCI) + +struct EHCIPCIState { + /*< private >*/ + PCIDevice pcidev; + /*< public >*/ + + EHCIState ehci; +}; + + +#define TYPE_SYS_BUS_EHCI "sysbus-ehci-usb" +#define TYPE_PLATFORM_EHCI "platform-ehci-usb" +#define TYPE_EXYNOS4210_EHCI "exynos4210-ehci-usb" +#define TYPE_AW_H3_EHCI "aw-h3-ehci-usb" +#define TYPE_NPCM7XX_EHCI "npcm7xx-ehci-usb" +#define TYPE_TEGRA2_EHCI "tegra2-ehci-usb" +#define TYPE_PPC4xx_EHCI "ppc4xx-ehci-usb" +#define TYPE_FUSBH200_EHCI "fusbh200-ehci-usb" + +OBJECT_DECLARE_TYPE(EHCISysBusState, SysBusEHCIClass, SYS_BUS_EHCI) + +struct EHCISysBusState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + EHCIState ehci; +}; + +struct SysBusEHCIClass { + /*< private >*/ + SysBusDeviceClass parent_class; + /*< public >*/ + + uint16_t capsbase; + uint16_t opregbase; + uint16_t portscbase; + uint16_t portnr; +}; + +OBJECT_DECLARE_SIMPLE_TYPE(FUSBH200EHCIState, FUSBH200_EHCI) + +struct FUSBH200EHCIState { + /*< private >*/ + EHCISysBusState parent_obj; + /*< public >*/ + + MemoryRegion mem_vendor; +}; + +#endif diff --git a/hw/usb/hcd-musb.c b/hw/usb/hcd-musb.c new file mode 100644 index 000000000..85f5ff5bd --- /dev/null +++ b/hw/usb/hcd-musb.c @@ -0,0 +1,1553 @@ +/* + * "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/osdep.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "hw/usb/hcd-musb.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", __func__, \ + __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, sizeof(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 { + musb_cb_tick(ep); + return; + } + + if (!ep->intv_timer[dir]) + ep->intv_timer[dir] = timer_new_ns(QEMU_CLOCK_VIRTUAL, musb_cb_tick, ep); + + timer_mod(ep->intv_timer[dir], qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + muldiv64(timeout, NANOSECONDS_PER_SECOND, 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 idx = epnum && dir; + int id; + 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]); + if (dev == NULL) { + return; + } + uep = usb_ep_get(dev, pid, ep->type[idx] & 0xf); + id = pid | (dev->addr << 16) | (uep->nr << 8); + usb_packet_setup(&ep->packey[dir].p, pid, uep, 0, id, false, true); + usb_packet_addbuf(&ep->packey[dir].p, ep->buf[idx], len); + ep->packey[dir].ep = ep; + ep->packey[dir].dir = dir; + + usb_handle_packet(dev, &ep->packey[dir].p); + + if (ep->packey[dir].p.status == USB_RET_ASYNC) { + usb_device_flush_ep_queue(dev, uep); + ep->status[dir] = len; + return; + } + + if (ep->packey[dir].p.status == USB_RET_SUCCESS) { + ep->status[dir] = ep->packey[dir].p.actual_length; + } else { + ep->status[dir] = ep->packey[dir].p.status; + } + 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; + + 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]) { + musb_packet(s, ep, epnum, USB_TOKEN_IN, + packey->iov.size, musb_rx_packet_complete, 1); + return; + } + + 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. */ + + 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 = ep->status[1]; /* 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. */ + } + + 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 + + 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]) + timer_del(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, hwaddr 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, hwaddr 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, hwaddr 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, hwaddr 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, hwaddr 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, hwaddr 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; + }; +} + +MUSBReadFunc * const musb_read[] = { + musb_readb, + musb_readh, + musb_readw, +}; + +MUSBWriteFunc * const musb_write[] = { + musb_writeb, + musb_writeh, + musb_writew, +}; diff --git a/hw/usb/hcd-ohci-pci.c b/hw/usb/hcd-ohci-pci.c new file mode 100644 index 000000000..8e1146b86 --- /dev/null +++ b/hw/usb/hcd-ohci-pci.c @@ -0,0 +1,164 @@ +/* + * 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.1 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 "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "hw/pci/pci.h" +#include "hw/sysbus.h" +#include "hw/qdev-dma.h" +#include "hw/qdev-properties.h" +#include "trace.h" +#include "hcd-ohci.h" +#include "qom/object.h" + +#define TYPE_PCI_OHCI "pci-ohci" +OBJECT_DECLARE_SIMPLE_TYPE(OHCIPCIState, PCI_OHCI) + +struct OHCIPCIState { + /*< private >*/ + PCIDevice parent_obj; + /*< public >*/ + + OHCIState state; + char *masterbus; + uint32_t num_ports; + uint32_t firstport; +}; + +/** + * A typical PCI OHCI will additionally set PERR in its configspace to + * signal that it got an error. + */ +static void ohci_pci_die(struct OHCIState *ohci) +{ + OHCIPCIState *dev = container_of(ohci, OHCIPCIState, state); + + ohci_sysbus_die(ohci); + + pci_set_word(dev->parent_obj.config + PCI_STATUS, + PCI_STATUS_DETECTED_PARITY); +} + +static void usb_ohci_realize_pci(PCIDevice *dev, Error **errp) +{ + Error *err = NULL; + OHCIPCIState *ohci = PCI_OHCI(dev); + + dev->config[PCI_CLASS_PROG] = 0x10; /* OHCI */ + dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin A */ + + usb_ohci_init(&ohci->state, DEVICE(dev), ohci->num_ports, 0, + ohci->masterbus, ohci->firstport, + pci_get_address_space(dev), ohci_pci_die, &err); + if (err) { + error_propagate(errp, err); + return; + } + + ohci->state.irq = pci_allocate_irq(dev); + pci_register_bar(dev, 0, 0, &ohci->state.mem); +} + +static void usb_ohci_exit(PCIDevice *dev) +{ + OHCIPCIState *ohci = PCI_OHCI(dev); + OHCIState *s = &ohci->state; + + trace_usb_ohci_exit(s->name); + ohci_bus_stop(s); + + if (s->async_td) { + usb_cancel_packet(&s->usb_packet); + s->async_td = 0; + } + ohci_stop_endpoints(s); + + if (!ohci->masterbus) { + usb_bus_release(&s->bus); + } + + timer_free(s->eof_timer); +} + +static void usb_ohci_reset_pci(DeviceState *d) +{ + PCIDevice *dev = PCI_DEVICE(d); + OHCIPCIState *ohci = PCI_OHCI(dev); + OHCIState *s = &ohci->state; + + ohci_hard_reset(s); +} + +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 const VMStateDescription vmstate_ohci = { + .name = "ohci", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(parent_obj, OHCIPCIState), + VMSTATE_STRUCT(state, OHCIPCIState, 1, vmstate_ohci_state, OHCIState), + VMSTATE_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->realize = usb_ohci_realize_pci; + k->exit = usb_ohci_exit; + k->vendor_id = PCI_VENDOR_ID_APPLE; + k->device_id = PCI_DEVICE_ID_APPLE_IPID_USB; + k->class_id = PCI_CLASS_SERIAL_USB; + set_bit(DEVICE_CATEGORY_USB, dc->categories); + dc->desc = "Apple USB Controller"; + device_class_set_props(dc, ohci_pci_properties); + dc->hotpluggable = false; + dc->vmsd = &vmstate_ohci; + dc->reset = usb_ohci_reset_pci; +} + +static const TypeInfo ohci_pci_info = { + .name = TYPE_PCI_OHCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(OHCIPCIState), + .class_init = ohci_pci_class_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +static void ohci_pci_register_types(void) +{ + type_register_static(&ohci_pci_info); +} + +type_init(ohci_pci_register_types) diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c new file mode 100644 index 000000000..1cf281677 --- /dev/null +++ b/hw/usb/hcd-ohci.c @@ -0,0 +1,2028 @@ +/* + * 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.1 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 BIOS work to boot from USB storage +*/ + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "hw/sysbus.h" +#include "hw/qdev-dma.h" +#include "hw/qdev-properties.h" +#include "trace.h" +#include "hcd-ohci.h" + +/* This causes frames to occur 1000x slower */ +//#define OHCI_TIME_WARP 1 + +#define ED_LINK_LIMIT 32 + +static int64_t usb_frame_time; +static int64_t usb_bit_time; + +/* Host Controller Communications Area */ +struct ohci_hcca { + uint32_t intr[32]; + uint16_t frame, pad; + uint32_t done; +}; +#define HCCA_WRITEBACK_OFFSET offsetof(struct ohci_hcca, frame) +#define HCCA_WRITEBACK_SIZE 8 /* frame, pad, done */ + +#define ED_WBACK_OFFSET offsetof(struct ohci_ed, head) +#define ED_WBACK_SIZE 4 + +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 (1U<<0) /* Scheduling overrun */ +#define OHCI_INTR_WD (1U<<1) /* HcDoneHead writeback */ +#define OHCI_INTR_SF (1U<<2) /* Start of frame */ +#define OHCI_INTR_RD (1U<<3) /* Resume detect */ +#define OHCI_INTR_UE (1U<<4) /* Unrecoverable error */ +#define OHCI_INTR_FNO (1U<<5) /* Frame number overflow */ +#define OHCI_INTR_RHSC (1U<<6) /* Root hub status change */ +#define OHCI_INTR_OC (1U<<30) /* Ownership change */ +#define OHCI_INTR_MIE (1U<<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 (1U<<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 (1U<<0) +#define OHCI_RHS_OCI (1U<<1) +#define OHCI_RHS_DRWE (1U<<15) +#define OHCI_RHS_LPSC (1U<<16) +#define OHCI_RHS_OCIC (1U<<17) +#define OHCI_RHS_CRWE (1U<<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) + +static void ohci_die(OHCIState *ohci) +{ + ohci->ohci_die(ohci); +} + +/* 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); + } + + trace_usb_ohci_port_attach(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; + } + trace_usb_ohci_port_detach(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) { + trace_usb_ohci_port_wakeup(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) { + trace_usb_ohci_remote_wakeup(s->name); + /* 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; +} + +void ohci_stop_endpoints(OHCIState *ohci) +{ + USBDevice *dev; + int i, j; + + for (i = 0; i < ohci->num_ports; i++) { + dev = ohci->rhport[i].port.dev; + if (dev && dev->attached) { + usb_device_ep_stopped(dev, &dev->ep_ctl); + for (j = 0; j < USB_MAX_ENDPOINTS; j++) { + usb_device_ep_stopped(dev, &dev->ep_in[j]); + usb_device_ep_stopped(dev, &dev->ep_out[j]); + } + } + } +} + +static void ohci_roothub_reset(OHCIState *ohci) +{ + OHCIPort *port; + int i; + + ohci_bus_stop(ohci); + 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; + } + ohci_stop_endpoints(ohci); +} + +/* Reset the controller */ +static void ohci_soft_reset(OHCIState *ohci) +{ + trace_usb_ohci_reset(ohci->name); + + ohci_bus_stop(ohci); + ohci->ctl = (ohci->ctl & OHCI_CTL_IR) | OHCI_USB_SUSPEND; + 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; +} + +void ohci_hard_reset(OHCIState *ohci) +{ + ohci_soft_reset(ohci); + ohci->ctl = 0; + ohci_roothub_reset(ohci); +} + +/* Get an array of dwords from main memory */ +static inline int get_dwords(OHCIState *ohci, + dma_addr_t addr, uint32_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + if (dma_memory_read(ohci->as, addr, buf, sizeof(*buf))) { + return -1; + } + *buf = le32_to_cpu(*buf); + } + + return 0; +} + +/* Put an array of dwords in to main memory */ +static inline int put_dwords(OHCIState *ohci, + dma_addr_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); + if (dma_memory_write(ohci->as, addr, &tmp, sizeof(tmp))) { + return -1; + } + } + + return 0; +} + +/* Get an array of words from main memory */ +static inline int get_words(OHCIState *ohci, + dma_addr_t addr, uint16_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + if (dma_memory_read(ohci->as, addr, buf, sizeof(*buf))) { + return -1; + } + *buf = le16_to_cpu(*buf); + } + + return 0; +} + +/* Put an array of words in to main memory */ +static inline int put_words(OHCIState *ohci, + dma_addr_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); + if (dma_memory_write(ohci->as, addr, &tmp, sizeof(tmp))) { + return -1; + } + } + + return 0; +} + +static inline int ohci_read_ed(OHCIState *ohci, + dma_addr_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, + dma_addr_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, + dma_addr_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, + dma_addr_t addr, struct ohci_hcca *hcca) +{ + return dma_memory_read(ohci->as, addr + ohci->localmem_base, + hcca, sizeof(*hcca)); +} + +static inline int ohci_put_ed(OHCIState *ohci, + dma_addr_t addr, struct ohci_ed *ed) +{ + /* ed->tail is under control of the HCD. + * Since just ed->head is changed by HC, just write back this + */ + + return put_dwords(ohci, addr + ED_WBACK_OFFSET, + (uint32_t *)((char *)ed + ED_WBACK_OFFSET), + ED_WBACK_SIZE >> 2); +} + +static inline int ohci_put_td(OHCIState *ohci, + dma_addr_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, + dma_addr_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, + dma_addr_t addr, struct ohci_hcca *hcca) +{ + return dma_memory_write(ohci->as, + addr + ohci->localmem_base + HCCA_WRITEBACK_OFFSET, + (char *)hcca + HCCA_WRITEBACK_OFFSET, + HCCA_WRITEBACK_SIZE); +} + +/* Read/Write the contents of a TD from/to main memory. */ +static int ohci_copy_td(OHCIState *ohci, struct ohci_td *td, + uint8_t *buf, int len, DMADirection dir) +{ + dma_addr_t ptr, n; + + ptr = td->cbp; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + + if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf, n, dir)) { + return -1; + } + if (n == len) { + return 0; + } + ptr = td->be & ~0xfffu; + buf += n; + if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf, + len - n, dir)) { + return -1; + } + return 0; +} + +/* Read/Write the contents of an ISO TD from/to main memory. */ +static int ohci_copy_iso_td(OHCIState *ohci, + uint32_t start_addr, uint32_t end_addr, + uint8_t *buf, int len, DMADirection dir) +{ + dma_addr_t ptr, n; + + ptr = start_addr; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + + if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf, n, dir)) { + return -1; + } + if (n == len) { + return 0; + } + ptr = end_addr & ~0xfffu; + buf += n; + if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf, + len - n, dir)) { + return -1; + } + return 0; +} + +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); + + trace_usb_ohci_async_complete(); + ohci->async_complete = true; + 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; + const char *str = NULL; + 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)) { + trace_usb_ohci_iso_td_read_failed(addr); + ohci_die(ohci); + return 1; + } + + 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); + + trace_usb_ohci_iso_td_head( + ed->head & OHCI_DPTR_MASK, ed->tail & OHCI_DPTR_MASK, + iso_td.flags, iso_td.bp, iso_td.next, iso_td.be, + ohci->frame_number, starting_frame, + frame_count, relative_frame_number); + trace_usb_ohci_iso_td_head_offset( + 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]); + + if (relative_frame_number < 0) { + trace_usb_ohci_iso_td_relative_frame_number_neg(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 */ + trace_usb_ohci_iso_td_relative_frame_number_big(relative_frame_number, + frame_count); + if (OHCI_CC_DATAOVERRUN == OHCI_BM(iso_td.flags, TD_CC)) { + /* avoid infinite loop */ + return 1; + } + 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; + if (ohci_put_iso_td(ohci, addr, &iso_td)) { + ohci_die(ohci); + return 1; + } + return 0; + } + + dir = OHCI_BM(ed->flags, ED_D); + switch (dir) { + case OHCI_TD_DIR_IN: + str = "in"; + pid = USB_TOKEN_IN; + break; + case OHCI_TD_DIR_OUT: + str = "out"; + pid = USB_TOKEN_OUT; + break; + case OHCI_TD_DIR_SETUP: + str = "setup"; + pid = USB_TOKEN_SETUP; + break; + default: + trace_usb_ohci_iso_td_bad_direction(dir); + return 1; + } + + if (!iso_td.bp || !iso_td.be) { + trace_usb_ohci_iso_td_bad_bp_be(iso_td.bp, iso_td.be); + return 1; + } + + start_offset = iso_td.offset[relative_frame_number]; + if (relative_frame_number < frame_count) { + next_offset = iso_td.offset[relative_frame_number + 1]; + } else { + next_offset = iso_td.be; + } + + if (!(OHCI_BM(start_offset, TD_PSW_CC) & 0xe) || + ((relative_frame_number < frame_count) && + !(OHCI_BM(next_offset, TD_PSW_CC) & 0xe))) { + trace_usb_ohci_iso_td_bad_cc_not_accessed(start_offset, next_offset); + return 1; + } + + if ((relative_frame_number < frame_count) && (start_offset > next_offset)) { + trace_usb_ohci_iso_td_bad_cc_overrun(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 = next_offset; + } + + if (start_addr > end_addr) { + trace_usb_ohci_iso_td_bad_cc_overrun(start_addr, end_addr); + return 1; + } + + 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 > sizeof(ohci->usb_buf)) { + len = sizeof(ohci->usb_buf); + } + + if (len && dir != OHCI_TD_DIR_IN) { + if (ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, len, + DMA_DIRECTION_TO_DEVICE)) { + ohci_die(ohci); + return 1; + } + } + + if (!completion) { + bool int_req = relative_frame_number == frame_count && + OHCI_BM(iso_td.flags, TD_DI) == 0; + dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA)); + if (dev == NULL) { + trace_usb_ohci_td_dev_error(); + return 1; + } + ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN)); + usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, false, int_req); + usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, len); + usb_handle_packet(dev, &ohci->usb_packet); + if (ohci->usb_packet.status == USB_RET_ASYNC) { + usb_device_flush_ep_queue(dev, ep); + return 1; + } + } + if (ohci->usb_packet.status == USB_RET_SUCCESS) { + ret = ohci->usb_packet.actual_length; + } else { + ret = ohci->usb_packet.status; + } + + trace_usb_ohci_iso_td_so(start_offset, end_offset, start_addr, end_addr, + str, len, ret); + + /* Writeback */ + if (dir == OHCI_TD_DIR_IN && ret >= 0 && ret <= len) { + /* IN transfer succeeded */ + if (ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, ret, + DMA_DIRECTION_FROM_DEVICE)) { + ohci_die(ohci); + return 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) { + trace_usb_ohci_iso_td_data_overrun(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) { + trace_usb_ohci_iso_td_data_underrun(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: + trace_usb_ohci_iso_td_nak(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: + trace_usb_ohci_iso_td_bad_response(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; + } + if (ohci_put_iso_td(ohci, addr, &iso_td)) { + ohci_die(ohci); + } + return 1; +} + +static void ohci_td_pkt(const char *msg, const uint8_t *buf, size_t len) +{ + bool print16; + bool printall; + const int width = 16; + int i; + char tmp[3 * width + 1]; + char *p = tmp; + + print16 = !!trace_event_get_state_backends(TRACE_USB_OHCI_TD_PKT_SHORT); + printall = !!trace_event_get_state_backends(TRACE_USB_OHCI_TD_PKT_FULL); + + if (!printall && !print16) { + return; + } + + for (i = 0; ; i++) { + if (i && (!(i % width) || (i == len))) { + if (!printall) { + trace_usb_ohci_td_pkt_short(msg, tmp); + break; + } + trace_usb_ohci_td_pkt_full(msg, tmp); + p = tmp; + *p = 0; + } + if (i == len) { + break; + } + + p += sprintf(p, " %.2x", buf[i]); + } +} + +/* 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; + const char *str = NULL; + 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) { + trace_usb_ohci_td_skip_async(); + return 1; + } + if (ohci_read_td(ohci, addr, &td)) { + trace_usb_ohci_td_read_error(addr); + ohci_die(ohci); + return 1; + } + + 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: + str = "in"; + pid = USB_TOKEN_IN; + break; + case OHCI_TD_DIR_OUT: + str = "out"; + pid = USB_TOKEN_OUT; + break; + case OHCI_TD_DIR_SETUP: + str = "setup"; + pid = USB_TOKEN_SETUP; + break; + default: + trace_usb_ohci_td_bad_direction(dir); + return 1; + } + if (td.cbp && td.be) { + if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) { + len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff); + } else { + if (td.cbp > td.be) { + trace_usb_ohci_iso_td_bad_cc_overrun(td.cbp, td.be); + ohci_die(ohci); + return 1; + } + len = (td.be - td.cbp) + 1; + } + if (len > sizeof(ohci->usb_buf)) { + len = sizeof(ohci->usb_buf); + } + + 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) { + if (ohci_copy_td(ohci, &td, ohci->usb_buf, pktlen, + DMA_DIRECTION_TO_DEVICE)) { + ohci_die(ohci); + } + } + } + } + + flag_r = (td.flags & OHCI_TD_R) != 0; + trace_usb_ohci_td_pkt_hdr(addr, (int64_t)pktlen, (int64_t)len, str, + flag_r, td.cbp, td.be); + ohci_td_pkt("OUT", ohci->usb_buf, pktlen); + + if (completion) { + ohci->async_td = 0; + ohci->async_complete = false; + } 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. + */ + trace_usb_ohci_td_too_many_pending(); + return 1; + } + dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA)); + if (dev == NULL) { + trace_usb_ohci_td_dev_error(); + return 1; + } + ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN)); + usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, !flag_r, + OHCI_BM(td.flags, TD_DI) == 0); + usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, pktlen); + usb_handle_packet(dev, &ohci->usb_packet); + trace_usb_ohci_td_packet_status(ohci->usb_packet.status); + + if (ohci->usb_packet.status == USB_RET_ASYNC) { + usb_device_flush_ep_queue(dev, ep); + ohci->async_td = addr; + return 1; + } + } + if (ohci->usb_packet.status == USB_RET_SUCCESS) { + ret = ohci->usb_packet.actual_length; + } else { + ret = ohci->usb_packet.status; + } + + if (ret >= 0) { + if (dir == OHCI_TD_DIR_IN) { + if (ohci_copy_td(ohci, &td, ohci->usb_buf, ret, + DMA_DIRECTION_FROM_DEVICE)) { + ohci_die(ohci); + } + ohci_td_pkt("IN", ohci->usb_buf, pktlen); + } 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) { + trace_usb_ohci_td_underrun(); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN); + } else { + switch (ret) { + case USB_RET_IOERROR: + case USB_RET_NODEV: + trace_usb_ohci_td_dev_error(); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING); + break; + case USB_RET_NAK: + trace_usb_ohci_td_nak(); + return 1; + case USB_RET_STALL: + trace_usb_ohci_td_stall(); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_STALL); + break; + case USB_RET_BABBLE: + trace_usb_ohci_td_babble(); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAOVERRUN); + break; + default: + trace_usb_ohci_td_bad_device_response(ret); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_UNDEXPETEDPID); + OHCI_SET_BM(td.flags, TD_EC, 3); + break; + } + /* An error occurred so we have to clear the interrupt counter. See + * spec at 6.4.4 on page 104 */ + ohci->done_count = 0; + } + 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: + if (ohci_put_td(ohci, addr, &td)) { + ohci_die(ohci); + return 1; + } + 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; + uint32_t link_cnt = 0; + active = 0; + + if (head == 0) + return 0; + + for (cur = head; cur && link_cnt++ < ED_LINK_LIMIT; cur = next_ed) { + if (ohci_read_ed(ohci, cur, &ed)) { + trace_usb_ohci_ed_read_error(cur); + ohci_die(ohci); + 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; + usb_device_ep_stopped(ohci->usb_packet.ep->dev, + ohci->usb_packet.ep); + } + continue; + } + + while ((ed.head & OHCI_DPTR_MASK) != ed.tail) { + trace_usb_ohci_ed_pkt(cur, (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); + trace_usb_ohci_ed_pkt_flags( + 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)); + + 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; + } + } + + if (ohci_put_ed(ohci, cur, &ed)) { + ohci_die(ohci); + return 0; + } + } + + return active; +} + +/* set a timer for EOF */ +static void ohci_eof_timer(OHCIState *ohci) +{ + timer_mod(ohci->eof_timer, ohci->sof_time + usb_frame_time); +} +/* Set a timer for EOF and generate a SOF event */ +static void ohci_sof(OHCIState *ohci) +{ + ohci->sof_time += usb_frame_time; + ohci_eof_timer(ohci); + 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) { + trace_usb_ohci_process_lists(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; + + if (ohci_read_hcca(ohci, ohci->hcca, &hcca)) { + trace_usb_ohci_hcca_read_error(ohci->hcca); + ohci_die(ohci); + return; + } + + /* 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->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) { + if (ohci->async_td) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + ohci_stop_endpoints(ohci); + } + ohci->old_ctl = ohci->ctl; + ohci_process_lists(ohci, 0); + + /* Stop if UnrecoverableError happened or ohci_sof will crash */ + if (ohci->intr_status & OHCI_INTR_UE) { + return; + } + + /* 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 */ + if (ohci_put_hcca(ohci, ohci->hcca, &hcca)) { + ohci_die(ohci); + } +} + +/* Start sending SOF tokens across the USB bus, lists are processed in + * next frame + */ +static int ohci_bus_start(OHCIState *ohci) +{ + trace_usb_ohci_start(ohci->name); + + /* Delay the first SOF event by one frame time as + * linux driver is not ready to receive it and + * can meet some race conditions + */ + + ohci->sof_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ohci_eof_timer(ohci); + + return 1; +} + +/* Stop sending SOF tokens on the bus */ +void ohci_bus_stop(OHCIState *ohci) +{ + trace_usb_ohci_stop(ohci->name); + timer_del(ohci->eof_timer); +} + +/* 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) { + trace_usb_ohci_set_frame_interval(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; + + trace_usb_ohci_set_ctl(ohci->name, new_state); + switch (new_state) { + case OHCI_USB_OPERATIONAL: + ohci_bus_start(ohci); + break; + case OHCI_USB_SUSPEND: + ohci_bus_stop(ohci); + /* clear pending SF otherwise linux driver loops in ohci_irq() */ + ohci->intr_status &= ~OHCI_INTR_SF; + ohci_intr_update(ohci); + break; + case OHCI_USB_RESUME: + trace_usb_ohci_resume(ohci->name); + break; + case OHCI_USB_RESET: + ohci_roothub_reset(ohci); + 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_clock_get_ns(QEMU_CLOCK_VIRTUAL) - ohci->sof_time; + if (tks < 0) { + tks = 0; + } + + /* avoid muldiv if possible */ + if (tks >= usb_frame_time) + return (ohci->frt << 31); + + tks = 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); + trace_usb_ohci_hub_power_down(); + } + + if (val & OHCI_RHS_LPSC) { + int i; + + for (i = 0; i < ohci->num_ports; i++) + ohci_port_power(ohci, i, 1); + trace_usb_ohci_hub_power_up(); + } + + 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)) { + trace_usb_ohci_port_suspend(portnum); + } + + if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PRS)) { + trace_usb_ohci_port_reset(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); +} + +static uint64_t ohci_mem_read(void *opaque, + hwaddr addr, + unsigned size) +{ + OHCIState *ohci = opaque; + uint32_t retval; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + trace_usb_ohci_mem_read_unaligned(addr); + 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: + trace_usb_ohci_mem_read_bad_offset(addr); + retval = 0xffffffff; + } + } + + return retval; +} + +static void ohci_mem_write(void *opaque, + hwaddr addr, + uint64_t val, + unsigned size) +{ + OHCIState *ohci = opaque; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + trace_usb_ohci_mem_write_unaligned(addr); + 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_soft_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); + break; + + case 25: /* HcHReset */ + ohci->hreset = val & ~OHCI_HRESET_FSBIR; + if (val & OHCI_HRESET_FSBIR) + ohci_hard_reset(ohci); + break; + + case 26: /* HcHInterruptEnable */ + ohci->hmask = val; + break; + + case 27: /* HcHInterruptTest */ + ohci->htest = val; + break; + + default: + trace_usb_ohci_mem_write_bad_offset(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 = { +}; + +void usb_ohci_init(OHCIState *ohci, DeviceState *dev, uint32_t num_ports, + dma_addr_t localmem_base, char *masterbus, + uint32_t firstport, AddressSpace *as, + void (*ohci_die_fn)(struct OHCIState *), Error **errp) +{ + Error *err = NULL; + int i; + + ohci->as = as; + ohci->ohci_die = ohci_die_fn; + + if (num_ports > OHCI_MAX_PORTS) { + error_setg(errp, "OHCI num-ports=%u is too big (limit is %u ports)", + num_ports, OHCI_MAX_PORTS); + return; + } + + if (usb_frame_time == 0) { +#ifdef OHCI_TIME_WARP + usb_frame_time = NANOSECONDS_PER_SECOND; + usb_bit_time = NANOSECONDS_PER_SECOND / (USB_HZ / 1000); +#else + usb_frame_time = NANOSECONDS_PER_SECOND / 1000; + if (NANOSECONDS_PER_SECOND >= USB_HZ) { + usb_bit_time = NANOSECONDS_PER_SECOND / USB_HZ; + } else { + usb_bit_time = 1; + } +#endif + trace_usb_ohci_init_time(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; + } + usb_register_companion(masterbus, ports, num_ports, + firstport, ohci, &ohci_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL, + &err); + if (err) { + error_propagate(errp, err); + return; + } + } else { + usb_bus_new(&ohci->bus, sizeof(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, OBJECT(dev), &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; + + ohci->eof_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + ohci_frame_boundary, ohci); +} + +/** + * A typical OHCI will stop operating and set itself into error state + * (which can be queried by MMIO) to signal that it got an error. + */ +void ohci_sysbus_die(struct OHCIState *ohci) +{ + trace_usb_ohci_die(); + + ohci_set_interrupt(ohci, OHCI_INTR_UE); + ohci_bus_stop(ohci); +} + +static void ohci_realize_pxa(DeviceState *dev, Error **errp) +{ + OHCISysBusState *s = SYSBUS_OHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Error *err = NULL; + + usb_ohci_init(&s->ohci, dev, s->num_ports, s->dma_offset, + s->masterbus, s->firstport, + &address_space_memory, ohci_sysbus_die, &err); + if (err) { + error_propagate(errp, err); + return; + } + sysbus_init_irq(sbd, &s->ohci.irq); + sysbus_init_mmio(sbd, &s->ohci.mem); +} + +static void usb_ohci_reset_sysbus(DeviceState *dev) +{ + OHCISysBusState *s = SYSBUS_OHCI(dev); + OHCIState *ohci = &s->ohci; + + ohci_hard_reset(ohci); +} + +static const VMStateDescription vmstate_ohci_state_port = { + .name = "ohci-core/port", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(ctrl, OHCIPort), + VMSTATE_END_OF_LIST() + }, +}; + +static bool ohci_eof_timer_needed(void *opaque) +{ + OHCIState *ohci = opaque; + + return timer_pending(ohci->eof_timer); +} + +static const VMStateDescription vmstate_ohci_eof_timer = { + .name = "ohci-core/eof-timer", + .version_id = 1, + .minimum_version_id = 1, + .needed = ohci_eof_timer_needed, + .fields = (VMStateField[]) { + VMSTATE_TIMER_PTR(eof_timer, OHCIState), + VMSTATE_END_OF_LIST() + }, +}; + +const VMStateDescription vmstate_ohci_state = { + .name = "ohci-core", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_INT64(sof_time, OHCIState), + VMSTATE_UINT32(ctl, OHCIState), + VMSTATE_UINT32(status, OHCIState), + VMSTATE_UINT32(intr_status, OHCIState), + VMSTATE_UINT32(intr, OHCIState), + VMSTATE_UINT32(hcca, OHCIState), + VMSTATE_UINT32(ctrl_head, OHCIState), + VMSTATE_UINT32(ctrl_cur, OHCIState), + VMSTATE_UINT32(bulk_head, OHCIState), + VMSTATE_UINT32(bulk_cur, OHCIState), + VMSTATE_UINT32(per_cur, OHCIState), + VMSTATE_UINT32(done, OHCIState), + VMSTATE_INT32(done_count, OHCIState), + VMSTATE_UINT16(fsmps, OHCIState), + VMSTATE_UINT8(fit, OHCIState), + VMSTATE_UINT16(fi, OHCIState), + VMSTATE_UINT8(frt, OHCIState), + VMSTATE_UINT16(frame_number, OHCIState), + VMSTATE_UINT16(padding, OHCIState), + VMSTATE_UINT32(pstart, OHCIState), + VMSTATE_UINT32(lst, OHCIState), + VMSTATE_UINT32(rhdesc_a, OHCIState), + VMSTATE_UINT32(rhdesc_b, OHCIState), + VMSTATE_UINT32(rhstatus, OHCIState), + VMSTATE_STRUCT_ARRAY(rhport, OHCIState, OHCI_MAX_PORTS, 0, + vmstate_ohci_state_port, OHCIPort), + VMSTATE_UINT32(hstatus, OHCIState), + VMSTATE_UINT32(hmask, OHCIState), + VMSTATE_UINT32(hreset, OHCIState), + VMSTATE_UINT32(htest, OHCIState), + VMSTATE_UINT32(old_ctl, OHCIState), + VMSTATE_UINT8_ARRAY(usb_buf, OHCIState, 8192), + VMSTATE_UINT32(async_td, OHCIState), + VMSTATE_BOOL(async_complete, OHCIState), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription*[]) { + &vmstate_ohci_eof_timer, + NULL + } +}; + +static Property ohci_sysbus_properties[] = { + DEFINE_PROP_STRING("masterbus", OHCISysBusState, masterbus), + DEFINE_PROP_UINT32("num-ports", OHCISysBusState, num_ports, 3), + DEFINE_PROP_UINT32("firstport", OHCISysBusState, firstport, 0), + DEFINE_PROP_DMAADDR("dma-offset", OHCISysBusState, dma_offset, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ohci_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = ohci_realize_pxa; + set_bit(DEVICE_CATEGORY_USB, dc->categories); + dc->desc = "OHCI USB Controller"; + device_class_set_props(dc, ohci_sysbus_properties); + dc->reset = usb_ohci_reset_sysbus; +} + +static const TypeInfo ohci_sysbus_info = { + .name = TYPE_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_sysbus_info); +} + +type_init(ohci_register_types) diff --git a/hw/usb/hcd-ohci.h b/hw/usb/hcd-ohci.h new file mode 100644 index 000000000..11ac57058 --- /dev/null +++ b/hw/usb/hcd-ohci.h @@ -0,0 +1,121 @@ +/* + * 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.1 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/>. + */ + +#ifndef HCD_OHCI_H +#define HCD_OHCI_H + +#include "sysemu/dma.h" +#include "hw/usb.h" +#include "qom/object.h" + +/* Number of Downstream Ports on the root hub: */ +#define OHCI_MAX_PORTS 15 + +typedef struct OHCIPort { + USBPort port; + uint32_t ctrl; +} OHCIPort; + +typedef struct OHCIState { + USBBus bus; + qemu_irq irq; + MemoryRegion mem; + AddressSpace *as; + uint32_t 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; + int32_t done_count; + + /* Frame counter partition */ + uint16_t fsmps; + uint8_t fit; + uint16_t fi; + uint8_t frt; + 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 */ + dma_addr_t localmem_base; + + /* Active packets. */ + uint32_t old_ctl; + USBPacket usb_packet; + uint8_t usb_buf[8192]; + uint32_t async_td; + bool async_complete; + + void (*ohci_die)(struct OHCIState *ohci); +} OHCIState; + +#define TYPE_SYSBUS_OHCI "sysbus-ohci" +OBJECT_DECLARE_SIMPLE_TYPE(OHCISysBusState, SYSBUS_OHCI) + +struct OHCISysBusState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + OHCIState ohci; + char *masterbus; + uint32_t num_ports; + uint32_t firstport; + dma_addr_t dma_offset; +}; + +extern const VMStateDescription vmstate_ohci_state; + +void usb_ohci_init(OHCIState *ohci, DeviceState *dev, uint32_t num_ports, + dma_addr_t localmem_base, char *masterbus, + uint32_t firstport, AddressSpace *as, + void (*ohci_die_fn)(struct OHCIState *), Error **errp); +void ohci_bus_stop(OHCIState *ohci); +void ohci_stop_endpoints(OHCIState *ohci); +void ohci_hard_reset(OHCIState *ohci); +void ohci_sysbus_die(struct OHCIState *ohci); + +#endif diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c new file mode 100644 index 000000000..d1b5657d7 --- /dev/null +++ b/hw/usb/hcd-uhci.c @@ -0,0 +1,1370 @@ +/* + * 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 "qemu/osdep.h" +#include "hw/usb.h" +#include "hw/usb/uhci-regs.h" +#include "migration/vmstate.h" +#include "hw/pci/pci.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/timer.h" +#include "qemu/iov.h" +#include "sysemu/dma.h" +#include "trace.h" +#include "qemu/main-loop.h" +#include "qemu/module.h" +#include "qom/object.h" +#include "hcd-uhci.h" + +#define FRAME_TIMER_FREQ 1000 + +#define FRAME_MAX_LOOPS 256 + +/* Must be large enough to handle 10 frame delay for initial isoc requests */ +#define QH_VALID 32 + +#define MAX_FRAMES_PER_TICK (QH_VALID / 2) + +enum { + TD_RESULT_STOP_FRAME = 10, + TD_RESULT_COMPLETE, + TD_RESULT_NEXT_QH, + TD_RESULT_ASYNC_START, + TD_RESULT_ASYNC_CONT, +}; + +typedef struct UHCIState UHCIState; +typedef struct UHCIAsync UHCIAsync; +typedef struct UHCIPCIDeviceClass UHCIPCIDeviceClass; + +struct UHCIPCIDeviceClass { + PCIDeviceClass parent_class; + UHCIInfo info; +}; + +/* + * Pending async transaction. + * 'packet' must be the first field because completion + * handler does "(UHCIAsync *) pkt" cast. + */ + +struct UHCIAsync { + USBPacket packet; + uint8_t static_buf[64]; /* 64 bytes is enough, except for isoc packets */ + uint8_t *buf; + UHCIQueue *queue; + QTAILQ_ENTRY(UHCIAsync) next; + uint32_t td_addr; + uint8_t done; +}; + +struct UHCIQueue { + uint32_t qh_addr; + uint32_t token; + UHCIState *uhci; + USBEndpoint *ep; + QTAILQ_ENTRY(UHCIQueue) next; + QTAILQ_HEAD(, UHCIAsync) asyncs; + int8_t valid; +}; + +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 void uhci_async_cancel(UHCIAsync *async); +static void uhci_queue_fill(UHCIQueue *q, UHCI_TD *td); +static void uhci_resume(void *opaque); + +static inline int32_t uhci_queue_token(UHCI_TD *td) +{ + if ((td->token & (0xf << 15)) == 0) { + /* ctrl ep, cover ep and dev, not pid! */ + return td->token & 0x7ff00; + } else { + /* covers ep, dev, pid -> identifies the endpoint */ + return td->token & 0x7ffff; + } +} + +static UHCIQueue *uhci_queue_new(UHCIState *s, uint32_t qh_addr, UHCI_TD *td, + USBEndpoint *ep) +{ + UHCIQueue *queue; + + queue = g_new0(UHCIQueue, 1); + queue->uhci = s; + queue->qh_addr = qh_addr; + queue->token = uhci_queue_token(td); + queue->ep = ep; + QTAILQ_INIT(&queue->asyncs); + QTAILQ_INSERT_HEAD(&s->queues, queue, next); + queue->valid = QH_VALID; + trace_usb_uhci_queue_add(queue->token); + return queue; +} + +static void uhci_queue_free(UHCIQueue *queue, const char *reason) +{ + UHCIState *s = queue->uhci; + UHCIAsync *async; + + while (!QTAILQ_EMPTY(&queue->asyncs)) { + async = QTAILQ_FIRST(&queue->asyncs); + uhci_async_cancel(async); + } + usb_device_ep_stopped(queue->ep->dev, queue->ep); + + trace_usb_uhci_queue_del(queue->token, reason); + QTAILQ_REMOVE(&s->queues, queue, next); + g_free(queue); +} + +static UHCIQueue *uhci_queue_find(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; + } + } + return NULL; +} + +static bool uhci_queue_verify(UHCIQueue *queue, uint32_t qh_addr, UHCI_TD *td, + uint32_t td_addr, bool queuing) +{ + UHCIAsync *first = QTAILQ_FIRST(&queue->asyncs); + uint32_t queue_token_addr = (queue->token >> 8) & 0x7f; + + return queue->qh_addr == qh_addr && + queue->token == uhci_queue_token(td) && + queue_token_addr == queue->ep->dev->addr && + (queuing || !(td->ctrl & TD_CTRL_ACTIVE) || first == NULL || + first->td_addr == td_addr); +} + +static UHCIAsync *uhci_async_alloc(UHCIQueue *queue, uint32_t td_addr) +{ + UHCIAsync *async = g_new0(UHCIAsync, 1); + + async->queue = queue; + async->td_addr = td_addr; + usb_packet_init(&async->packet); + trace_usb_uhci_packet_add(async->queue->token, async->td_addr); + + return async; +} + +static void uhci_async_free(UHCIAsync *async) +{ + trace_usb_uhci_packet_del(async->queue->token, async->td_addr); + usb_packet_cleanup(&async->packet); + if (async->buf != async->static_buf) { + g_free(async->buf); + } + g_free(async); +} + +static void uhci_async_link(UHCIAsync *async) +{ + UHCIQueue *queue = async->queue; + QTAILQ_INSERT_TAIL(&queue->asyncs, async, next); + trace_usb_uhci_packet_link_async(async->queue->token, async->td_addr); +} + +static void uhci_async_unlink(UHCIAsync *async) +{ + UHCIQueue *queue = async->queue; + QTAILQ_REMOVE(&queue->asyncs, async, next); + trace_usb_uhci_packet_unlink_async(async->queue->token, async->td_addr); +} + +static void uhci_async_cancel(UHCIAsync *async) +{ + uhci_async_unlink(async); + trace_usb_uhci_packet_cancel(async->queue->token, async->td_addr, + 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; + + QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) { + if (!queue->valid) { + uhci_queue_free(queue, "validate-end"); + } + } +} + +static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev) +{ + UHCIQueue *queue, *n; + + QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) { + if (queue->ep->dev == dev) { + uhci_queue_free(queue, "cancel-device"); + } + } +} + +static void uhci_async_cancel_all(UHCIState *s) +{ + UHCIQueue *queue, *nq; + + QTAILQ_FOREACH_SAFE(queue, &s->queues, next, nq) { + uhci_queue_free(queue, "cancel-all"); + } +} + +static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t td_addr) +{ + UHCIQueue *queue; + UHCIAsync *async; + + QTAILQ_FOREACH(queue, &s->queues, next) { + QTAILQ_FOREACH(async, &queue->asyncs, next) { + if (async->td_addr == td_addr) { + return async; + } + } + } + return NULL; +} + +static void uhci_update_irq(UHCIState *s) +{ + int level = 0; + 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; + } + qemu_set_irq(s->irq, level); +} + +static void uhci_reset(DeviceState *dev) +{ + PCIDevice *d = PCI_DEVICE(dev); + UHCIState *s = UHCI(d); + uint8_t *pci_conf; + int i; + UHCIPort *port; + + trace_usb_uhci_reset(); + + pci_conf = s->dev.config; + + pci_conf[0x6a] = 0x01; /* usb clock */ + pci_conf[0x6b] = 0x00; + s->cmd = 0; + s->status = UHCI_STS_HCHALTED; + 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); + qemu_bh_cancel(s->bh); + uhci_update_irq(s); +} + +static const VMStateDescription vmstate_uhci_port = { + .name = "uhci port", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(ctrl, UHCIPort), + VMSTATE_END_OF_LIST() + } +}; + +static int uhci_post_load(void *opaque, int version_id) +{ + UHCIState *s = opaque; + + if (version_id < 2) { + s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ); + } + return 0; +} + +static const VMStateDescription vmstate_uhci = { + .name = "uhci", + .version_id = 3, + .minimum_version_id = 1, + .post_load = uhci_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, UHCIState), + VMSTATE_UINT8_EQUAL(num_ports_vmstate, UHCIState, NULL), + 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_PTR(frame_timer, UHCIState), + VMSTATE_INT64_V(expire_time, UHCIState, 2), + VMSTATE_UINT32_V(pending_int_mask, UHCIState, 3), + VMSTATE_END_OF_LIST() + } +}; + +static void uhci_port_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + UHCIState *s = opaque; + + trace_usb_uhci_mmio_writew(addr, val); + + switch(addr) { + case 0x00: + if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) { + /* start frame processing */ + trace_usb_uhci_schedule_start(); + s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ); + timer_mod(s->frame_timer, s->expire_time); + 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(DEVICE(s)); + return; + } + if (val & UHCI_CMD_HCRESET) { + uhci_reset(DEVICE(s)); + return; + } + s->cmd = val; + if (val & UHCI_CMD_EGSM) { + if ((s->ports[0].ctrl & UHCI_PORT_RD) || + (s->ports[1].ctrl & UHCI_PORT_RD)) { + uhci_resume(s); + } + } + 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 0x08: + s->fl_base_addr &= 0xffff0000; + s->fl_base_addr |= val & ~0xfff; + break; + case 0x0a: + s->fl_base_addr &= 0x0000ffff; + s->fl_base_addr |= (val << 16); + break; + case 0x0c: + s->sof_timing = val & 0xff; + 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; + /* enabled may only be set if a device is connected */ + if (!(port->ctrl & UHCI_PORT_CCS)) { + val &= ~UHCI_PORT_EN; + } + 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 uint64_t uhci_port_read(void *opaque, hwaddr addr, unsigned size) +{ + UHCIState *s = opaque; + uint32_t val; + + 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 0x08: + val = s->fl_base_addr & 0xffff; + break; + case 0x0a: + val = (s->fl_base_addr >> 16) & 0xffff; + break; + case 0x0c: + val = s->sof_timing; + 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; + } + + trace_usb_uhci_mmio_readw(addr, val); + + 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_read_td(UHCIState *s, UHCI_TD *td, uint32_t link) +{ + 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); +} + +static int uhci_handle_td_error(UHCIState *s, UHCI_TD *td, uint32_t td_addr, + int status, uint32_t *int_mask) +{ + uint32_t queue_token = uhci_queue_token(td); + int ret; + + switch (status) { + case USB_RET_NAK: + td->ctrl |= TD_CTRL_NAK; + return TD_RESULT_NEXT_QH; + + case USB_RET_STALL: + td->ctrl |= TD_CTRL_STALL; + trace_usb_uhci_packet_complete_stall(queue_token, td_addr); + ret = TD_RESULT_NEXT_QH; + break; + + case USB_RET_BABBLE: + td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; + /* frame interrupted */ + trace_usb_uhci_packet_complete_babble(queue_token, td_addr); + ret = TD_RESULT_STOP_FRAME; + break; + + case USB_RET_IOERROR: + case USB_RET_NODEV: + default: + td->ctrl |= TD_CTRL_TIMEOUT; + td->ctrl &= ~(3 << TD_CTRL_ERROR_SHIFT); + trace_usb_uhci_packet_complete_error(queue_token, td_addr); + ret = TD_RESULT_NEXT_QH; + break; + } + + td->ctrl &= ~TD_CTRL_ACTIVE; + s->status |= UHCI_STS_USBERR; + if (td->ctrl & TD_CTRL_IOC) { + *int_mask |= 0x01; + } + uhci_update_irq(s); + return ret; +} + +static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_t *int_mask) +{ + int len = 0, max_len; + uint8_t pid; + + max_len = ((td->token >> 21) + 1) & 0x7ff; + pid = td->token & 0xff; + + if (td->ctrl & TD_CTRL_IOS) + td->ctrl &= ~TD_CTRL_ACTIVE; + + if (async->packet.status != USB_RET_SUCCESS) { + return uhci_handle_td_error(s, td, async->td_addr, + async->packet.status, int_mask); + } + + len = async->packet.actual_length; + 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) { + pci_dma_write(&s->dev, td->buffer, async->buf, len); + if ((td->ctrl & TD_CTRL_SPD) && len < max_len) { + *int_mask |= 0x02; + /* short packet: do not update QH */ + trace_usb_uhci_packet_complete_shortxfer(async->queue->token, + async->td_addr); + return TD_RESULT_NEXT_QH; + } + } + + /* success */ + trace_usb_uhci_packet_complete_success(async->queue->token, + async->td_addr); + return TD_RESULT_COMPLETE; +} + +static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, + UHCI_TD *td, uint32_t td_addr, uint32_t *int_mask) +{ + int ret, max_len; + bool spd; + bool queuing = (q != NULL); + uint8_t pid = td->token & 0xff; + UHCIAsync *async; + + async = uhci_async_find_td(s, td_addr); + if (async) { + if (uhci_queue_verify(async->queue, qh_addr, td, td_addr, queuing)) { + assert(q == NULL || q == async->queue); + q = async->queue; + } else { + uhci_queue_free(async->queue, "guest re-used pending td"); + async = NULL; + } + } + + if (q == NULL) { + q = uhci_queue_find(s, td); + if (q && !uhci_queue_verify(q, qh_addr, td, td_addr, queuing)) { + uhci_queue_free(q, "guest re-used qh"); + q = NULL; + } + } + + if (q) { + q->valid = QH_VALID; + } + + /* Is active ? */ + if (!(td->ctrl & TD_CTRL_ACTIVE)) { + if (async) { + /* Guest marked a pending td non-active, cancel the queue */ + uhci_queue_free(async->queue, "pending td non-active"); + } + /* + * ehci11d spec page 22: "Even if the Active bit in the TD is already + * cleared when the TD is fetched ... an IOC interrupt is generated" + */ + if (td->ctrl & TD_CTRL_IOC) { + *int_mask |= 0x01; + } + return TD_RESULT_NEXT_QH; + } + + switch (pid) { + case USB_TOKEN_OUT: + case USB_TOKEN_SETUP: + case USB_TOKEN_IN: + break; + default: + /* invalid pid : frame interrupted */ + s->status |= UHCI_STS_HCPERR; + s->cmd &= ~UHCI_CMD_RS; + uhci_update_irq(s); + return TD_RESULT_STOP_FRAME; + } + + if (async) { + if (queuing) { + /* we are busy filling the queue, we are not prepared + to consume completed packages then, just leave them + in async state */ + return TD_RESULT_ASYNC_CONT; + } + if (!async->done) { + UHCI_TD last_td; + UHCIAsync *last = QTAILQ_LAST(&async->queue->asyncs); + /* + * While we are waiting for the current td to complete, the guest + * may have added more tds to the queue. Note we re-read the td + * rather then caching it, as we want to see guest made changes! + */ + uhci_read_td(s, &last_td, last->td_addr); + uhci_queue_fill(async->queue, &last_td); + + return TD_RESULT_ASYNC_CONT; + } + uhci_async_unlink(async); + goto done; + } + + if (s->completions_only) { + return TD_RESULT_ASYNC_CONT; + } + + /* Allocate new packet */ + if (q == NULL) { + USBDevice *dev; + USBEndpoint *ep; + + dev = uhci_find_device(s, (td->token >> 8) & 0x7f); + if (dev == NULL) { + return uhci_handle_td_error(s, td, td_addr, USB_RET_NODEV, + int_mask); + } + ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf); + q = uhci_queue_new(s, qh_addr, td, ep); + } + async = uhci_async_alloc(q, td_addr); + + max_len = ((td->token >> 21) + 1) & 0x7ff; + spd = (pid == USB_TOKEN_IN && (td->ctrl & TD_CTRL_SPD) != 0); + usb_packet_setup(&async->packet, pid, q->ep, 0, td_addr, spd, + (td->ctrl & TD_CTRL_IOC) != 0); + if (max_len <= sizeof(async->static_buf)) { + async->buf = async->static_buf; + } else { + async->buf = g_malloc(max_len); + } + usb_packet_addbuf(&async->packet, async->buf, max_len); + + switch(pid) { + case USB_TOKEN_OUT: + case USB_TOKEN_SETUP: + pci_dma_read(&s->dev, td->buffer, async->buf, max_len); + usb_handle_packet(q->ep->dev, &async->packet); + if (async->packet.status == USB_RET_SUCCESS) { + async->packet.actual_length = max_len; + } + break; + + case USB_TOKEN_IN: + usb_handle_packet(q->ep->dev, &async->packet); + break; + + default: + abort(); /* Never to execute */ + } + + if (async->packet.status == USB_RET_ASYNC) { + uhci_async_link(async); + if (!queuing) { + uhci_queue_fill(q, td); + } + return TD_RESULT_ASYNC_START; + } + +done: + ret = uhci_complete_td(s, td, async, int_mask); + uhci_async_free(async); + return ret; +} + +static void uhci_async_complete(USBPort *port, USBPacket *packet) +{ + UHCIAsync *async = container_of(packet, UHCIAsync, packet); + UHCIState *s = async->queue->uhci; + + if (packet->status == USB_RET_REMOVE_FROM_QUEUE) { + uhci_async_cancel(async); + return; + } + + async->done = 1; + /* Force processing of this packet *now*, needed for migration */ + s->completions_only = true; + qemu_bh_schedule(s->bh); +} + +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_queue_fill(UHCIQueue *q, UHCI_TD *td) +{ + uint32_t int_mask = 0; + uint32_t plink = td->link; + UHCI_TD ptd; + int ret; + + while (is_valid(plink)) { + uhci_read_td(q->uhci, &ptd, plink); + if (!(ptd.ctrl & TD_CTRL_ACTIVE)) { + break; + } + if (uhci_queue_token(&ptd) != q->token) { + break; + } + trace_usb_uhci_td_queue(plink & ~0xf, ptd.ctrl, ptd.token); + ret = uhci_handle_td(q->uhci, q, q->qh_addr, &ptd, plink, &int_mask); + if (ret == TD_RESULT_ASYNC_CONT) { + break; + } + assert(ret == TD_RESULT_ASYNC_START); + assert(int_mask == 0); + plink = ptd.link; + } + usb_device_flush_ep_queue(q->ep->dev, q->ep); +} + +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; + int cnt, ret; + UHCI_TD td; + UHCI_QH qh; + QhDb qhdb; + + frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2); + + 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 (!s->completions_only && s->frame_bytes >= s->frame_bandwidth) { + /* We've reached the usb 1.1 bandwidth, which is + 1280 bytes/frame, stop processing */ + trace_usb_uhci_frame_stop_bandwidth(); + break; + } + if (is_qh(link)) { + /* QH */ + trace_usb_uhci_qh_load(link & ~0xf); + + 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 no transaction has been done + * since we've been here last time. + */ + if (td_count == 0) { + trace_usb_uhci_frame_loop_stop_idle(); + break; + } else { + trace_usb_uhci_frame_loop_continue(); + 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); + + 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 */ + uhci_read_td(s, &td, link); + trace_usb_uhci_td_load(curr_qh & ~0xf, link & ~0xf, td.ctrl, td.token); + + old_td_ctrl = td.ctrl; + ret = uhci_handle_td(s, NULL, curr_qh, &td, link, &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 TD_RESULT_STOP_FRAME: /* interrupted frame */ + goto out; + + case TD_RESULT_NEXT_QH: + case TD_RESULT_ASYNC_CONT: + trace_usb_uhci_td_nextqh(curr_qh & ~0xf, link & ~0xf); + link = curr_qh ? qh.link : td.link; + continue; + + case TD_RESULT_ASYNC_START: + trace_usb_uhci_td_async(curr_qh & ~0xf, link & ~0xf); + link = curr_qh ? qh.link : td.link; + continue; + + case TD_RESULT_COMPLETE: + trace_usb_uhci_td_complete(curr_qh & ~0xf, link & ~0xf); + link = td.link; + td_count++; + s->frame_bytes += (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 */ + 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_bh(void *opaque) +{ + UHCIState *s = opaque; + uhci_process_frame(s); +} + +static void uhci_frame_timer(void *opaque) +{ + UHCIState *s = opaque; + uint64_t t_now, t_last_run; + int i, frames; + const uint64_t frame_t = NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ; + + s->completions_only = false; + qemu_bh_cancel(s->bh); + + if (!(s->cmd & UHCI_CMD_RS)) { + /* Full stop */ + trace_usb_uhci_schedule_stop(); + timer_del(s->frame_timer); + uhci_async_cancel_all(s); + /* set hchalted bit in status - UHCI11D 2.1.2 */ + s->status |= UHCI_STS_HCHALTED; + return; + } + + /* We still store expire_time in our state, for migration */ + t_last_run = s->expire_time - frame_t; + t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + /* Process up to MAX_FRAMES_PER_TICK frames */ + frames = (t_now - t_last_run) / frame_t; + if (frames > s->maxframes) { + int skipped = frames - s->maxframes; + s->expire_time += skipped * frame_t; + s->frnum = (s->frnum + skipped) & 0x7ff; + frames -= skipped; + } + if (frames > MAX_FRAMES_PER_TICK) { + frames = MAX_FRAMES_PER_TICK; + } + + for (i = 0; i < frames; i++) { + s->frame_bytes = 0; + trace_usb_uhci_frame_start(s->frnum); + uhci_async_validate_begin(s); + uhci_process_frame(s); + uhci_async_validate_end(s); + /* The spec says frnum is the frame currently being processed, and + * the guest must look at frnum - 1 on interrupt, so inc frnum now */ + s->frnum = (s->frnum + 1) & 0x7ff; + s->expire_time += frame_t; + } + + /* Complete the previous frame(s) */ + 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; + + timer_mod(s->frame_timer, t_now + frame_t); +} + +static const MemoryRegionOps uhci_ioport_ops = { + .read = uhci_port_read, + .write = uhci_port_write, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .impl.min_access_size = 2, + .impl.max_access_size = 2, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +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 = { +}; + +void usb_uhci_common_realize(PCIDevice *dev, Error **errp) +{ + Error *err = NULL; + PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); + UHCIPCIDeviceClass *u = container_of(pc, UHCIPCIDeviceClass, parent_class); + UHCIState *s = UHCI(dev); + uint8_t *pci_conf = s->dev.config; + int i; + + pci_conf[PCI_CLASS_PROG] = 0x00; + /* TODO: reset value should be 0. */ + pci_conf[USB_SBRN] = USB_RELEASE_1; /* release number */ + pci_config_set_interrupt_pin(pci_conf, u->info.irq_pin + 1); + s->irq = pci_allocate_irq(dev); + + if (s->masterbus) { + USBPort *ports[NB_PORTS]; + for(i = 0; i < NB_PORTS; i++) { + ports[i] = &s->ports[i].port; + } + usb_register_companion(s->masterbus, ports, NB_PORTS, + s->firstport, s, &uhci_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL, + &err); + if (err) { + error_propagate(errp, err); + return; + } + } else { + usb_bus_new(&s->bus, sizeof(s->bus), &uhci_bus_ops, DEVICE(dev)); + 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->bh = qemu_bh_new(uhci_bh, s); + s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, uhci_frame_timer, s); + s->num_ports_vmstate = NB_PORTS; + QTAILQ_INIT(&s->queues); + + memory_region_init_io(&s->io_bar, OBJECT(s), &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); +} + +static void usb_uhci_exit(PCIDevice *dev) +{ + UHCIState *s = UHCI(dev); + + trace_usb_uhci_exit(); + + if (s->frame_timer) { + timer_free(s->frame_timer); + s->frame_timer = NULL; + } + + if (s->bh) { + qemu_bh_delete(s->bh); + } + + uhci_async_cancel_all(s); + + if (!s->masterbus) { + usb_bus_release(&s->bus); + } +} + +static Property uhci_properties_companion[] = { + DEFINE_PROP_STRING("masterbus", UHCIState, masterbus), + DEFINE_PROP_UINT32("firstport", UHCIState, firstport, 0), + DEFINE_PROP_UINT32("bandwidth", UHCIState, frame_bandwidth, 1280), + DEFINE_PROP_UINT32("maxframes", UHCIState, maxframes, 128), + DEFINE_PROP_END_OF_LIST(), +}; +static Property uhci_properties_standalone[] = { + DEFINE_PROP_UINT32("bandwidth", UHCIState, frame_bandwidth, 1280), + DEFINE_PROP_UINT32("maxframes", UHCIState, maxframes, 128), + DEFINE_PROP_END_OF_LIST(), +}; + +static void uhci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->class_id = PCI_CLASS_SERIAL_USB; + dc->vmsd = &vmstate_uhci; + dc->reset = uhci_reset; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo uhci_pci_type_info = { + .name = TYPE_UHCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_size = sizeof(UHCIPCIDeviceClass), + .abstract = true, + .class_init = uhci_class_init, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { }, + }, +}; + +void uhci_data_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + UHCIPCIDeviceClass *u = container_of(k, UHCIPCIDeviceClass, parent_class); + UHCIInfo *info = data; + + k->realize = info->realize ? info->realize : usb_uhci_common_realize; + k->exit = info->unplug ? usb_uhci_exit : NULL; + k->vendor_id = info->vendor_id; + k->device_id = info->device_id; + k->revision = info->revision; + if (!info->unplug) { + /* uhci controllers in companion setups can't be hotplugged */ + dc->hotpluggable = false; + device_class_set_props(dc, uhci_properties_companion); + } else { + device_class_set_props(dc, uhci_properties_standalone); + } + if (info->notuser) { + dc->user_creatable = false; + } + u->info = *info; +} + +static UHCIInfo uhci_info[] = { + { + .name = "piix3-usb-uhci", + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82371SB_2, + .revision = 0x01, + .irq_pin = 3, + .unplug = true, + },{ + .name = "piix4-usb-uhci", + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82371AB_2, + .revision = 0x01, + .irq_pin = 3, + .unplug = true, + },{ + .name = "ich9-usb-uhci1", /* 00:1d.0 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI1, + .revision = 0x03, + .irq_pin = 0, + .unplug = false, + },{ + .name = "ich9-usb-uhci2", /* 00:1d.1 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI2, + .revision = 0x03, + .irq_pin = 1, + .unplug = false, + },{ + .name = "ich9-usb-uhci3", /* 00:1d.2 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI3, + .revision = 0x03, + .irq_pin = 2, + .unplug = false, + },{ + .name = "ich9-usb-uhci4", /* 00:1a.0 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI4, + .revision = 0x03, + .irq_pin = 0, + .unplug = false, + },{ + .name = "ich9-usb-uhci5", /* 00:1a.1 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI5, + .revision = 0x03, + .irq_pin = 1, + .unplug = false, + },{ + .name = "ich9-usb-uhci6", /* 00:1a.2 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI6, + .revision = 0x03, + .irq_pin = 2, + .unplug = false, + } +}; + +static void uhci_register_types(void) +{ + TypeInfo uhci_type_info = { + .parent = TYPE_UHCI, + .class_init = uhci_data_class_init, + }; + int i; + + type_register_static(&uhci_pci_type_info); + + for (i = 0; i < ARRAY_SIZE(uhci_info); i++) { + uhci_type_info.name = uhci_info[i].name; + uhci_type_info.class_data = uhci_info + i; + type_register(&uhci_type_info); + } +} + +type_init(uhci_register_types) diff --git a/hw/usb/hcd-uhci.h b/hw/usb/hcd-uhci.h new file mode 100644 index 000000000..c85ab7868 --- /dev/null +++ b/hw/usb/hcd-uhci.h @@ -0,0 +1,94 @@ +/* + * 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. + */ +#ifndef HW_USB_HCD_UHCI_H +#define HW_USB_HCD_UHCI_H + +#include "exec/memory.h" +#include "qemu/timer.h" +#include "hw/pci/pci.h" +#include "hw/usb.h" + +typedef struct UHCIQueue UHCIQueue; + +#define NB_PORTS 2 + +typedef struct UHCIPort { + USBPort port; + uint16_t ctrl; +} UHCIPort; + +typedef 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; + QEMUBH *bh; + uint32_t frame_bytes; + uint32_t frame_bandwidth; + bool completions_only; + UHCIPort ports[NB_PORTS]; + qemu_irq irq; + /* 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; + uint32_t maxframes; +} UHCIState; + +#define TYPE_UHCI "pci-uhci-usb" +DECLARE_INSTANCE_CHECKER(UHCIState, UHCI, TYPE_UHCI) + +typedef struct UHCIInfo { + const char *name; + uint16_t vendor_id; + uint16_t device_id; + uint8_t revision; + uint8_t irq_pin; + void (*realize)(PCIDevice *dev, Error **errp); + bool unplug; + bool notuser; /* disallow user_creatable */ +} UHCIInfo; + +void uhci_data_class_init(ObjectClass *klass, void *data); +void usb_uhci_common_realize(PCIDevice *dev, Error **errp); + +#endif diff --git a/hw/usb/hcd-xhci-nec.c b/hw/usb/hcd-xhci-nec.c new file mode 100644 index 000000000..13c9ac5db --- /dev/null +++ b/hw/usb/hcd-xhci-nec.c @@ -0,0 +1,85 @@ +/* + * 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.1 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 "qemu/osdep.h" +#include "hw/usb.h" +#include "qemu/module.h" +#include "hw/pci/pci.h" +#include "hw/qdev-properties.h" + +#include "hcd-xhci-pci.h" + +typedef struct XHCINecState { + /*< private >*/ + XHCIPciState parent_obj; + /*< public >*/ + uint32_t flags; + uint32_t intrs; + uint32_t slots; +} XHCINecState; + +static Property nec_xhci_properties[] = { + DEFINE_PROP_ON_OFF_AUTO("msi", XHCIPciState, msi, ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO("msix", XHCIPciState, msix, ON_OFF_AUTO_AUTO), + DEFINE_PROP_BIT("superspeed-ports-first", XHCINecState, flags, + XHCI_FLAG_SS_FIRST, true), + DEFINE_PROP_BIT("force-pcie-endcap", XHCINecState, flags, + XHCI_FLAG_FORCE_PCIE_ENDCAP, false), + DEFINE_PROP_UINT32("intrs", XHCINecState, intrs, XHCI_MAXINTRS), + DEFINE_PROP_UINT32("slots", XHCINecState, slots, XHCI_MAXSLOTS), + DEFINE_PROP_END_OF_LIST(), +}; + +static void nec_xhci_instance_init(Object *obj) +{ + XHCIPciState *pci = XHCI_PCI(obj); + XHCINecState *nec = container_of(pci, XHCINecState, parent_obj); + + pci->xhci.flags = nec->flags; + pci->xhci.numintrs = nec->intrs; + pci->xhci.numslots = nec->slots; +} + +static void nec_xhci_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, nec_xhci_properties); + k->vendor_id = PCI_VENDOR_ID_NEC; + k->device_id = PCI_DEVICE_ID_NEC_UPD720200; + k->revision = 0x03; +} + +static const TypeInfo nec_xhci_info = { + .name = TYPE_NEC_XHCI, + .parent = TYPE_XHCI_PCI, + .instance_size = sizeof(XHCINecState), + .instance_init = nec_xhci_instance_init, + .class_init = nec_xhci_class_init, +}; + +static void nec_xhci_register_types(void) +{ + type_register_static(&nec_xhci_info); +} + +type_init(nec_xhci_register_types) diff --git a/hw/usb/hcd-xhci-pci.c b/hw/usb/hcd-xhci-pci.c new file mode 100644 index 000000000..e934b1a5b --- /dev/null +++ b/hw/usb/hcd-xhci-pci.c @@ -0,0 +1,262 @@ +/* + * USB xHCI controller with PCI bus emulation + * + * SPDX-FileCopyrightText: 2011 Securiforest + * SPDX-FileContributor: Hector Martin <hector@marcansoft.com> + * SPDX-sourceInfo: Based on usb-ohci.c, emulates Renesas NEC USB 3.0 + * SPDX-FileCopyrightText: 2020 Xilinx + * SPDX-FileContributor: Sai Pavan Boddu <sai.pavan.boddu@xilinx.com> + * SPDX-sourceInfo: Moved the pci specific content for hcd-xhci.c to + * hcd-xhci-pci.c + * + * 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.1 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 "qemu/osdep.h" +#include "hw/pci/pci.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "hw/pci/msi.h" +#include "hw/pci/msix.h" +#include "hcd-xhci-pci.h" +#include "trace.h" +#include "qapi/error.h" + +#define OFF_MSIX_TABLE 0x3000 +#define OFF_MSIX_PBA 0x3800 + +static void xhci_pci_intr_update(XHCIState *xhci, int n, bool enable) +{ + XHCIPciState *s = container_of(xhci, XHCIPciState, xhci); + PCIDevice *pci_dev = PCI_DEVICE(s); + + if (!msix_enabled(pci_dev)) { + return; + } + if (enable == !!xhci->intr[n].msix_used) { + return; + } + if (enable) { + trace_usb_xhci_irq_msix_use(n); + msix_vector_use(pci_dev, n); + xhci->intr[n].msix_used = true; + } else { + trace_usb_xhci_irq_msix_unuse(n); + msix_vector_unuse(pci_dev, n); + xhci->intr[n].msix_used = false; + } +} + +static bool xhci_pci_intr_raise(XHCIState *xhci, int n, bool level) +{ + XHCIPciState *s = container_of(xhci, XHCIPciState, xhci); + PCIDevice *pci_dev = PCI_DEVICE(s); + + if (n == 0 && + !(msix_enabled(pci_dev) || + msi_enabled(pci_dev))) { + pci_set_irq(pci_dev, level); + } + + if (msix_enabled(pci_dev) && level) { + msix_notify(pci_dev, n); + return true; + } + + if (msi_enabled(pci_dev) && level) { + msi_notify(pci_dev, n); + return true; + } + + return false; +} + +static void xhci_pci_reset(DeviceState *dev) +{ + XHCIPciState *s = XHCI_PCI(dev); + + device_legacy_reset(DEVICE(&s->xhci)); +} + +static int xhci_pci_vmstate_post_load(void *opaque, int version_id) +{ + XHCIPciState *s = XHCI_PCI(opaque); + PCIDevice *pci_dev = PCI_DEVICE(s); + int intr; + + for (intr = 0; intr < s->xhci.numintrs; intr++) { + if (s->xhci.intr[intr].msix_used) { + msix_vector_use(pci_dev, intr); + } else { + msix_vector_unuse(pci_dev, intr); + } + } + return 0; +} + +static void usb_xhci_pci_realize(struct PCIDevice *dev, Error **errp) +{ + int ret; + Error *err = NULL; + XHCIPciState *s = XHCI_PCI(dev); + + dev->config[PCI_CLASS_PROG] = 0x30; /* xHCI */ + dev->config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin 1 */ + dev->config[PCI_CACHE_LINE_SIZE] = 0x10; + dev->config[0x60] = 0x30; /* release number */ + + object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL); + s->xhci.intr_update = xhci_pci_intr_update; + s->xhci.intr_raise = xhci_pci_intr_raise; + if (!qdev_realize(DEVICE(&s->xhci), NULL, errp)) { + return; + } + if (strcmp(object_get_typename(OBJECT(dev)), TYPE_NEC_XHCI) == 0) { + s->xhci.nec_quirks = true; + } + + if (s->msi != ON_OFF_AUTO_OFF) { + ret = msi_init(dev, 0x70, s->xhci.numintrs, true, false, &err); + /* + * Any error other than -ENOTSUP(board's MSI support is broken) + * is a programming error + */ + assert(!ret || ret == -ENOTSUP); + if (ret && s->msi == ON_OFF_AUTO_ON) { + /* Can't satisfy user's explicit msi=on request, fail */ + error_append_hint(&err, "You have to use msi=auto (default) or " + "msi=off with this machine type.\n"); + error_propagate(errp, err); + return; + } + assert(!err || s->msi == ON_OFF_AUTO_AUTO); + /* With msi=auto, we fall back to MSI off silently */ + error_free(err); + } + pci_register_bar(dev, 0, + PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_64, + &s->xhci.mem); + + if (pci_bus_is_express(pci_get_bus(dev)) || + xhci_get_flag(&s->xhci, XHCI_FLAG_FORCE_PCIE_ENDCAP)) { + ret = pcie_endpoint_cap_init(dev, 0xa0); + assert(ret > 0); + } + + if (s->msix != ON_OFF_AUTO_OFF) { + /* TODO check for errors, and should fail when msix=on */ + msix_init(dev, s->xhci.numintrs, + &s->xhci.mem, 0, OFF_MSIX_TABLE, + &s->xhci.mem, 0, OFF_MSIX_PBA, + 0x90, NULL); + } + s->xhci.as = pci_get_address_space(dev); +} + +static void usb_xhci_pci_exit(PCIDevice *dev) +{ + XHCIPciState *s = XHCI_PCI(dev); + /* destroy msix memory region */ + if (dev->msix_table && dev->msix_pba + && dev->msix_entry_used) { + msix_uninit(dev, &s->xhci.mem, &s->xhci.mem); + } +} + +static const VMStateDescription vmstate_xhci_pci = { + .name = "xhci", + .version_id = 1, + .post_load = xhci_pci_vmstate_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(parent_obj, XHCIPciState), + VMSTATE_MSIX(parent_obj, XHCIPciState), + VMSTATE_STRUCT(xhci, XHCIPciState, 1, vmstate_xhci, XHCIState), + VMSTATE_END_OF_LIST() + } +}; + +static void xhci_instance_init(Object *obj) +{ + XHCIPciState *s = XHCI_PCI(obj); + /* + * QEMU_PCI_CAP_EXPRESS initialization does not depend on QEMU command + * line, therefore, no need to wait to realize like other devices + */ + PCI_DEVICE(obj)->cap_present |= QEMU_PCI_CAP_EXPRESS; + object_initialize_child(obj, "xhci-core", &s->xhci, TYPE_XHCI); + qdev_alias_all_properties(DEVICE(&s->xhci), obj); +} + +static void xhci_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = xhci_pci_reset; + dc->vmsd = &vmstate_xhci_pci; + set_bit(DEVICE_CATEGORY_USB, dc->categories); + k->realize = usb_xhci_pci_realize; + k->exit = usb_xhci_pci_exit; + k->class_id = PCI_CLASS_SERIAL_USB; +} + +static const TypeInfo xhci_pci_info = { + .name = TYPE_XHCI_PCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(XHCIPciState), + .class_init = xhci_class_init, + .instance_init = xhci_instance_init, + .abstract = true, + .interfaces = (InterfaceInfo[]) { + { INTERFACE_PCIE_DEVICE }, + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { } + }, +}; + +static void qemu_xhci_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->vendor_id = PCI_VENDOR_ID_REDHAT; + k->device_id = PCI_DEVICE_ID_REDHAT_XHCI; + k->revision = 0x01; +} + +static void qemu_xhci_instance_init(Object *obj) +{ + XHCIPciState *s = XHCI_PCI(obj); + XHCIState *xhci = &s->xhci; + + s->msi = ON_OFF_AUTO_OFF; + s->msix = ON_OFF_AUTO_AUTO; + xhci->numintrs = XHCI_MAXINTRS; + xhci->numslots = XHCI_MAXSLOTS; + xhci_set_flag(xhci, XHCI_FLAG_SS_FIRST); +} + +static const TypeInfo qemu_xhci_info = { + .name = TYPE_QEMU_XHCI, + .parent = TYPE_XHCI_PCI, + .class_init = qemu_xhci_class_init, + .instance_init = qemu_xhci_instance_init, +}; + +static void xhci_register_types(void) +{ + type_register_static(&xhci_pci_info); + type_register_static(&qemu_xhci_info); +} + +type_init(xhci_register_types) diff --git a/hw/usb/hcd-xhci-pci.h b/hw/usb/hcd-xhci-pci.h new file mode 100644 index 000000000..c193f7944 --- /dev/null +++ b/hw/usb/hcd-xhci-pci.h @@ -0,0 +1,44 @@ +/* + * 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 + * Date: 2020-01-1; Author: Sai Pavan Boddu <sai.pavan.boddu@xilinx.com> + * PCI hooks are moved from XHCIState to XHCIPciState + * + * 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.1 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/>. + */ + +#ifndef HW_USB_HCD_XHCI_PCI_H +#define HW_USB_HCD_XHCI_PCI_H + +#include "hw/usb.h" +#include "hcd-xhci.h" + +#define TYPE_XHCI_PCI "pci-xhci" +#define XHCI_PCI(obj) \ + OBJECT_CHECK(XHCIPciState, (obj), TYPE_XHCI_PCI) + + +typedef struct XHCIPciState { + /*< private >*/ + PCIDevice parent_obj; + /*< public >*/ + XHCIState xhci; + OnOffAuto msi; + OnOffAuto msix; +} XHCIPciState; + +#endif diff --git a/hw/usb/hcd-xhci-sysbus.c b/hw/usb/hcd-xhci-sysbus.c new file mode 100644 index 000000000..a14e43819 --- /dev/null +++ b/hw/usb/hcd-xhci-sysbus.c @@ -0,0 +1,123 @@ +/* + * USB xHCI controller for system-bus interface + * Based on hcd-echi-sysbus.c + + * SPDX-FileCopyrightText: 2020 Xilinx + * SPDX-FileContributor: Author: Sai Pavan Boddu <sai.pavan.boddu@xilinx.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "trace.h" +#include "qapi/error.h" +#include "hcd-xhci-sysbus.h" +#include "hw/acpi/aml-build.h" +#include "hw/irq.h" + +static bool xhci_sysbus_intr_raise(XHCIState *xhci, int n, bool level) +{ + XHCISysbusState *s = container_of(xhci, XHCISysbusState, xhci); + + qemu_set_irq(s->irq[n], level); + + return false; +} + +void xhci_sysbus_reset(DeviceState *dev) +{ + XHCISysbusState *s = XHCI_SYSBUS(dev); + + device_legacy_reset(DEVICE(&s->xhci)); +} + +static void xhci_sysbus_realize(DeviceState *dev, Error **errp) +{ + XHCISysbusState *s = XHCI_SYSBUS(dev); + + object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL); + if (!qdev_realize(DEVICE(&s->xhci), NULL, errp)) { + return; + } + s->irq = g_new0(qemu_irq, s->xhci.numintrs); + qdev_init_gpio_out_named(dev, s->irq, SYSBUS_DEVICE_GPIO_IRQ, + s->xhci.numintrs); + if (s->xhci.dma_mr) { + s->xhci.as = g_malloc0(sizeof(AddressSpace)); + address_space_init(s->xhci.as, s->xhci.dma_mr, NULL); + } else { + s->xhci.as = &address_space_memory; + } + + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->xhci.mem); +} + +static void xhci_sysbus_instance_init(Object *obj) +{ + XHCISysbusState *s = XHCI_SYSBUS(obj); + + object_initialize_child(obj, "xhci-core", &s->xhci, TYPE_XHCI); + qdev_alias_all_properties(DEVICE(&s->xhci), obj); + + object_property_add_link(obj, "dma", TYPE_MEMORY_REGION, + (Object **)&s->xhci.dma_mr, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); + s->xhci.intr_update = NULL; + s->xhci.intr_raise = xhci_sysbus_intr_raise; +} + +void xhci_sysbus_build_aml(Aml *scope, uint32_t mmio, unsigned int irq) +{ + Aml *dev = aml_device("XHCI"); + Aml *crs = aml_resource_template(); + + aml_append(crs, aml_memory32_fixed(mmio, XHCI_LEN_REGS, AML_READ_WRITE)); + aml_append(crs, aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH, + AML_EXCLUSIVE, &irq, 1)); + + aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0D10"))); + aml_append(dev, aml_name_decl("_CRS", crs)); + aml_append(scope, dev); +} + +static Property xhci_sysbus_props[] = { + DEFINE_PROP_UINT32("intrs", XHCISysbusState, xhci.numintrs, XHCI_MAXINTRS), + DEFINE_PROP_UINT32("slots", XHCISysbusState, xhci.numslots, XHCI_MAXSLOTS), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription vmstate_xhci_sysbus = { + .name = "xhci-sysbus", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(xhci, XHCISysbusState, 1, vmstate_xhci, XHCIState), + VMSTATE_END_OF_LIST() + } +}; + +static void xhci_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = xhci_sysbus_reset; + dc->realize = xhci_sysbus_realize; + dc->vmsd = &vmstate_xhci_sysbus; + device_class_set_props(dc, xhci_sysbus_props); +} + +static const TypeInfo xhci_sysbus_info = { + .name = TYPE_XHCI_SYSBUS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XHCISysbusState), + .class_init = xhci_sysbus_class_init, + .instance_init = xhci_sysbus_instance_init +}; + +static void xhci_sysbus_register_types(void) +{ + type_register_static(&xhci_sysbus_info); +} + +type_init(xhci_sysbus_register_types); diff --git a/hw/usb/hcd-xhci-sysbus.h b/hw/usb/hcd-xhci-sysbus.h new file mode 100644 index 000000000..fdfcbbee3 --- /dev/null +++ b/hw/usb/hcd-xhci-sysbus.h @@ -0,0 +1,31 @@ +/* + * USB xHCI controller for system-bus interface + * + * SPDX-FileCopyrightText: 2020 Xilinx + * SPDX-FileContributor: Author: Sai Pavan Boddu <sai.pavan.boddu@xilinx.com> + * SPDX-sourceInfo: Based on hcd-echi-sysbus + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_USB_HCD_XHCI_SYSBUS_H +#define HW_USB_HCD_XHCI_SYSBUS_H + +#include "hw/usb.h" +#include "hcd-xhci.h" +#include "hw/sysbus.h" + +#define XHCI_SYSBUS(obj) \ + OBJECT_CHECK(XHCISysbusState, (obj), TYPE_XHCI_SYSBUS) + + +typedef struct XHCISysbusState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + XHCIState xhci; + qemu_irq *irq; +} XHCISysbusState; + +void xhci_sysbus_reset(DeviceState *dev); +#endif diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c new file mode 100644 index 000000000..e01700039 --- /dev/null +++ b/hw/usb/hcd-xhci.c @@ -0,0 +1,3613 @@ +/* + * 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.1 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 "qemu/osdep.h" +#include "qemu/timer.h" +#include "qemu/module.h" +#include "qemu/queue.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "trace.h" +#include "qapi/error.h" + +#include "hcd-xhci.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(_msg) do { fprintf(stderr, "FIXME %s:%d %s\n", \ + __func__, __LINE__, _msg); abort(); } while (0) + +#define TRB_LINK_LIMIT 32 +#define COMMAND_LIMIT 256 +#define TRANSFER_LIMIT 256 + +#define LEN_CAP 0x40 +#define LEN_OPER (0x400 + 0x10 * XHCI_MAXPORTS) +#define LEN_RUNTIME ((XHCI_MAXINTRS + 1) * 0x20) +#define LEN_DOORBELL ((XHCI_MAXSLOTS + 1) * 0x20) + +#define OFF_OPER LEN_CAP +#define OFF_RUNTIME 0x1000 +#define OFF_DOORBELL 0x2000 + +#if (OFF_OPER + LEN_OPER) > OFF_RUNTIME +#error Increase OFF_RUNTIME +#endif +#if (OFF_RUNTIME + LEN_RUNTIME) > OFF_DOORBELL +#error Increase OFF_DOORBELL +#endif +#if (OFF_DOORBELL + LEN_DOORBELL) > XHCI_LEN_REGS +# error Increase XHCI_LEN_REGS +#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; + dma_addr_t addr; + bool ccs; +} XHCITRB; + +enum { + PLS_U0 = 0, + PLS_U1 = 1, + PLS_U2 = 2, + PLS_U3 = 3, + PLS_DISABLED = 4, + PLS_RX_DETECT = 5, + PLS_INACTIVE = 6, + PLS_POLLING = 7, + PLS_RECOVERY = 8, + PLS_HOT_RESET = 9, + PLS_COMPILANCE_MODE = 10, + PLS_TEST_MODE = 11, + PLS_RESUME = 15, +}; + +#define CR_LINK TR_LINK + +#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 TRB_INTR_SHIFT 22 +#define TRB_INTR_MASK 0x3ff +#define TRB_INTR(t) (((t).status >> TRB_INTR_SHIFT) & TRB_INTR_MASK) + +#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 + +#define get_field(data, field) \ + (((data) >> field##_SHIFT) & field##_MASK) + +#define set_field(data, newval, field) do { \ + uint32_t val = *data; \ + val &= ~(field##_MASK << field##_SHIFT); \ + val |= ((newval) & field##_MASK) << field##_SHIFT; \ + *data = val; \ + } while (0) + +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 XHCITransfer { + XHCIEPContext *epctx; + USBPacket packet; + QEMUSGList sgl; + bool running_async; + bool running_retry; + bool complete; + bool int_req; + unsigned int iso_pkts; + unsigned int streamid; + bool in_xfer; + bool iso_xfer; + bool timed_xfer; + + unsigned int trb_count; + XHCITRB *trbs; + + TRBCCode status; + + unsigned int pkts; + unsigned int pktsize; + unsigned int cur_pkt; + + uint64_t mfindex_kick; + + QTAILQ_ENTRY(XHCITransfer) next; +} XHCITransfer; + +struct XHCIStreamContext { + dma_addr_t pctx; + unsigned int sct; + XHCIRing ring; +}; + +struct XHCIEPContext { + XHCIState *xhci; + unsigned int slotid; + unsigned int epid; + + XHCIRing ring; + uint32_t xfer_count; + QTAILQ_HEAD(, XHCITransfer) transfers; + XHCITransfer *retry; + EPType type; + dma_addr_t pctx; + unsigned int max_psize; + uint32_t state; + uint32_t kick_active; + + /* streams */ + unsigned int max_pstreams; + bool lsa; + unsigned int nr_pstreams; + XHCIStreamContext *pstreams; + + /* iso xfer scheduling */ + unsigned int interval; + int64_t mfindex_last; + QEMUTimer *kick_timer; +}; + +typedef struct XHCIEvRingSeg { + uint32_t addr_low; + uint32_t addr_high; + uint32_t size; + uint32_t rsvd; +} XHCIEvRingSeg; + +static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid, unsigned int streamid); +static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid); +static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid); +static void xhci_xfer_report(XHCITransfer *xfer); +static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v); +static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v); +static USBEndpoint *xhci_epid_to_usbep(XHCIEPContext *epctx); + +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_NEC_FIRMWARE_REVISION] = "CR_VENDOR_NEC_FIRMWARE_REVISION", + [CR_VENDOR_NEC_CHALLENGE_RESPONSE] = "CR_VENDOR_NEC_CHALLENGE_RESPONSE", +}; + +static const char *TRBCCode_names[] = { + [CC_INVALID] = "CC_INVALID", + [CC_SUCCESS] = "CC_SUCCESS", + [CC_DATA_BUFFER_ERROR] = "CC_DATA_BUFFER_ERROR", + [CC_BABBLE_DETECTED] = "CC_BABBLE_DETECTED", + [CC_USB_TRANSACTION_ERROR] = "CC_USB_TRANSACTION_ERROR", + [CC_TRB_ERROR] = "CC_TRB_ERROR", + [CC_STALL_ERROR] = "CC_STALL_ERROR", + [CC_RESOURCE_ERROR] = "CC_RESOURCE_ERROR", + [CC_BANDWIDTH_ERROR] = "CC_BANDWIDTH_ERROR", + [CC_NO_SLOTS_ERROR] = "CC_NO_SLOTS_ERROR", + [CC_INVALID_STREAM_TYPE_ERROR] = "CC_INVALID_STREAM_TYPE_ERROR", + [CC_SLOT_NOT_ENABLED_ERROR] = "CC_SLOT_NOT_ENABLED_ERROR", + [CC_EP_NOT_ENABLED_ERROR] = "CC_EP_NOT_ENABLED_ERROR", + [CC_SHORT_PACKET] = "CC_SHORT_PACKET", + [CC_RING_UNDERRUN] = "CC_RING_UNDERRUN", + [CC_RING_OVERRUN] = "CC_RING_OVERRUN", + [CC_VF_ER_FULL] = "CC_VF_ER_FULL", + [CC_PARAMETER_ERROR] = "CC_PARAMETER_ERROR", + [CC_BANDWIDTH_OVERRUN] = "CC_BANDWIDTH_OVERRUN", + [CC_CONTEXT_STATE_ERROR] = "CC_CONTEXT_STATE_ERROR", + [CC_NO_PING_RESPONSE_ERROR] = "CC_NO_PING_RESPONSE_ERROR", + [CC_EVENT_RING_FULL_ERROR] = "CC_EVENT_RING_FULL_ERROR", + [CC_INCOMPATIBLE_DEVICE_ERROR] = "CC_INCOMPATIBLE_DEVICE_ERROR", + [CC_MISSED_SERVICE_ERROR] = "CC_MISSED_SERVICE_ERROR", + [CC_COMMAND_RING_STOPPED] = "CC_COMMAND_RING_STOPPED", + [CC_COMMAND_ABORTED] = "CC_COMMAND_ABORTED", + [CC_STOPPED] = "CC_STOPPED", + [CC_STOPPED_LENGTH_INVALID] = "CC_STOPPED_LENGTH_INVALID", + [CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR] + = "CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR", + [CC_ISOCH_BUFFER_OVERRUN] = "CC_ISOCH_BUFFER_OVERRUN", + [CC_EVENT_LOST_ERROR] = "CC_EVENT_LOST_ERROR", + [CC_UNDEFINED_ERROR] = "CC_UNDEFINED_ERROR", + [CC_INVALID_STREAM_ID_ERROR] = "CC_INVALID_STREAM_ID_ERROR", + [CC_SECONDARY_BANDWIDTH_ERROR] = "CC_SECONDARY_BANDWIDTH_ERROR", + [CC_SPLIT_TRANSACTION_ERROR] = "CC_SPLIT_TRANSACTION_ERROR", +}; + +static const char *ep_state_names[] = { + [EP_DISABLED] = "disabled", + [EP_RUNNING] = "running", + [EP_HALTED] = "halted", + [EP_STOPPED] = "stopped", + [EP_ERROR] = "error", +}; + +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)); +} + +static const char *event_name(XHCIEvent *event) +{ + return lookup_name(event->ccode, TRBCCode_names, + ARRAY_SIZE(TRBCCode_names)); +} + +static const char *ep_state_name(uint32_t state) +{ + return lookup_name(state, ep_state_names, + ARRAY_SIZE(ep_state_names)); +} + +bool xhci_get_flag(XHCIState *xhci, enum xhci_flags bit) +{ + return xhci->flags & (1 << bit); +} + +void xhci_set_flag(XHCIState *xhci, enum xhci_flags bit) +{ + xhci->flags |= (1 << bit); +} + +static uint64_t xhci_mfindex_get(XHCIState *xhci) +{ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + return (now - xhci->mfindex_start) / 125000; +} + +static void xhci_mfwrap_update(XHCIState *xhci) +{ + const uint32_t bits = USBCMD_RS | USBCMD_EWE; + uint32_t mfindex, left; + int64_t now; + + if ((xhci->usbcmd & bits) == bits) { + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + mfindex = ((now - xhci->mfindex_start) / 125000) & 0x3fff; + left = 0x4000 - mfindex; + timer_mod(xhci->mfwrap_timer, now + left * 125000); + } else { + timer_del(xhci->mfwrap_timer); + } +} + +static void xhci_mfwrap_timer(void *opaque) +{ + XHCIState *xhci = opaque; + XHCIEvent wrap = { ER_MFINDEX_WRAP, CC_SUCCESS }; + + xhci_event(xhci, &wrap, 0); + xhci_mfwrap_update(xhci); +} + +static inline dma_addr_t xhci_addr64(uint32_t low, uint32_t high) +{ + if (sizeof(dma_addr_t) == 4) { + return low; + } else { + return low | (((dma_addr_t)high << 16) << 16); + } +} + +static inline dma_addr_t xhci_mask64(uint64_t addr) +{ + if (sizeof(dma_addr_t) == 4) { + return addr & 0xffffffff; + } else { + return addr; + } +} + +static inline void xhci_dma_read_u32s(XHCIState *xhci, dma_addr_t addr, + uint32_t *buf, size_t len) +{ + int i; + + assert((len % sizeof(uint32_t)) == 0); + + dma_memory_read(xhci->as, addr, buf, len); + + for (i = 0; i < (len / sizeof(uint32_t)); i++) { + buf[i] = le32_to_cpu(buf[i]); + } +} + +static inline void xhci_dma_write_u32s(XHCIState *xhci, dma_addr_t addr, + uint32_t *buf, size_t len) +{ + int i; + uint32_t tmp[5]; + uint32_t n = len / sizeof(uint32_t); + + assert((len % sizeof(uint32_t)) == 0); + assert(n <= ARRAY_SIZE(tmp)); + + for (i = 0; i < n; i++) { + tmp[i] = cpu_to_le32(buf[i]); + } + dma_memory_write(xhci->as, addr, tmp, len); +} + +static XHCIPort *xhci_lookup_port(XHCIState *xhci, struct USBPort *uport) +{ + int index; + + if (!uport->dev) { + return NULL; + } + switch (uport->dev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + index = uport->index + xhci->numports_3; + } else { + index = uport->index; + } + break; + case USB_SPEED_SUPER: + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + index = uport->index; + } else { + index = uport->index + xhci->numports_2; + } + break; + default: + return NULL; + } + return &xhci->ports[index]; +} + +static void xhci_intr_update(XHCIState *xhci, int v) +{ + int level = 0; + + if (v == 0) { + if (xhci->intr[0].iman & IMAN_IP && + xhci->intr[0].iman & IMAN_IE && + xhci->usbcmd & USBCMD_INTE) { + level = 1; + } + if (xhci->intr_raise) { + if (xhci->intr_raise(xhci, 0, level)) { + xhci->intr[0].iman &= ~IMAN_IP; + } + } + } + if (xhci->intr_update) { + xhci->intr_update(xhci, v, + xhci->intr[v].iman & IMAN_IE); + } +} + +static void xhci_intr_raise(XHCIState *xhci, int v) +{ + bool pending = (xhci->intr[v].erdp_low & ERDP_EHB); + + xhci->intr[v].erdp_low |= ERDP_EHB; + xhci->intr[v].iman |= IMAN_IP; + xhci->usbsts |= USBSTS_EINT; + + if (pending) { + return; + } + if (!(xhci->intr[v].iman & IMAN_IE)) { + return; + } + + if (!(xhci->usbcmd & USBCMD_INTE)) { + return; + } + if (xhci->intr_raise) { + if (xhci->intr_raise(xhci, v, true)) { + xhci->intr[v].iman &= ~IMAN_IP; + } + } +} + +static inline int xhci_running(XHCIState *xhci) +{ + return !(xhci->usbsts & USBSTS_HCH); +} + +static void xhci_die(XHCIState *xhci) +{ + xhci->usbsts |= USBSTS_HCE; + DPRINTF("xhci: asserted controller error\n"); +} + +static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v) +{ + XHCIInterrupter *intr = &xhci->intr[v]; + XHCITRB ev_trb; + dma_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 (intr->er_pcs) { + ev_trb.control |= TRB_C; + } + ev_trb.control = cpu_to_le32(ev_trb.control); + + trace_usb_xhci_queue_event(v, intr->er_ep_idx, trb_name(&ev_trb), + event_name(event), ev_trb.parameter, + ev_trb.status, ev_trb.control); + + addr = intr->er_start + TRB_SIZE*intr->er_ep_idx; + dma_memory_write(xhci->as, addr, &ev_trb, TRB_SIZE); + + intr->er_ep_idx++; + if (intr->er_ep_idx >= intr->er_size) { + intr->er_ep_idx = 0; + intr->er_pcs = !intr->er_pcs; + } +} + +static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v) +{ + XHCIInterrupter *intr; + dma_addr_t erdp; + unsigned int dp_idx; + + if (v >= xhci->numintrs) { + DPRINTF("intr nr out of range (%d >= %d)\n", v, xhci->numintrs); + return; + } + intr = &xhci->intr[v]; + + erdp = xhci_addr64(intr->erdp_low, intr->erdp_high); + if (erdp < intr->er_start || + erdp >= (intr->er_start + TRB_SIZE*intr->er_size)) { + DPRINTF("xhci: ERDP out of bounds: "DMA_ADDR_FMT"\n", erdp); + DPRINTF("xhci: ER[%d] at "DMA_ADDR_FMT" len %d\n", + v, intr->er_start, intr->er_size); + xhci_die(xhci); + return; + } + + dp_idx = (erdp - intr->er_start) / TRB_SIZE; + assert(dp_idx < intr->er_size); + + if ((intr->er_ep_idx + 2) % intr->er_size == dp_idx) { + DPRINTF("xhci: ER %d full, send ring full error\n", v); + XHCIEvent full = {ER_HOST_CONTROLLER, CC_EVENT_RING_FULL_ERROR}; + xhci_write_event(xhci, &full, v); + } else if ((intr->er_ep_idx + 1) % intr->er_size == dp_idx) { + DPRINTF("xhci: ER %d full, drop event\n", v); + } else { + xhci_write_event(xhci, event, v); + } + + xhci_intr_raise(xhci, v); +} + +static void xhci_ring_init(XHCIState *xhci, XHCIRing *ring, + dma_addr_t base) +{ + ring->dequeue = base; + ring->ccs = 1; +} + +static TRBType xhci_ring_fetch(XHCIState *xhci, XHCIRing *ring, XHCITRB *trb, + dma_addr_t *addr) +{ + uint32_t link_cnt = 0; + + while (1) { + TRBType type; + dma_memory_read(xhci->as, ring->dequeue, 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); + + trace_usb_xhci_fetch_trb(ring->dequeue, trb_name(trb), + trb->parameter, trb->status, trb->control); + + 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 { + if (++link_cnt > TRB_LINK_LIMIT) { + trace_usb_xhci_enforced_limit("trb-link"); + return 0; + } + 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; + dma_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; + uint32_t link_cnt = 0; + + while (1) { + TRBType type; + dma_memory_read(xhci->as, dequeue, &trb, TRB_SIZE); + le64_to_cpus(&trb.parameter); + le32_to_cpus(&trb.status); + le32_to_cpus(&trb.control); + + if ((trb.control & TRB_C) != ccs) { + return -length; + } + + type = TRB_TYPE(trb); + + if (type == TR_LINK) { + if (++link_cnt > TRB_LINK_LIMIT) { + return -length; + } + 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, int v) +{ + XHCIInterrupter *intr = &xhci->intr[v]; + XHCIEvRingSeg seg; + dma_addr_t erstba = xhci_addr64(intr->erstba_low, intr->erstba_high); + + if (intr->erstsz == 0 || erstba == 0) { + /* disabled */ + intr->er_start = 0; + intr->er_size = 0; + return; + } + /* cache the (sole) event ring segment location */ + if (intr->erstsz != 1) { + DPRINTF("xhci: invalid value for ERSTSZ: %d\n", intr->erstsz); + xhci_die(xhci); + return; + } + dma_memory_read(xhci->as, erstba, &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) { + DPRINTF("xhci: invalid value for segment size: %d\n", seg.size); + xhci_die(xhci); + return; + } + intr->er_start = xhci_addr64(seg.addr_low, seg.addr_high); + intr->er_size = seg.size; + + intr->er_ep_idx = 0; + intr->er_pcs = 1; + + DPRINTF("xhci: event ring[%d]:" DMA_ADDR_FMT " [%d]\n", + v, intr->er_start, intr->er_size); +} + +static void xhci_run(XHCIState *xhci) +{ + trace_usb_xhci_run(); + xhci->usbsts &= ~USBSTS_HCH; + xhci->mfindex_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +static void xhci_stop(XHCIState *xhci) +{ + trace_usb_xhci_stop(); + xhci->usbsts |= USBSTS_HCH; + xhci->crcr_low &= ~CRCR_CRR; +} + +static XHCIStreamContext *xhci_alloc_stream_contexts(unsigned count, + dma_addr_t base) +{ + XHCIStreamContext *stctx; + unsigned int i; + + stctx = g_new0(XHCIStreamContext, count); + for (i = 0; i < count; i++) { + stctx[i].pctx = base + i * 16; + stctx[i].sct = -1; + } + return stctx; +} + +static void xhci_reset_streams(XHCIEPContext *epctx) +{ + unsigned int i; + + for (i = 0; i < epctx->nr_pstreams; i++) { + epctx->pstreams[i].sct = -1; + } +} + +static void xhci_alloc_streams(XHCIEPContext *epctx, dma_addr_t base) +{ + assert(epctx->pstreams == NULL); + epctx->nr_pstreams = 2 << epctx->max_pstreams; + epctx->pstreams = xhci_alloc_stream_contexts(epctx->nr_pstreams, base); +} + +static void xhci_free_streams(XHCIEPContext *epctx) +{ + assert(epctx->pstreams != NULL); + + g_free(epctx->pstreams); + epctx->pstreams = NULL; + epctx->nr_pstreams = 0; +} + +static int xhci_epmask_to_eps_with_streams(XHCIState *xhci, + unsigned int slotid, + uint32_t epmask, + XHCIEPContext **epctxs, + USBEndpoint **eps) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + USBEndpoint *ep; + int i, j; + + assert(slotid >= 1 && slotid <= xhci->numslots); + + slot = &xhci->slots[slotid - 1]; + + for (i = 2, j = 0; i <= 31; i++) { + if (!(epmask & (1u << i))) { + continue; + } + + epctx = slot->eps[i - 1]; + ep = xhci_epid_to_usbep(epctx); + if (!epctx || !epctx->nr_pstreams || !ep) { + continue; + } + + if (epctxs) { + epctxs[j] = epctx; + } + eps[j++] = ep; + } + return j; +} + +static void xhci_free_device_streams(XHCIState *xhci, unsigned int slotid, + uint32_t epmask) +{ + USBEndpoint *eps[30]; + int nr_eps; + + nr_eps = xhci_epmask_to_eps_with_streams(xhci, slotid, epmask, NULL, eps); + if (nr_eps) { + usb_device_free_streams(eps[0]->dev, eps, nr_eps); + } +} + +static TRBCCode xhci_alloc_device_streams(XHCIState *xhci, unsigned int slotid, + uint32_t epmask) +{ + XHCIEPContext *epctxs[30]; + USBEndpoint *eps[30]; + int i, r, nr_eps, req_nr_streams, dev_max_streams; + + nr_eps = xhci_epmask_to_eps_with_streams(xhci, slotid, epmask, epctxs, + eps); + if (nr_eps == 0) { + return CC_SUCCESS; + } + + req_nr_streams = epctxs[0]->nr_pstreams; + dev_max_streams = eps[0]->max_streams; + + for (i = 1; i < nr_eps; i++) { + /* + * HdG: I don't expect these to ever trigger, but if they do we need + * to come up with another solution, ie group identical endpoints + * together and make an usb_device_alloc_streams call per group. + */ + if (epctxs[i]->nr_pstreams != req_nr_streams) { + FIXME("guest streams config not identical for all eps"); + return CC_RESOURCE_ERROR; + } + if (eps[i]->max_streams != dev_max_streams) { + FIXME("device streams config not identical for all eps"); + return CC_RESOURCE_ERROR; + } + } + + /* + * max-streams in both the device descriptor and in the controller is a + * power of 2. But stream id 0 is reserved, so if a device can do up to 4 + * streams the guest will ask for 5 rounded up to the next power of 2 which + * becomes 8. For emulated devices usb_device_alloc_streams is a nop. + * + * For redirected devices however this is an issue, as there we must ask + * the real xhci controller to alloc streams, and the host driver for the + * real xhci controller will likely disallow allocating more streams then + * the device can handle. + * + * So we limit the requested nr_streams to the maximum number the device + * can handle. + */ + if (req_nr_streams > dev_max_streams) { + req_nr_streams = dev_max_streams; + } + + r = usb_device_alloc_streams(eps[0]->dev, eps, nr_eps, req_nr_streams); + if (r != 0) { + DPRINTF("xhci: alloc streams failed\n"); + return CC_RESOURCE_ERROR; + } + + return CC_SUCCESS; +} + +static XHCIStreamContext *xhci_find_stream(XHCIEPContext *epctx, + unsigned int streamid, + uint32_t *cc_error) +{ + XHCIStreamContext *sctx; + dma_addr_t base; + uint32_t ctx[2], sct; + + assert(streamid != 0); + if (epctx->lsa) { + if (streamid >= epctx->nr_pstreams) { + *cc_error = CC_INVALID_STREAM_ID_ERROR; + return NULL; + } + sctx = epctx->pstreams + streamid; + } else { + FIXME("secondary streams not implemented yet"); + } + + if (sctx->sct == -1) { + xhci_dma_read_u32s(epctx->xhci, sctx->pctx, ctx, sizeof(ctx)); + sct = (ctx[0] >> 1) & 0x07; + if (epctx->lsa && sct != 1) { + *cc_error = CC_INVALID_STREAM_TYPE_ERROR; + return NULL; + } + sctx->sct = sct; + base = xhci_addr64(ctx[0] & ~0xf, ctx[1]); + xhci_ring_init(epctx->xhci, &sctx->ring, base); + } + return sctx; +} + +static void xhci_set_ep_state(XHCIState *xhci, XHCIEPContext *epctx, + XHCIStreamContext *sctx, uint32_t state) +{ + XHCIRing *ring = NULL; + uint32_t ctx[5]; + uint32_t ctx2[2]; + + xhci_dma_read_u32s(xhci, epctx->pctx, ctx, sizeof(ctx)); + ctx[0] &= ~EP_STATE_MASK; + ctx[0] |= state; + + /* update ring dequeue ptr */ + if (epctx->nr_pstreams) { + if (sctx != NULL) { + ring = &sctx->ring; + xhci_dma_read_u32s(xhci, sctx->pctx, ctx2, sizeof(ctx2)); + ctx2[0] &= 0xe; + ctx2[0] |= sctx->ring.dequeue | sctx->ring.ccs; + ctx2[1] = (sctx->ring.dequeue >> 16) >> 16; + xhci_dma_write_u32s(xhci, sctx->pctx, ctx2, sizeof(ctx2)); + } + } else { + ring = &epctx->ring; + } + if (ring) { + ctx[2] = ring->dequeue | ring->ccs; + ctx[3] = (ring->dequeue >> 16) >> 16; + + DPRINTF("xhci: set epctx: " DMA_ADDR_FMT " state=%d dequeue=%08x%08x\n", + epctx->pctx, state, ctx[3], ctx[2]); + } + + xhci_dma_write_u32s(xhci, epctx->pctx, ctx, sizeof(ctx)); + if (epctx->state != state) { + trace_usb_xhci_ep_state(epctx->slotid, epctx->epid, + ep_state_name(epctx->state), + ep_state_name(state)); + } + epctx->state = state; +} + +static void xhci_ep_kick_timer(void *opaque) +{ + XHCIEPContext *epctx = opaque; + xhci_kick_epctx(epctx, 0); +} + +static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci, + unsigned int slotid, + unsigned int epid) +{ + XHCIEPContext *epctx; + + epctx = g_new0(XHCIEPContext, 1); + epctx->xhci = xhci; + epctx->slotid = slotid; + epctx->epid = epid; + + QTAILQ_INIT(&epctx->transfers); + epctx->kick_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_ep_kick_timer, epctx); + + return epctx; +} + +static void xhci_init_epctx(XHCIEPContext *epctx, + dma_addr_t pctx, uint32_t *ctx) +{ + dma_addr_t dequeue; + + dequeue = xhci_addr64(ctx[2] & ~0xf, ctx[3]); + + epctx->type = (ctx[1] >> EP_TYPE_SHIFT) & EP_TYPE_MASK; + epctx->pctx = pctx; + epctx->max_psize = ctx[1]>>16; + epctx->max_psize *= 1+((ctx[1]>>8)&0xff); + epctx->max_pstreams = (ctx[0] >> 10) & epctx->xhci->max_pstreams_mask; + epctx->lsa = (ctx[0] >> 15) & 1; + if (epctx->max_pstreams) { + xhci_alloc_streams(epctx, dequeue); + } else { + xhci_ring_init(epctx->xhci, &epctx->ring, dequeue); + epctx->ring.ccs = ctx[2] & 1; + } + + epctx->interval = 1 << ((ctx[0] >> 16) & 0xff); +} + +static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid, dma_addr_t pctx, + uint32_t *ctx) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + + trace_usb_xhci_ep_enable(slotid, epid); + assert(slotid >= 1 && slotid <= xhci->numslots); + assert(epid >= 1 && epid <= 31); + + slot = &xhci->slots[slotid-1]; + if (slot->eps[epid-1]) { + xhci_disable_ep(xhci, slotid, epid); + } + + epctx = xhci_alloc_epctx(xhci, slotid, epid); + slot->eps[epid-1] = epctx; + xhci_init_epctx(epctx, pctx, ctx); + + DPRINTF("xhci: endpoint %d.%d type is %d, max transaction (burst) " + "size is %d\n", epid/2, epid%2, epctx->type, epctx->max_psize); + + epctx->mfindex_last = 0; + + epctx->state = EP_RUNNING; + ctx[0] &= ~EP_STATE_MASK; + ctx[0] |= EP_RUNNING; + + return CC_SUCCESS; +} + +static XHCITransfer *xhci_ep_alloc_xfer(XHCIEPContext *epctx, + uint32_t length) +{ + uint32_t limit = epctx->nr_pstreams + 16; + XHCITransfer *xfer; + + if (epctx->xfer_count >= limit) { + return NULL; + } + + xfer = g_new0(XHCITransfer, 1); + xfer->epctx = epctx; + xfer->trbs = g_new(XHCITRB, length); + xfer->trb_count = length; + usb_packet_init(&xfer->packet); + + QTAILQ_INSERT_TAIL(&epctx->transfers, xfer, next); + epctx->xfer_count++; + + return xfer; +} + +static void xhci_ep_free_xfer(XHCITransfer *xfer) +{ + QTAILQ_REMOVE(&xfer->epctx->transfers, xfer, next); + xfer->epctx->xfer_count--; + + usb_packet_cleanup(&xfer->packet); + g_free(xfer->trbs); + g_free(xfer); +} + +static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report) +{ + int killed = 0; + + if (report && (t->running_async || t->running_retry)) { + t->status = report; + xhci_xfer_report(t); + } + + if (t->running_async) { + usb_cancel_packet(&t->packet); + t->running_async = 0; + killed = 1; + } + if (t->running_retry) { + if (t->epctx) { + t->epctx->retry = NULL; + timer_del(t->epctx->kick_timer); + } + t->running_retry = 0; + killed = 1; + } + g_free(t->trbs); + + t->trbs = NULL; + t->trb_count = 0; + + return killed; +} + +static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid, + unsigned int epid, TRBCCode report) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + XHCITransfer *xfer; + int killed = 0; + USBEndpoint *ep = NULL; + assert(slotid >= 1 && slotid <= xhci->numslots); + 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]; + + for (;;) { + xfer = QTAILQ_FIRST(&epctx->transfers); + if (xfer == NULL) { + break; + } + killed += xhci_ep_nuke_one_xfer(xfer, report); + if (killed) { + report = 0; /* Only report once */ + } + xhci_ep_free_xfer(xfer); + } + + ep = xhci_epid_to_usbep(epctx); + if (ep) { + usb_device_ep_stopped(ep->dev, ep); + } + return killed; +} + +static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + + trace_usb_xhci_ep_disable(slotid, epid); + assert(slotid >= 1 && slotid <= xhci->numslots); + assert(epid >= 1 && epid <= 31); + + 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, 0); + + epctx = slot->eps[epid-1]; + + if (epctx->nr_pstreams) { + xhci_free_streams(epctx); + } + + /* only touch guest RAM if we're not resetting the HC */ + if (xhci->dcbaap_low || xhci->dcbaap_high) { + xhci_set_ep_state(xhci, epctx, NULL, EP_DISABLED); + } + + timer_free(epctx->kick_timer); + 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; + + trace_usb_xhci_ep_stop(slotid, epid); + assert(slotid >= 1 && slotid <= xhci->numslots); + + if (epid < 1 || epid > 31) { + DPRINTF("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, CC_STOPPED) > 0) { + DPRINTF("xhci: FIXME: endpoint stopped w/ xfers running, " + "data might be lost\n"); + } + + epctx = slot->eps[epid-1]; + + xhci_set_ep_state(xhci, epctx, NULL, EP_STOPPED); + + if (epctx->nr_pstreams) { + xhci_reset_streams(epctx); + } + + return CC_SUCCESS; +} + +static TRBCCode xhci_reset_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + + trace_usb_xhci_ep_reset(slotid, epid); + assert(slotid >= 1 && slotid <= xhci->numslots); + + if (epid < 1 || epid > 31) { + DPRINTF("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) { + DPRINTF("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) > 0) { + DPRINTF("xhci: FIXME: endpoint reset w/ xfers running, " + "data might be lost\n"); + } + + if (!xhci->slots[slotid-1].uport || + !xhci->slots[slotid-1].uport->dev || + !xhci->slots[slotid-1].uport->dev->attached) { + return CC_USB_TRANSACTION_ERROR; + } + + xhci_set_ep_state(xhci, epctx, NULL, EP_STOPPED); + + if (epctx->nr_pstreams) { + xhci_reset_streams(epctx); + } + + return CC_SUCCESS; +} + +static TRBCCode xhci_set_ep_dequeue(XHCIState *xhci, unsigned int slotid, + unsigned int epid, unsigned int streamid, + uint64_t pdequeue) +{ + XHCISlot *slot; + XHCIEPContext *epctx; + XHCIStreamContext *sctx; + dma_addr_t dequeue; + + assert(slotid >= 1 && slotid <= xhci->numslots); + + if (epid < 1 || epid > 31) { + DPRINTF("xhci: bad ep %d\n", epid); + return CC_TRB_ERROR; + } + + trace_usb_xhci_ep_set_dequeue(slotid, epid, streamid, 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) { + DPRINTF("xhci: set EP dequeue pointer while EP %d not stopped\n", epid); + return CC_CONTEXT_STATE_ERROR; + } + + if (epctx->nr_pstreams) { + uint32_t err; + sctx = xhci_find_stream(epctx, streamid, &err); + if (sctx == NULL) { + return err; + } + xhci_ring_init(xhci, &sctx->ring, dequeue & ~0xf); + sctx->ring.ccs = dequeue & 1; + } else { + sctx = NULL; + xhci_ring_init(xhci, &epctx->ring, dequeue & ~0xF); + epctx->ring.ccs = dequeue & 1; + } + + xhci_set_ep_state(xhci, epctx, sctx, EP_STOPPED); + + return CC_SUCCESS; +} + +static int xhci_xfer_create_sgl(XHCITransfer *xfer, int in_xfer) +{ + XHCIState *xhci = xfer->epctx->xhci; + int i; + + xfer->int_req = false; + qemu_sglist_init(&xfer->sgl, DEVICE(xhci), xfer->trb_count, xhci->as); + for (i = 0; i < xfer->trb_count; i++) { + XHCITRB *trb = &xfer->trbs[i]; + dma_addr_t addr; + unsigned int chunk = 0; + + if (trb->control & TRB_TR_IOC) { + xfer->int_req = true; + } + + switch (TRB_TYPE(*trb)) { + case TR_DATA: + if ((!(trb->control & TRB_TR_DIR)) != (!in_xfer)) { + DPRINTF("xhci: data direction mismatch for TR_DATA\n"); + goto err; + } + /* fallthrough */ + case TR_NORMAL: + case TR_ISOCH: + addr = xhci_mask64(trb->parameter); + chunk = trb->status & 0x1ffff; + if (trb->control & TRB_TR_IDT) { + if (chunk > 8 || in_xfer) { + DPRINTF("xhci: invalid immediate data TRB\n"); + goto err; + } + qemu_sglist_add(&xfer->sgl, trb->addr, chunk); + } else { + qemu_sglist_add(&xfer->sgl, addr, chunk); + } + break; + } + } + + return 0; + +err: + qemu_sglist_destroy(&xfer->sgl); + xhci_die(xhci); + return -1; +} + +static void xhci_xfer_unmap(XHCITransfer *xfer) +{ + usb_packet_unmap(&xfer->packet, &xfer->sgl); + qemu_sglist_destroy(&xfer->sgl); +} + +static void xhci_xfer_report(XHCITransfer *xfer) +{ + uint32_t edtla = 0; + unsigned int left; + bool reported = 0; + bool shortpkt = 0; + XHCIEvent event = {ER_TRANSFER, CC_SUCCESS}; + XHCIState *xhci = xfer->epctx->xhci; + int i; + + left = xfer->packet.actual_length; + + for (i = 0; i < xfer->trb_count; i++) { + XHCITRB *trb = &xfer->trbs[i]; + unsigned int chunk = 0; + + switch (TRB_TYPE(*trb)) { + case TR_SETUP: + chunk = trb->status & 0x1ffff; + if (chunk > 8) { + chunk = 8; + } + break; + case TR_DATA: + case TR_NORMAL: + case TR_ISOCH: + chunk = trb->status & 0x1ffff; + if (chunk > left) { + chunk = left; + if (xfer->status == CC_SUCCESS) { + shortpkt = 1; + } + } + left -= chunk; + edtla += chunk; + break; + case TR_STATUS: + reported = 0; + shortpkt = 0; + break; + } + + if (!reported && ((trb->control & TRB_TR_IOC) || + (shortpkt && (trb->control & TRB_TR_ISP)) || + (xfer->status != CC_SUCCESS && left == 0))) { + event.slotid = xfer->epctx->slotid; + event.epid = xfer->epctx->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, TRB_INTR(*trb)); + reported = 1; + if (xfer->status != CC_SUCCESS) { + return; + } + } + + switch (TRB_TYPE(*trb)) { + case TR_SETUP: + reported = 0; + shortpkt = 0; + break; + } + + } +} + +static void xhci_stall_ep(XHCITransfer *xfer) +{ + XHCIEPContext *epctx = xfer->epctx; + XHCIState *xhci = epctx->xhci; + uint32_t err; + XHCIStreamContext *sctx; + + if (epctx->type == ET_ISO_IN || epctx->type == ET_ISO_OUT) { + /* never halt isoch endpoints, 4.10.2 */ + return; + } + + if (epctx->nr_pstreams) { + sctx = xhci_find_stream(epctx, xfer->streamid, &err); + if (sctx == NULL) { + return; + } + sctx->ring.dequeue = xfer->trbs[0].addr; + sctx->ring.ccs = xfer->trbs[0].ccs; + xhci_set_ep_state(xhci, epctx, sctx, EP_HALTED); + } else { + epctx->ring.dequeue = xfer->trbs[0].addr; + epctx->ring.ccs = xfer->trbs[0].ccs; + xhci_set_ep_state(xhci, epctx, NULL, EP_HALTED); + } +} + +static int xhci_setup_packet(XHCITransfer *xfer) +{ + USBEndpoint *ep; + int dir; + + dir = xfer->in_xfer ? USB_TOKEN_IN : USB_TOKEN_OUT; + + if (xfer->packet.ep) { + ep = xfer->packet.ep; + } else { + ep = xhci_epid_to_usbep(xfer->epctx); + if (!ep) { + DPRINTF("xhci: slot %d has no device\n", + xfer->epctx->slotid); + return -1; + } + } + + xhci_xfer_create_sgl(xfer, dir == USB_TOKEN_IN); /* Also sets int_req */ + usb_packet_setup(&xfer->packet, dir, ep, xfer->streamid, + xfer->trbs[0].addr, false, xfer->int_req); + if (usb_packet_map(&xfer->packet, &xfer->sgl)) { + qemu_sglist_destroy(&xfer->sgl); + return -1; + } + DPRINTF("xhci: setup packet pid 0x%x addr %d ep %d\n", + xfer->packet.pid, ep->dev->addr, ep->nr); + return 0; +} + +static int xhci_try_complete_packet(XHCITransfer *xfer) +{ + if (xfer->packet.status == USB_RET_ASYNC) { + trace_usb_xhci_xfer_async(xfer); + xfer->running_async = 1; + xfer->running_retry = 0; + xfer->complete = 0; + return 0; + } else if (xfer->packet.status == USB_RET_NAK) { + trace_usb_xhci_xfer_nak(xfer); + xfer->running_async = 0; + xfer->running_retry = 1; + xfer->complete = 0; + return 0; + } else { + xfer->running_async = 0; + xfer->running_retry = 0; + xfer->complete = 1; + xhci_xfer_unmap(xfer); + } + + if (xfer->packet.status == USB_RET_SUCCESS) { + trace_usb_xhci_xfer_success(xfer, xfer->packet.actual_length); + xfer->status = CC_SUCCESS; + xhci_xfer_report(xfer); + return 0; + } + + /* error */ + trace_usb_xhci_xfer_error(xfer, xfer->packet.status); + switch (xfer->packet.status) { + case USB_RET_NODEV: + case USB_RET_IOERROR: + xfer->status = CC_USB_TRANSACTION_ERROR; + xhci_xfer_report(xfer); + xhci_stall_ep(xfer); + break; + case USB_RET_STALL: + xfer->status = CC_STALL_ERROR; + xhci_xfer_report(xfer); + xhci_stall_ep(xfer); + break; + case USB_RET_BABBLE: + xfer->status = CC_BABBLE_DETECTED; + xhci_xfer_report(xfer); + xhci_stall_ep(xfer); + break; + default: + DPRINTF("%s: FIXME: status = %d\n", __func__, + xfer->packet.status); + FIXME("unhandled USB_RET_*"); + } + return 0; +} + +static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer) +{ + XHCITRB *trb_setup, *trb_status; + uint8_t bmRequestType; + + trb_setup = &xfer->trbs[0]; + trb_status = &xfer->trbs[xfer->trb_count-1]; + + trace_usb_xhci_xfer_start(xfer, xfer->epctx->slotid, + xfer->epctx->epid, xfer->streamid); + + /* 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) { + DPRINTF("xhci: ep0 first TD not SETUP: %d\n", + TRB_TYPE(*trb_setup)); + return -1; + } + if (TRB_TYPE(*trb_status) != TR_STATUS) { + DPRINTF("xhci: ep0 last TD not STATUS: %d\n", + TRB_TYPE(*trb_status)); + return -1; + } + if (!(trb_setup->control & TRB_TR_IDT)) { + DPRINTF("xhci: Setup TRB doesn't have IDT set\n"); + return -1; + } + if ((trb_setup->status & 0x1ffff) != 8) { + DPRINTF("xhci: Setup TRB has bad length (%d)\n", + (trb_setup->status & 0x1ffff)); + return -1; + } + + bmRequestType = trb_setup->parameter; + + xfer->in_xfer = bmRequestType & USB_DIR_IN; + xfer->iso_xfer = false; + xfer->timed_xfer = false; + + if (xhci_setup_packet(xfer) < 0) { + return -1; + } + xfer->packet.parameter = trb_setup->parameter; + + usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); + xhci_try_complete_packet(xfer); + return 0; +} + +static void xhci_calc_intr_kick(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx, uint64_t mfindex) +{ + uint64_t asap = ((mfindex + epctx->interval - 1) & + ~(epctx->interval-1)); + uint64_t kick = epctx->mfindex_last + epctx->interval; + + assert(epctx->interval != 0); + xfer->mfindex_kick = MAX(asap, kick); +} + +static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx, uint64_t mfindex) +{ + if (xfer->trbs[0].control & TRB_TR_SIA) { + uint64_t asap = ((mfindex + epctx->interval - 1) & + ~(epctx->interval-1)); + if (asap >= epctx->mfindex_last && + asap <= epctx->mfindex_last + epctx->interval * 4) { + xfer->mfindex_kick = epctx->mfindex_last + epctx->interval; + } else { + xfer->mfindex_kick = asap; + } + } else { + xfer->mfindex_kick = ((xfer->trbs[0].control >> TRB_TR_FRAMEID_SHIFT) + & TRB_TR_FRAMEID_MASK) << 3; + xfer->mfindex_kick |= mfindex & ~0x3fff; + if (xfer->mfindex_kick + 0x100 < mfindex) { + xfer->mfindex_kick += 0x4000; + } + } +} + +static void xhci_check_intr_iso_kick(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx, uint64_t mfindex) +{ + if (xfer->mfindex_kick > mfindex) { + timer_mod(epctx->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + (xfer->mfindex_kick - mfindex) * 125000); + xfer->running_retry = 1; + } else { + epctx->mfindex_last = xfer->mfindex_kick; + timer_del(epctx->kick_timer); + xfer->running_retry = 0; + } +} + + +static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx) +{ + uint64_t mfindex; + + DPRINTF("xhci_submit(slotid=%d,epid=%d)\n", epctx->slotid, epctx->epid); + + xfer->in_xfer = epctx->type>>2; + + switch(epctx->type) { + case ET_INTR_OUT: + case ET_INTR_IN: + xfer->pkts = 0; + xfer->iso_xfer = false; + xfer->timed_xfer = true; + mfindex = xhci_mfindex_get(xhci); + xhci_calc_intr_kick(xhci, xfer, epctx, mfindex); + xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex); + if (xfer->running_retry) { + return -1; + } + break; + case ET_BULK_OUT: + case ET_BULK_IN: + xfer->pkts = 0; + xfer->iso_xfer = false; + xfer->timed_xfer = false; + break; + case ET_ISO_OUT: + case ET_ISO_IN: + xfer->pkts = 1; + xfer->iso_xfer = true; + xfer->timed_xfer = true; + mfindex = xhci_mfindex_get(xhci); + xhci_calc_iso_kick(xhci, xfer, epctx, mfindex); + xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex); + if (xfer->running_retry) { + return -1; + } + break; + default: + trace_usb_xhci_unimplemented("endpoint type", epctx->type); + return -1; + } + + if (xhci_setup_packet(xfer) < 0) { + return -1; + } + usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); + xhci_try_complete_packet(xfer); + return 0; +} + +static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx) +{ + trace_usb_xhci_xfer_start(xfer, xfer->epctx->slotid, + xfer->epctx->epid, xfer->streamid); + return xhci_submit(xhci, xfer, epctx); +} + +static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid, unsigned int streamid) +{ + XHCIEPContext *epctx; + + assert(slotid >= 1 && slotid <= xhci->numslots); + assert(epid >= 1 && epid <= 31); + + if (!xhci->slots[slotid-1].enabled) { + DPRINTF("xhci: xhci_kick_ep for disabled slot %d\n", slotid); + return; + } + epctx = xhci->slots[slotid-1].eps[epid-1]; + if (!epctx) { + DPRINTF("xhci: xhci_kick_ep for disabled endpoint %d,%d\n", + epid, slotid); + return; + } + + if (epctx->kick_active) { + return; + } + xhci_kick_epctx(epctx, streamid); +} + +static bool xhci_slot_ok(XHCIState *xhci, int slotid) +{ + return (xhci->slots[slotid - 1].uport && + xhci->slots[slotid - 1].uport->dev && + xhci->slots[slotid - 1].uport->dev->attached); +} + +static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid) +{ + XHCIState *xhci = epctx->xhci; + XHCIStreamContext *stctx = NULL; + XHCITransfer *xfer; + XHCIRing *ring; + USBEndpoint *ep = NULL; + uint64_t mfindex; + unsigned int count = 0; + int length; + int i; + + trace_usb_xhci_ep_kick(epctx->slotid, epctx->epid, streamid); + assert(!epctx->kick_active); + + /* If the device has been detached, but the guest has not noticed this + yet the 2 above checks will succeed, but we must NOT continue */ + if (!xhci_slot_ok(xhci, epctx->slotid)) { + return; + } + + if (epctx->retry) { + XHCITransfer *xfer = epctx->retry; + + trace_usb_xhci_xfer_retry(xfer); + assert(xfer->running_retry); + if (xfer->timed_xfer) { + /* time to kick the transfer? */ + mfindex = xhci_mfindex_get(xhci); + xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex); + if (xfer->running_retry) { + return; + } + xfer->timed_xfer = 0; + xfer->running_retry = 1; + } + if (xfer->iso_xfer) { + /* retry iso transfer */ + if (xhci_setup_packet(xfer) < 0) { + return; + } + usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); + assert(xfer->packet.status != USB_RET_NAK); + xhci_try_complete_packet(xfer); + } else { + /* retry nak'ed transfer */ + if (xhci_setup_packet(xfer) < 0) { + return; + } + usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); + if (xfer->packet.status == USB_RET_NAK) { + xhci_xfer_unmap(xfer); + return; + } + xhci_try_complete_packet(xfer); + } + assert(!xfer->running_retry); + if (xfer->complete) { + /* update ring dequeue ptr */ + xhci_set_ep_state(xhci, epctx, stctx, epctx->state); + xhci_ep_free_xfer(epctx->retry); + } + epctx->retry = NULL; + } + + if (epctx->state == EP_HALTED) { + DPRINTF("xhci: ep halted, not running schedule\n"); + return; + } + + + if (epctx->nr_pstreams) { + uint32_t err; + stctx = xhci_find_stream(epctx, streamid, &err); + if (stctx == NULL) { + return; + } + ring = &stctx->ring; + xhci_set_ep_state(xhci, epctx, stctx, EP_RUNNING); + } else { + ring = &epctx->ring; + streamid = 0; + xhci_set_ep_state(xhci, epctx, NULL, EP_RUNNING); + } + if (!ring->dequeue) { + return; + } + + epctx->kick_active++; + while (1) { + length = xhci_ring_chain_length(xhci, ring); + if (length <= 0) { + if (epctx->type == ET_ISO_OUT || epctx->type == ET_ISO_IN) { + /* 4.10.3.1 */ + XHCIEvent ev = { ER_TRANSFER }; + ev.ccode = epctx->type == ET_ISO_IN ? + CC_RING_OVERRUN : CC_RING_UNDERRUN; + ev.slotid = epctx->slotid; + ev.epid = epctx->epid; + ev.ptr = epctx->ring.dequeue; + xhci_event(xhci, &ev, xhci->slots[epctx->slotid-1].intr); + } + break; + } + xfer = xhci_ep_alloc_xfer(epctx, length); + if (xfer == NULL) { + break; + } + + for (i = 0; i < length; i++) { + TRBType type; + type = xhci_ring_fetch(xhci, ring, &xfer->trbs[i], NULL); + if (!type) { + xhci_die(xhci); + xhci_ep_free_xfer(xfer); + epctx->kick_active--; + return; + } + } + xfer->streamid = streamid; + + if (epctx->epid == 1) { + xhci_fire_ctl_transfer(xhci, xfer); + } else { + xhci_fire_transfer(xhci, xfer, epctx); + } + if (!xhci_slot_ok(xhci, epctx->slotid)) { + /* surprise removal -> stop processing */ + break; + } + if (xfer->complete) { + /* update ring dequeue ptr */ + xhci_set_ep_state(xhci, epctx, stctx, epctx->state); + xhci_ep_free_xfer(xfer); + xfer = NULL; + } + + if (epctx->state == EP_HALTED) { + break; + } + if (xfer != NULL && xfer->running_retry) { + DPRINTF("xhci: xfer nacked, stopping schedule\n"); + epctx->retry = xfer; + xhci_xfer_unmap(xfer); + break; + } + if (count++ > TRANSFER_LIMIT) { + trace_usb_xhci_enforced_limit("transfers"); + break; + } + } + epctx->kick_active--; + + ep = xhci_epid_to_usbep(epctx); + if (ep) { + usb_device_flush_ep_queue(ep->dev, ep); + } +} + +static TRBCCode xhci_enable_slot(XHCIState *xhci, unsigned int slotid) +{ + trace_usb_xhci_slot_enable(slotid); + assert(slotid >= 1 && slotid <= xhci->numslots); + xhci->slots[slotid-1].enabled = 1; + xhci->slots[slotid-1].uport = NULL; + 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; + + trace_usb_xhci_slot_disable(slotid); + assert(slotid >= 1 && slotid <= xhci->numslots); + + 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; + xhci->slots[slotid-1].addressed = 0; + xhci->slots[slotid-1].uport = NULL; + xhci->slots[slotid-1].intr = 0; + return CC_SUCCESS; +} + +static USBPort *xhci_lookup_uport(XHCIState *xhci, uint32_t *slot_ctx) +{ + USBPort *uport; + char path[32]; + int i, pos, port; + + port = (slot_ctx[1]>>16) & 0xFF; + if (port < 1 || port > xhci->numports) { + return NULL; + } + port = xhci->ports[port-1].uport->index+1; + pos = snprintf(path, sizeof(path), "%d", port); + for (i = 0; i < 5; i++) { + port = (slot_ctx[0] >> 4*i) & 0x0f; + if (!port) { + break; + } + pos += snprintf(path + pos, sizeof(path) - pos, ".%d", port); + } + + QTAILQ_FOREACH(uport, &xhci->bus.used, next) { + if (strcmp(uport->path, path) == 0) { + return uport; + } + } + return NULL; +} + +static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid, + uint64_t pictx, bool bsr) +{ + XHCISlot *slot; + USBPort *uport; + USBDevice *dev; + dma_addr_t ictx, octx, dcbaap; + uint64_t poctx; + uint32_t ictl_ctx[2]; + uint32_t slot_ctx[4]; + uint32_t ep0_ctx[5]; + int i; + TRBCCode res; + + assert(slotid >= 1 && slotid <= xhci->numslots); + + dcbaap = xhci_addr64(xhci->dcbaap_low, xhci->dcbaap_high); + poctx = ldq_le_dma(xhci->as, dcbaap + 8 * slotid); + ictx = xhci_mask64(pictx); + octx = xhci_mask64(poctx); + + DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx); + DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx); + + xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx)); + + if (ictl_ctx[0] != 0x0 || ictl_ctx[1] != 0x3) { + DPRINTF("xhci: invalid input context control %08x %08x\n", + ictl_ctx[0], ictl_ctx[1]); + return CC_TRB_ERROR; + } + + xhci_dma_read_u32s(xhci, ictx+32, slot_ctx, sizeof(slot_ctx)); + xhci_dma_read_u32s(xhci, ictx+64, 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]); + + uport = xhci_lookup_uport(xhci, slot_ctx); + if (uport == NULL) { + DPRINTF("xhci: port not found\n"); + return CC_TRB_ERROR; + } + trace_usb_xhci_slot_address(slotid, uport->path); + + dev = uport->dev; + if (!dev || !dev->attached) { + DPRINTF("xhci: port %s not connected\n", uport->path); + return CC_USB_TRANSACTION_ERROR; + } + + for (i = 0; i < xhci->numslots; i++) { + if (i == slotid-1) { + continue; + } + if (xhci->slots[i].uport == uport) { + DPRINTF("xhci: port %s already assigned to slot %d\n", + uport->path, i+1); + return CC_TRB_ERROR; + } + } + + slot = &xhci->slots[slotid-1]; + slot->uport = uport; + slot->ctx = octx; + slot->intr = get_field(slot_ctx[2], TRB_INTR); + + /* Make sure device is in USB_STATE_DEFAULT state */ + usb_device_reset(dev); + if (bsr) { + slot_ctx[3] = SLOT_DEFAULT << SLOT_STATE_SHIFT; + } else { + USBPacket p; + uint8_t buf[1]; + + slot_ctx[3] = (SLOT_ADDRESSED << SLOT_STATE_SHIFT) | slotid; + memset(&p, 0, sizeof(p)); + usb_packet_addbuf(&p, buf, sizeof(buf)); + usb_packet_setup(&p, USB_TOKEN_OUT, + usb_ep_get(dev, USB_TOKEN_OUT, 0), 0, + 0, false, false); + usb_device_handle_control(dev, &p, + DeviceOutRequest | USB_REQ_SET_ADDRESS, + slotid, 0, 0, NULL); + assert(p.status != USB_RET_ASYNC); + usb_packet_cleanup(&p); + } + + 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]); + + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); + xhci_dma_write_u32s(xhci, octx+32, ep0_ctx, sizeof(ep0_ctx)); + + xhci->slots[slotid-1].addressed = 1; + return res; +} + + +static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, + uint64_t pictx, bool dc) +{ + dma_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; + + trace_usb_xhci_slot_configure(slotid); + assert(slotid >= 1 && slotid <= xhci->numslots); + + ictx = xhci_mask64(pictx); + octx = xhci->slots[slotid-1].ctx; + + DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx); + DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx); + + if (dc) { + for (i = 2; i <= 31; i++) { + if (xhci->slots[slotid-1].eps[i-1]) { + xhci_disable_ep(xhci, slotid, i); + } + } + + xhci_dma_read_u32s(xhci, octx, 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]); + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); + + return CC_SUCCESS; + } + + xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx)); + + if ((ictl_ctx[0] & 0x3) != 0x0 || (ictl_ctx[1] & 0x3) != 0x1) { + DPRINTF("xhci: invalid input context control %08x %08x\n", + ictl_ctx[0], ictl_ctx[1]); + return CC_TRB_ERROR; + } + + xhci_dma_read_u32s(xhci, ictx+32, islot_ctx, sizeof(islot_ctx)); + xhci_dma_read_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); + + if (SLOT_STATE(slot_ctx[3]) < SLOT_ADDRESSED) { + DPRINTF("xhci: invalid slot state %08x\n", slot_ctx[3]); + return CC_CONTEXT_STATE_ERROR; + } + + xhci_free_device_streams(xhci, slotid, ictl_ctx[0] | ictl_ctx[1]); + + for (i = 2; i <= 31; i++) { + if (ictl_ctx[0] & (1<<i)) { + xhci_disable_ep(xhci, slotid, i); + } + if (ictl_ctx[1] & (1<<i)) { + xhci_dma_read_u32s(xhci, ictx+32+(32*i), 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]); + xhci_dma_write_u32s(xhci, octx+(32*i), ep_ctx, sizeof(ep_ctx)); + } + } + + res = xhci_alloc_device_streams(xhci, slotid, ictl_ctx[1]); + if (res != CC_SUCCESS) { + for (i = 2; i <= 31; i++) { + if (ictl_ctx[1] & (1u << i)) { + xhci_disable_ep(xhci, slotid, i); + } + } + return res; + } + + 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]); + + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); + + return CC_SUCCESS; +} + + +static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid, + uint64_t pictx) +{ + dma_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]; + + trace_usb_xhci_slot_evaluate(slotid); + assert(slotid >= 1 && slotid <= xhci->numslots); + + ictx = xhci_mask64(pictx); + octx = xhci->slots[slotid-1].ctx; + + DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx); + DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx); + + xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx)); + + if (ictl_ctx[0] != 0x0 || ictl_ctx[1] & ~0x3) { + DPRINTF("xhci: invalid input context control %08x %08x\n", + ictl_ctx[0], ictl_ctx[1]); + return CC_TRB_ERROR; + } + + if (ictl_ctx[1] & 0x1) { + xhci_dma_read_u32s(xhci, ictx+32, 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]); + + xhci_dma_read_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); + + slot_ctx[1] &= ~0xFFFF; /* max exit latency */ + slot_ctx[1] |= islot_ctx[1] & 0xFFFF; + /* update interrupter target field */ + xhci->slots[slotid-1].intr = get_field(islot_ctx[2], TRB_INTR); + set_field(&slot_ctx[2], xhci->slots[slotid-1].intr, TRB_INTR); + + DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", + slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); + } + + if (ictl_ctx[1] & 0x2) { + xhci_dma_read_u32s(xhci, ictx+64, 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]); + + xhci_dma_read_u32s(xhci, octx+32, 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]); + + xhci_dma_write_u32s(xhci, octx+32, ep0_ctx, sizeof(ep0_ctx)); + } + + return CC_SUCCESS; +} + +static TRBCCode xhci_reset_slot(XHCIState *xhci, unsigned int slotid) +{ + uint32_t slot_ctx[4]; + dma_addr_t octx; + int i; + + trace_usb_xhci_slot_reset(slotid); + assert(slotid >= 1 && slotid <= xhci->numslots); + + octx = xhci->slots[slotid-1].ctx; + + DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx); + + for (i = 2; i <= 31; i++) { + if (xhci->slots[slotid-1].eps[i-1]) { + xhci_disable_ep(xhci, slotid, i); + } + } + + xhci_dma_read_u32s(xhci, octx, 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]); + xhci_dma_write_u32s(xhci, octx, 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 > xhci->numslots) { + DPRINTF("xhci: bad slot id %d\n", slotid); + event->ccode = CC_TRB_ERROR; + return 0; + } else if (!xhci->slots[slotid-1].enabled) { + DPRINTF("xhci: slot id %d not enabled\n", slotid); + event->ccode = CC_SLOT_NOT_ENABLED_ERROR; + return 0; + } + return slotid; +} + +/* cleanup slot state on usb device detach */ +static void xhci_detach_slot(XHCIState *xhci, USBPort *uport) +{ + int slot, ep; + + for (slot = 0; slot < xhci->numslots; slot++) { + if (xhci->slots[slot].uport == uport) { + break; + } + } + if (slot == xhci->numslots) { + return; + } + + for (ep = 0; ep < 31; ep++) { + if (xhci->slots[slot].eps[ep]) { + xhci_ep_nuke_xfers(xhci, slot + 1, ep + 1, 0); + } + } + xhci->slots[slot].uport = NULL; +} + +static TRBCCode xhci_get_port_bandwidth(XHCIState *xhci, uint64_t pctx) +{ + dma_addr_t ctx; + uint8_t bw_ctx[xhci->numports+1]; + + DPRINTF("xhci_get_port_bandwidth()\n"); + + ctx = xhci_mask64(pctx); + + DPRINTF("xhci: bandwidth context at "DMA_ADDR_FMT"\n", ctx); + + /* TODO: actually implement real values here */ + bw_ctx[0] = 0; + memset(&bw_ctx[1], 80, xhci->numports); /* 80% */ + dma_memory_write(xhci->as, 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_process_commands(XHCIState *xhci) +{ + XHCITRB trb; + TRBType type; + XHCIEvent event = {ER_COMMAND_COMPLETE, CC_SUCCESS}; + dma_addr_t addr; + unsigned int i, slotid = 0, count = 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 < xhci->numslots; i++) { + if (!xhci->slots[i].enabled) { + break; + } + } + if (i >= xhci->numslots) { + DPRINTF("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; + unsigned int streamid = (trb.status >> 16) & 0xffff; + event.ccode = xhci_set_ep_dequeue(xhci, slotid, + epid, streamid, + 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_NOOP: + event.ccode = CC_SUCCESS; + break; + case CR_VENDOR_NEC_FIRMWARE_REVISION: + if (xhci->nec_quirks) { + event.type = 48; /* NEC reply */ + event.length = 0x3025; + } else { + event.ccode = CC_TRB_ERROR; + } + break; + case CR_VENDOR_NEC_CHALLENGE_RESPONSE: + if (xhci->nec_quirks) { + 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 */ + } else { + event.ccode = CC_TRB_ERROR; + } + break; + default: + trace_usb_xhci_unimplemented("command", type); + event.ccode = CC_TRB_ERROR; + break; + } + event.slotid = slotid; + xhci_event(xhci, &event, 0); + + if (count++ > COMMAND_LIMIT) { + trace_usb_xhci_enforced_limit("commands"); + return; + } + } +} + +static bool xhci_port_have_device(XHCIPort *port) +{ + if (!port->uport->dev || !port->uport->dev->attached) { + return false; /* no device present */ + } + if (!((1 << port->uport->dev->speed) & port->speedmask)) { + return false; /* speed mismatch */ + } + return true; +} + +static void xhci_port_notify(XHCIPort *port, uint32_t bits) +{ + XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS, + port->portnr << 24 }; + + if ((port->portsc & bits) == bits) { + return; + } + trace_usb_xhci_port_notify(port->portnr, bits); + port->portsc |= bits; + if (!xhci_running(port->xhci)) { + return; + } + xhci_event(port->xhci, &ev, 0); +} + +static void xhci_port_update(XHCIPort *port, int is_detach) +{ + uint32_t pls = PLS_RX_DETECT; + + assert(port); + port->portsc = PORTSC_PP; + if (!is_detach && xhci_port_have_device(port)) { + port->portsc |= PORTSC_CCS; + switch (port->uport->dev->speed) { + case USB_SPEED_LOW: + port->portsc |= PORTSC_SPEED_LOW; + pls = PLS_POLLING; + break; + case USB_SPEED_FULL: + port->portsc |= PORTSC_SPEED_FULL; + pls = PLS_POLLING; + break; + case USB_SPEED_HIGH: + port->portsc |= PORTSC_SPEED_HIGH; + pls = PLS_POLLING; + break; + case USB_SPEED_SUPER: + port->portsc |= PORTSC_SPEED_SUPER; + port->portsc |= PORTSC_PED; + pls = PLS_U0; + break; + } + } + set_field(&port->portsc, pls, PORTSC_PLS); + trace_usb_xhci_port_link(port->portnr, pls); + xhci_port_notify(port, PORTSC_CSC); +} + +static void xhci_port_reset(XHCIPort *port, bool warm_reset) +{ + trace_usb_xhci_port_reset(port->portnr, warm_reset); + + if (!xhci_port_have_device(port)) { + return; + } + + usb_device_reset(port->uport->dev); + + switch (port->uport->dev->speed) { + case USB_SPEED_SUPER: + if (warm_reset) { + port->portsc |= PORTSC_WRC; + } + /* fall through */ + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + set_field(&port->portsc, PLS_U0, PORTSC_PLS); + trace_usb_xhci_port_link(port->portnr, PLS_U0); + port->portsc |= PORTSC_PED; + break; + } + + port->portsc &= ~PORTSC_PR; + xhci_port_notify(port, PORTSC_PRC); +} + +static void xhci_reset(DeviceState *dev) +{ + XHCIState *xhci = XHCI(dev); + int i; + + trace_usb_xhci_reset(); + if (!(xhci->usbsts & USBSTS_HCH)) { + DPRINTF("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; + + for (i = 0; i < xhci->numslots; i++) { + xhci_disable_slot(xhci, i+1); + } + + for (i = 0; i < xhci->numports; i++) { + xhci_port_update(xhci->ports + i, 0); + } + + for (i = 0; i < xhci->numintrs; i++) { + xhci->intr[i].iman = 0; + xhci->intr[i].imod = 0; + xhci->intr[i].erstsz = 0; + xhci->intr[i].erstba_low = 0; + xhci->intr[i].erstba_high = 0; + xhci->intr[i].erdp_low = 0; + xhci->intr[i].erdp_high = 0; + + xhci->intr[i].er_ep_idx = 0; + xhci->intr[i].er_pcs = 1; + xhci->intr[i].ev_buffer_put = 0; + xhci->intr[i].ev_buffer_get = 0; + } + + xhci->mfindex_start = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + xhci_mfwrap_update(xhci); +} + +static uint64_t xhci_cap_read(void *ptr, hwaddr reg, unsigned size) +{ + XHCIState *xhci = ptr; + uint32_t ret; + + switch (reg) { + case 0x00: /* HCIVERSION, CAPLENGTH */ + ret = 0x01000000 | LEN_CAP; + break; + case 0x04: /* HCSPARAMS 1 */ + ret = ((xhci->numports_2+xhci->numports_3)<<24) + | (xhci->numintrs<<8) | xhci->numslots; + break; + case 0x08: /* HCSPARAMS 2 */ + ret = 0x0000000f; + break; + case 0x0c: /* HCSPARAMS 3 */ + ret = 0x00000000; + break; + case 0x10: /* HCCPARAMS */ + if (sizeof(dma_addr_t) == 4) { + ret = 0x00080000 | (xhci->max_pstreams_mask << 12); + } else { + ret = 0x00080001 | (xhci->max_pstreams_mask << 12); + } + break; + case 0x14: /* DBOFF */ + ret = OFF_DOORBELL; + break; + case 0x18: /* RTSOFF */ + ret = OFF_RUNTIME; + break; + + /* extended capabilities */ + case 0x20: /* Supported Protocol:00 */ + ret = 0x02000402; /* USB 2.0 */ + break; + case 0x24: /* Supported Protocol:04 */ + ret = 0x20425355; /* "USB " */ + break; + case 0x28: /* Supported Protocol:08 */ + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + ret = (xhci->numports_2<<8) | (xhci->numports_3+1); + } else { + ret = (xhci->numports_2<<8) | 1; + } + break; + case 0x2c: /* Supported Protocol:0c */ + ret = 0x00000000; /* reserved */ + break; + case 0x30: /* Supported Protocol:00 */ + ret = 0x03000002; /* USB 3.0 */ + break; + case 0x34: /* Supported Protocol:04 */ + ret = 0x20425355; /* "USB " */ + break; + case 0x38: /* Supported Protocol:08 */ + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + ret = (xhci->numports_3<<8) | 1; + } else { + ret = (xhci->numports_3<<8) | (xhci->numports_2+1); + } + break; + case 0x3c: /* Supported Protocol:0c */ + ret = 0x00000000; /* reserved */ + break; + default: + trace_usb_xhci_unimplemented("cap read", reg); + ret = 0; + } + + trace_usb_xhci_cap_read(reg, ret); + return ret; +} + +static uint64_t xhci_port_read(void *ptr, hwaddr reg, unsigned size) +{ + XHCIPort *port = ptr; + uint32_t ret; + + switch (reg) { + case 0x00: /* PORTSC */ + ret = port->portsc; + break; + case 0x04: /* PORTPMSC */ + case 0x08: /* PORTLI */ + ret = 0; + break; + case 0x0c: /* reserved */ + default: + trace_usb_xhci_unimplemented("port read", reg); + ret = 0; + } + + trace_usb_xhci_port_read(port->portnr, reg, ret); + return ret; +} + +static void xhci_port_write(void *ptr, hwaddr reg, + uint64_t val, unsigned size) +{ + XHCIPort *port = ptr; + uint32_t portsc, notify; + + trace_usb_xhci_port_write(port->portnr, reg, val); + + switch (reg) { + case 0x00: /* PORTSC */ + /* write-1-to-start bits */ + if (val & PORTSC_WPR) { + xhci_port_reset(port, true); + break; + } + if (val & PORTSC_PR) { + xhci_port_reset(port, false); + break; + } + + portsc = port->portsc; + notify = 0; + /* 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 */ + uint32_t old_pls = get_field(port->portsc, PORTSC_PLS); + uint32_t new_pls = get_field(val, PORTSC_PLS); + switch (new_pls) { + case PLS_U0: + if (old_pls != PLS_U0) { + set_field(&portsc, new_pls, PORTSC_PLS); + trace_usb_xhci_port_link(port->portnr, new_pls); + notify = PORTSC_PLC; + } + break; + case PLS_U3: + if (old_pls < PLS_U3) { + set_field(&portsc, new_pls, PORTSC_PLS); + trace_usb_xhci_port_link(port->portnr, new_pls); + } + break; + case PLS_RESUME: + /* windows does this for some reason, don't spam stderr */ + break; + default: + DPRINTF("%s: ignore pls write (old %d, new %d)\n", + __func__, old_pls, new_pls); + break; + } + } + /* read/write bits */ + portsc &= ~(PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE); + portsc |= (val & (PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE)); + port->portsc = portsc; + if (notify) { + xhci_port_notify(port, notify); + } + break; + case 0x04: /* PORTPMSC */ + case 0x08: /* PORTLI */ + default: + trace_usb_xhci_unimplemented("port write", reg); + } +} + +static uint64_t xhci_oper_read(void *ptr, hwaddr reg, unsigned size) +{ + XHCIState *xhci = ptr; + uint32_t ret; + + switch (reg) { + case 0x00: /* USBCMD */ + ret = xhci->usbcmd; + break; + case 0x04: /* USBSTS */ + ret = xhci->usbsts; + break; + case 0x08: /* PAGESIZE */ + ret = 1; /* 4KiB */ + break; + case 0x14: /* DNCTRL */ + ret = xhci->dnctrl; + break; + case 0x18: /* CRCR low */ + ret = xhci->crcr_low & ~0xe; + break; + case 0x1c: /* CRCR high */ + ret = xhci->crcr_high; + break; + case 0x30: /* DCBAAP low */ + ret = xhci->dcbaap_low; + break; + case 0x34: /* DCBAAP high */ + ret = xhci->dcbaap_high; + break; + case 0x38: /* CONFIG */ + ret = xhci->config; + break; + default: + trace_usb_xhci_unimplemented("oper read", reg); + ret = 0; + } + + trace_usb_xhci_oper_read(reg, ret); + return ret; +} + +static void xhci_oper_write(void *ptr, hwaddr reg, + uint64_t val, unsigned size) +{ + XHCIState *xhci = XHCI(ptr); + + trace_usb_xhci_oper_write(reg, val); + + 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); + } + if (val & USBCMD_CSS) { + /* save state */ + xhci->usbsts &= ~USBSTS_SRE; + } + if (val & USBCMD_CRS) { + /* restore state */ + xhci->usbsts |= USBSTS_SRE; + } + xhci->usbcmd = val & 0xc0f; + xhci_mfwrap_update(xhci); + if (val & USBCMD_HCRST) { + xhci_reset(DEVICE(xhci)); + } + xhci_intr_update(xhci, 0); + break; + + case 0x04: /* USBSTS */ + /* these bits are write-1-to-clear */ + xhci->usbsts &= ~(val & (USBSTS_HSE|USBSTS_EINT|USBSTS_PCD|USBSTS_SRE)); + xhci_intr_update(xhci, 0); + 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, 0); + DPRINTF("xhci: command ring stopped (CRCR=%08x)\n", xhci->crcr_low); + } else { + dma_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: + trace_usb_xhci_unimplemented("oper write", reg); + } +} + +static uint64_t xhci_runtime_read(void *ptr, hwaddr reg, + unsigned size) +{ + XHCIState *xhci = ptr; + uint32_t ret = 0; + + if (reg < 0x20) { + switch (reg) { + case 0x00: /* MFINDEX */ + ret = xhci_mfindex_get(xhci) & 0x3fff; + break; + default: + trace_usb_xhci_unimplemented("runtime read", reg); + break; + } + } else { + int v = (reg - 0x20) / 0x20; + XHCIInterrupter *intr = &xhci->intr[v]; + switch (reg & 0x1f) { + case 0x00: /* IMAN */ + ret = intr->iman; + break; + case 0x04: /* IMOD */ + ret = intr->imod; + break; + case 0x08: /* ERSTSZ */ + ret = intr->erstsz; + break; + case 0x10: /* ERSTBA low */ + ret = intr->erstba_low; + break; + case 0x14: /* ERSTBA high */ + ret = intr->erstba_high; + break; + case 0x18: /* ERDP low */ + ret = intr->erdp_low; + break; + case 0x1c: /* ERDP high */ + ret = intr->erdp_high; + break; + } + } + + trace_usb_xhci_runtime_read(reg, ret); + return ret; +} + +static void xhci_runtime_write(void *ptr, hwaddr reg, + uint64_t val, unsigned size) +{ + XHCIState *xhci = ptr; + XHCIInterrupter *intr; + int v; + + trace_usb_xhci_runtime_write(reg, val); + + if (reg < 0x20) { + trace_usb_xhci_unimplemented("runtime write", reg); + return; + } + v = (reg - 0x20) / 0x20; + intr = &xhci->intr[v]; + + switch (reg & 0x1f) { + case 0x00: /* IMAN */ + if (val & IMAN_IP) { + intr->iman &= ~IMAN_IP; + } + intr->iman &= ~IMAN_IE; + intr->iman |= val & IMAN_IE; + xhci_intr_update(xhci, v); + break; + case 0x04: /* IMOD */ + intr->imod = val; + break; + case 0x08: /* ERSTSZ */ + intr->erstsz = val & 0xffff; + break; + case 0x10: /* ERSTBA low */ + if (xhci->nec_quirks) { + /* NEC driver bug: it doesn't align this to 64 bytes */ + intr->erstba_low = val & 0xfffffff0; + } else { + intr->erstba_low = val & 0xffffffc0; + } + break; + case 0x14: /* ERSTBA high */ + intr->erstba_high = val; + xhci_er_reset(xhci, v); + break; + case 0x18: /* ERDP low */ + if (val & ERDP_EHB) { + intr->erdp_low &= ~ERDP_EHB; + } + intr->erdp_low = (val & ~ERDP_EHB) | (intr->erdp_low & ERDP_EHB); + if (val & ERDP_EHB) { + dma_addr_t erdp = xhci_addr64(intr->erdp_low, intr->erdp_high); + unsigned int dp_idx = (erdp - intr->er_start) / TRB_SIZE; + if (erdp >= intr->er_start && + erdp < (intr->er_start + TRB_SIZE * intr->er_size) && + dp_idx != intr->er_ep_idx) { + xhci_intr_raise(xhci, v); + } + } + break; + case 0x1c: /* ERDP high */ + intr->erdp_high = val; + break; + default: + trace_usb_xhci_unimplemented("oper write", reg); + } +} + +static uint64_t xhci_doorbell_read(void *ptr, hwaddr reg, + unsigned size) +{ + /* doorbells always read as 0 */ + trace_usb_xhci_doorbell_read(reg, 0); + return 0; +} + +static void xhci_doorbell_write(void *ptr, hwaddr reg, + uint64_t val, unsigned size) +{ + XHCIState *xhci = ptr; + unsigned int epid, streamid; + + trace_usb_xhci_doorbell_write(reg, val); + + if (!xhci_running(xhci)) { + DPRINTF("xhci: wrote doorbell while xHC stopped or paused\n"); + return; + } + + reg >>= 2; + + if (reg == 0) { + if (val == 0) { + xhci_process_commands(xhci); + } else { + DPRINTF("xhci: bad doorbell 0 write: 0x%x\n", + (uint32_t)val); + } + } else { + epid = val & 0xff; + streamid = (val >> 16) & 0xffff; + if (reg > xhci->numslots) { + DPRINTF("xhci: bad doorbell %d\n", (int)reg); + } else if (epid == 0 || epid > 31) { + DPRINTF("xhci: bad doorbell %d write: 0x%x\n", + (int)reg, (uint32_t)val); + } else { + xhci_kick_ep(xhci, reg, epid, streamid); + } + } +} + +static void xhci_cap_write(void *opaque, hwaddr addr, uint64_t val, + unsigned width) +{ + /* nothing */ +} + +static const MemoryRegionOps xhci_cap_ops = { + .read = xhci_cap_read, + .write = xhci_cap_write, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .impl.min_access_size = 4, + .impl.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const MemoryRegionOps xhci_oper_ops = { + .read = xhci_oper_read, + .write = xhci_oper_write, + .valid.min_access_size = 4, + .valid.max_access_size = sizeof(dma_addr_t), + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const MemoryRegionOps xhci_port_ops = { + .read = xhci_port_read, + .write = xhci_port_write, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const MemoryRegionOps xhci_runtime_ops = { + .read = xhci_runtime_read, + .write = xhci_runtime_write, + .valid.min_access_size = 4, + .valid.max_access_size = sizeof(dma_addr_t), + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const MemoryRegionOps xhci_doorbell_ops = { + .read = xhci_doorbell_read, + .write = xhci_doorbell_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_lookup_port(xhci, usbport); + + xhci_port_update(port, 0); +} + +static void xhci_detach(USBPort *usbport) +{ + XHCIState *xhci = usbport->opaque; + XHCIPort *port = xhci_lookup_port(xhci, usbport); + + xhci_detach_slot(xhci, usbport); + xhci_port_update(port, 1); +} + +static void xhci_wakeup(USBPort *usbport) +{ + XHCIState *xhci = usbport->opaque; + XHCIPort *port = xhci_lookup_port(xhci, usbport); + + assert(port); + if (get_field(port->portsc, PORTSC_PLS) != PLS_U3) { + return; + } + set_field(&port->portsc, PLS_RESUME, PORTSC_PLS); + xhci_port_notify(port, PORTSC_PLC); +} + +static void xhci_complete(USBPort *port, USBPacket *packet) +{ + XHCITransfer *xfer = container_of(packet, XHCITransfer, packet); + + if (packet->status == USB_RET_REMOVE_FROM_QUEUE) { + xhci_ep_nuke_one_xfer(xfer, 0); + return; + } + xhci_try_complete_packet(xfer); + xhci_kick_epctx(xfer->epctx, xfer->streamid); + if (xfer->complete) { + xhci_ep_free_xfer(xfer); + } +} + +static void xhci_child_detach(USBPort *uport, USBDevice *child) +{ + USBBus *bus = usb_bus_from_device(child); + XHCIState *xhci = container_of(bus, XHCIState, bus); + + xhci_detach_slot(xhci, child->port); +} + +static USBPortOps xhci_uport_ops = { + .attach = xhci_attach, + .detach = xhci_detach, + .wakeup = xhci_wakeup, + .complete = xhci_complete, + .child_detach = xhci_child_detach, +}; + +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 USBEndpoint *xhci_epid_to_usbep(XHCIEPContext *epctx) +{ + USBPort *uport; + uint32_t token; + + if (!epctx) { + return NULL; + } + uport = epctx->xhci->slots[epctx->slotid - 1].uport; + if (!uport || !uport->dev) { + return NULL; + } + token = (epctx->epid & 1) ? USB_TOKEN_IN : USB_TOKEN_OUT; + return usb_ep_get(uport->dev, token, epctx->epid >> 1); +} + +static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep, + unsigned int stream) +{ + XHCIState *xhci = container_of(bus, XHCIState, bus); + int slotid; + + DPRINTF("%s\n", __func__); + slotid = ep->dev->addr; + 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), stream); +} + +static USBBusOps xhci_bus_ops = { + .wakeup_endpoint = xhci_wakeup_endpoint, +}; + +static void usb_xhci_init(XHCIState *xhci) +{ + XHCIPort *port; + unsigned int i, usbports, speedmask; + + xhci->usbsts = USBSTS_HCH; + + if (xhci->numports_2 > XHCI_MAXPORTS_2) { + xhci->numports_2 = XHCI_MAXPORTS_2; + } + if (xhci->numports_3 > XHCI_MAXPORTS_3) { + xhci->numports_3 = XHCI_MAXPORTS_3; + } + usbports = MAX(xhci->numports_2, xhci->numports_3); + xhci->numports = xhci->numports_2 + xhci->numports_3; + + usb_bus_new(&xhci->bus, sizeof(xhci->bus), &xhci_bus_ops, xhci->hostOpaque); + + for (i = 0; i < usbports; i++) { + speedmask = 0; + if (i < xhci->numports_2) { + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + port = &xhci->ports[i + xhci->numports_3]; + port->portnr = i + 1 + xhci->numports_3; + } else { + port = &xhci->ports[i]; + port->portnr = i + 1; + } + port->uport = &xhci->uports[i]; + port->speedmask = + USB_SPEED_MASK_LOW | + USB_SPEED_MASK_FULL | + USB_SPEED_MASK_HIGH; + assert(i < XHCI_MAXPORTS); + snprintf(port->name, sizeof(port->name), "usb2 port #%d", i+1); + speedmask |= port->speedmask; + } + if (i < xhci->numports_3) { + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + port = &xhci->ports[i]; + port->portnr = i + 1; + } else { + port = &xhci->ports[i + xhci->numports_2]; + port->portnr = i + 1 + xhci->numports_2; + } + port->uport = &xhci->uports[i]; + port->speedmask = USB_SPEED_MASK_SUPER; + assert(i < XHCI_MAXPORTS); + snprintf(port->name, sizeof(port->name), "usb3 port #%d", i+1); + speedmask |= port->speedmask; + } + usb_register_port(&xhci->bus, &xhci->uports[i], xhci, i, + &xhci_uport_ops, speedmask); + } +} + +static void usb_xhci_realize(DeviceState *dev, Error **errp) +{ + int i; + + XHCIState *xhci = XHCI(dev); + + if (xhci->numintrs > XHCI_MAXINTRS) { + xhci->numintrs = XHCI_MAXINTRS; + } + while (xhci->numintrs & (xhci->numintrs - 1)) { /* ! power of 2 */ + xhci->numintrs++; + } + if (xhci->numintrs < 1) { + xhci->numintrs = 1; + } + if (xhci->numslots > XHCI_MAXSLOTS) { + xhci->numslots = XHCI_MAXSLOTS; + } + if (xhci->numslots < 1) { + xhci->numslots = 1; + } + if (xhci_get_flag(xhci, XHCI_FLAG_ENABLE_STREAMS)) { + xhci->max_pstreams_mask = 7; /* == 256 primary streams */ + } else { + xhci->max_pstreams_mask = 0; + } + + usb_xhci_init(xhci); + xhci->mfwrap_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_mfwrap_timer, xhci); + + memory_region_init(&xhci->mem, OBJECT(dev), "xhci", XHCI_LEN_REGS); + memory_region_init_io(&xhci->mem_cap, OBJECT(dev), &xhci_cap_ops, xhci, + "capabilities", LEN_CAP); + memory_region_init_io(&xhci->mem_oper, OBJECT(dev), &xhci_oper_ops, xhci, + "operational", 0x400); + memory_region_init_io(&xhci->mem_runtime, OBJECT(dev), &xhci_runtime_ops, + xhci, "runtime", LEN_RUNTIME); + memory_region_init_io(&xhci->mem_doorbell, OBJECT(dev), &xhci_doorbell_ops, + xhci, "doorbell", LEN_DOORBELL); + + memory_region_add_subregion(&xhci->mem, 0, &xhci->mem_cap); + memory_region_add_subregion(&xhci->mem, OFF_OPER, &xhci->mem_oper); + memory_region_add_subregion(&xhci->mem, OFF_RUNTIME, &xhci->mem_runtime); + memory_region_add_subregion(&xhci->mem, OFF_DOORBELL, &xhci->mem_doorbell); + + for (i = 0; i < xhci->numports; i++) { + XHCIPort *port = &xhci->ports[i]; + uint32_t offset = OFF_OPER + 0x400 + 0x10 * i; + port->xhci = xhci; + memory_region_init_io(&port->mem, OBJECT(dev), &xhci_port_ops, port, + port->name, 0x10); + memory_region_add_subregion(&xhci->mem, offset, &port->mem); + } +} + +static void usb_xhci_unrealize(DeviceState *dev) +{ + int i; + XHCIState *xhci = XHCI(dev); + + trace_usb_xhci_exit(); + + for (i = 0; i < xhci->numslots; i++) { + xhci_disable_slot(xhci, i + 1); + } + + if (xhci->mfwrap_timer) { + timer_free(xhci->mfwrap_timer); + xhci->mfwrap_timer = NULL; + } + + memory_region_del_subregion(&xhci->mem, &xhci->mem_cap); + memory_region_del_subregion(&xhci->mem, &xhci->mem_oper); + memory_region_del_subregion(&xhci->mem, &xhci->mem_runtime); + memory_region_del_subregion(&xhci->mem, &xhci->mem_doorbell); + + for (i = 0; i < xhci->numports; i++) { + XHCIPort *port = &xhci->ports[i]; + memory_region_del_subregion(&xhci->mem, &port->mem); + } + + usb_bus_release(&xhci->bus); +} + +static int usb_xhci_post_load(void *opaque, int version_id) +{ + XHCIState *xhci = opaque; + XHCISlot *slot; + XHCIEPContext *epctx; + dma_addr_t dcbaap, pctx; + uint32_t slot_ctx[4]; + uint32_t ep_ctx[5]; + int slotid, epid, state; + + dcbaap = xhci_addr64(xhci->dcbaap_low, xhci->dcbaap_high); + + for (slotid = 1; slotid <= xhci->numslots; slotid++) { + slot = &xhci->slots[slotid-1]; + if (!slot->addressed) { + continue; + } + slot->ctx = + xhci_mask64(ldq_le_dma(xhci->as, dcbaap + 8 * slotid)); + xhci_dma_read_u32s(xhci, slot->ctx, slot_ctx, sizeof(slot_ctx)); + slot->uport = xhci_lookup_uport(xhci, slot_ctx); + if (!slot->uport) { + /* should not happen, but may trigger on guest bugs */ + slot->enabled = 0; + slot->addressed = 0; + continue; + } + assert(slot->uport && slot->uport->dev); + + for (epid = 1; epid <= 31; epid++) { + pctx = slot->ctx + 32 * epid; + xhci_dma_read_u32s(xhci, pctx, ep_ctx, sizeof(ep_ctx)); + state = ep_ctx[0] & EP_STATE_MASK; + if (state == EP_DISABLED) { + continue; + } + epctx = xhci_alloc_epctx(xhci, slotid, epid); + slot->eps[epid-1] = epctx; + xhci_init_epctx(epctx, pctx, ep_ctx); + epctx->state = state; + if (state == EP_RUNNING) { + /* kick endpoint after vmload is finished */ + timer_mod(epctx->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); + } + } + } + return 0; +} + +static const VMStateDescription vmstate_xhci_ring = { + .name = "xhci-ring", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(dequeue, XHCIRing), + VMSTATE_BOOL(ccs, XHCIRing), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_xhci_port = { + .name = "xhci-port", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(portsc, XHCIPort), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_xhci_slot = { + .name = "xhci-slot", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BOOL(enabled, XHCISlot), + VMSTATE_BOOL(addressed, XHCISlot), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_xhci_event = { + .name = "xhci-event", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(type, XHCIEvent), + VMSTATE_UINT32(ccode, XHCIEvent), + VMSTATE_UINT64(ptr, XHCIEvent), + VMSTATE_UINT32(length, XHCIEvent), + VMSTATE_UINT32(flags, XHCIEvent), + VMSTATE_UINT8(slotid, XHCIEvent), + VMSTATE_UINT8(epid, XHCIEvent), + VMSTATE_END_OF_LIST() + } +}; + +static bool xhci_er_full(void *opaque, int version_id) +{ + return false; +} + +static const VMStateDescription vmstate_xhci_intr = { + .name = "xhci-intr", + .version_id = 1, + .fields = (VMStateField[]) { + /* registers */ + VMSTATE_UINT32(iman, XHCIInterrupter), + VMSTATE_UINT32(imod, XHCIInterrupter), + VMSTATE_UINT32(erstsz, XHCIInterrupter), + VMSTATE_UINT32(erstba_low, XHCIInterrupter), + VMSTATE_UINT32(erstba_high, XHCIInterrupter), + VMSTATE_UINT32(erdp_low, XHCIInterrupter), + VMSTATE_UINT32(erdp_high, XHCIInterrupter), + + /* state */ + VMSTATE_BOOL(msix_used, XHCIInterrupter), + VMSTATE_BOOL(er_pcs, XHCIInterrupter), + VMSTATE_UINT64(er_start, XHCIInterrupter), + VMSTATE_UINT32(er_size, XHCIInterrupter), + VMSTATE_UINT32(er_ep_idx, XHCIInterrupter), + + /* event queue (used if ring is full) */ + VMSTATE_BOOL(er_full_unused, XHCIInterrupter), + VMSTATE_UINT32_TEST(ev_buffer_put, XHCIInterrupter, xhci_er_full), + VMSTATE_UINT32_TEST(ev_buffer_get, XHCIInterrupter, xhci_er_full), + VMSTATE_STRUCT_ARRAY_TEST(ev_buffer, XHCIInterrupter, EV_QUEUE, + xhci_er_full, 1, + vmstate_xhci_event, XHCIEvent), + + VMSTATE_END_OF_LIST() + } +}; + +const VMStateDescription vmstate_xhci = { + .name = "xhci-core", + .version_id = 1, + .post_load = usb_xhci_post_load, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_VARRAY_UINT32(ports, XHCIState, numports, 1, + vmstate_xhci_port, XHCIPort), + VMSTATE_STRUCT_VARRAY_UINT32(slots, XHCIState, numslots, 1, + vmstate_xhci_slot, XHCISlot), + VMSTATE_STRUCT_VARRAY_UINT32(intr, XHCIState, numintrs, 1, + vmstate_xhci_intr, XHCIInterrupter), + + /* Operational Registers */ + VMSTATE_UINT32(usbcmd, XHCIState), + VMSTATE_UINT32(usbsts, XHCIState), + VMSTATE_UINT32(dnctrl, XHCIState), + VMSTATE_UINT32(crcr_low, XHCIState), + VMSTATE_UINT32(crcr_high, XHCIState), + VMSTATE_UINT32(dcbaap_low, XHCIState), + VMSTATE_UINT32(dcbaap_high, XHCIState), + VMSTATE_UINT32(config, XHCIState), + + /* Runtime Registers & state */ + VMSTATE_INT64(mfindex_start, XHCIState), + VMSTATE_TIMER_PTR(mfwrap_timer, XHCIState), + VMSTATE_STRUCT(cmd_ring, XHCIState, 1, vmstate_xhci_ring, XHCIRing), + + VMSTATE_END_OF_LIST() + } +}; + +static Property xhci_properties[] = { + DEFINE_PROP_BIT("streams", XHCIState, flags, + XHCI_FLAG_ENABLE_STREAMS, true), + DEFINE_PROP_UINT32("p2", XHCIState, numports_2, 4), + DEFINE_PROP_UINT32("p3", XHCIState, numports_3, 4), + DEFINE_PROP_LINK("host", XHCIState, hostOpaque, TYPE_DEVICE, + DeviceState *), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xhci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = usb_xhci_realize; + dc->unrealize = usb_xhci_unrealize; + dc->reset = xhci_reset; + device_class_set_props(dc, xhci_properties); + dc->user_creatable = false; +} + +static const TypeInfo xhci_info = { + .name = TYPE_XHCI, + .parent = TYPE_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/hcd-xhci.h b/hw/usb/hcd-xhci.h new file mode 100644 index 000000000..98f598382 --- /dev/null +++ b/hw/usb/hcd-xhci.h @@ -0,0 +1,228 @@ +/* + * 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.1 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/>. + */ + +#ifndef HW_USB_HCD_XHCI_H +#define HW_USB_HCD_XHCI_H +#include "qom/object.h" + +#include "hw/usb.h" +#include "hw/usb/xhci.h" +#include "sysemu/dma.h" + +OBJECT_DECLARE_SIMPLE_TYPE(XHCIState, XHCI) + +/* Very pessimistic, let's hope it's enough for all cases */ +#define EV_QUEUE (((3 * 24) + 16) * XHCI_MAXSLOTS) + +typedef struct XHCIStreamContext XHCIStreamContext; +typedef struct XHCIEPContext XHCIEPContext; + +enum xhci_flags { + XHCI_FLAG_SS_FIRST = 1, + XHCI_FLAG_FORCE_PCIE_ENDCAP, + XHCI_FLAG_ENABLE_STREAMS, +}; + +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_NEC_FIRMWARE_REVISION = 49, + CR_VENDOR_NEC_CHALLENGE_RESPONSE = 50, +} TRBType; + +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; + +typedef struct XHCIRing { + dma_addr_t dequeue; + bool ccs; +} XHCIRing; + +typedef struct XHCIPort { + XHCIState *xhci; + uint32_t portsc; + uint32_t portnr; + USBPort *uport; + uint32_t speedmask; + char name[20]; + MemoryRegion mem; +} XHCIPort; + +typedef struct XHCISlot { + bool enabled; + bool addressed; + uint16_t intr; + dma_addr_t ctx; + USBPort *uport; + 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; + +typedef struct XHCIInterrupter { + 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; + + bool msix_used, er_pcs; + + dma_addr_t er_start; + uint32_t er_size; + unsigned int er_ep_idx; + + /* kept for live migration compat only */ + bool er_full_unused; + XHCIEvent ev_buffer[EV_QUEUE]; + unsigned int ev_buffer_put; + unsigned int ev_buffer_get; + +} XHCIInterrupter; + +typedef struct XHCIState { + DeviceState parent; + + USBBus bus; + MemoryRegion mem; + MemoryRegion *dma_mr; + AddressSpace *as; + MemoryRegion mem_cap; + MemoryRegion mem_oper; + MemoryRegion mem_runtime; + MemoryRegion mem_doorbell; + + /* properties */ + uint32_t numports_2; + uint32_t numports_3; + uint32_t numintrs; + uint32_t numslots; + uint32_t flags; + uint32_t max_pstreams_mask; + void (*intr_update)(XHCIState *s, int n, bool enable); + bool (*intr_raise)(XHCIState *s, int n, bool level); + DeviceState *hostOpaque; + + /* 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; + + USBPort uports[MAX_CONST(XHCI_MAXPORTS_2, XHCI_MAXPORTS_3)]; + XHCIPort ports[XHCI_MAXPORTS]; + XHCISlot slots[XHCI_MAXSLOTS]; + uint32_t numports; + + /* Runtime Registers */ + int64_t mfindex_start; + QEMUTimer *mfwrap_timer; + XHCIInterrupter intr[XHCI_MAXINTRS]; + + XHCIRing cmd_ring; + + bool nec_quirks; +} XHCIState; + +extern const VMStateDescription vmstate_xhci; +bool xhci_get_flag(XHCIState *xhci, enum xhci_flags bit); +void xhci_set_flag(XHCIState *xhci, enum xhci_flags bit); +#endif diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c new file mode 100644 index 000000000..d0d46dd0a --- /dev/null +++ b/hw/usb/host-libusb.c @@ -0,0 +1,1978 @@ +/* + * 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 + * + * (c) 2012 Gerd Hoffmann <kraxel@redhat.com> + * Completely rewritten to use libusb instead of usbfs ioctls. + * + * 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/osdep.h" +#include "qom/object.h" +#ifndef CONFIG_WIN32 +#include <poll.h> +#endif +#include <libusb.h> + +#ifdef CONFIG_LINUX +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> +#endif + +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "monitor/monitor.h" +#include "qemu/error-report.h" +#include "qemu/main-loop.h" +#include "qemu/module.h" +#include "sysemu/runstate.h" +#include "sysemu/sysemu.h" +#include "trace.h" + +#include "hw/qdev-properties.h" +#include "hw/usb.h" + +/* ------------------------------------------------------------------------ */ + +#define TYPE_USB_HOST_DEVICE "usb-host" +OBJECT_DECLARE_SIMPLE_TYPE(USBHostDevice, USB_HOST_DEVICE) + +typedef struct USBHostRequest USBHostRequest; +typedef struct USBHostIsoXfer USBHostIsoXfer; +typedef struct USBHostIsoRing USBHostIsoRing; + +struct USBAutoFilter { + uint32_t bus_num; + uint32_t addr; + char *port; + uint32_t vendor_id; + uint32_t product_id; +}; + +enum USBHostDeviceOptions { + USB_HOST_OPT_PIPELINE, +}; + +struct USBHostDevice { + USBDevice parent_obj; + + /* properties */ + struct USBAutoFilter match; + char *hostdevice; + int32_t bootindex; + uint32_t iso_urb_count; + uint32_t iso_urb_frames; + uint32_t options; + uint32_t loglevel; + bool needs_autoscan; + bool allow_one_guest_reset; + bool allow_all_guest_resets; + bool suppress_remote_wake; + + /* state */ + QTAILQ_ENTRY(USBHostDevice) next; + int seen, errcount; + int bus_num; + int addr; + char port[16]; + + int hostfd; + libusb_device *dev; + libusb_device_handle *dh; + struct libusb_device_descriptor ddesc; + + struct { + bool detached; + bool claimed; + } ifs[USB_MAX_INTERFACES]; + + /* callbacks & friends */ + QEMUBH *bh_nodev; + QEMUBH *bh_postld; + bool bh_postld_pending; + Notifier exit; + + /* request queues */ + QTAILQ_HEAD(, USBHostRequest) requests; + QTAILQ_HEAD(, USBHostIsoRing) isorings; +}; + +struct USBHostRequest { + USBHostDevice *host; + USBPacket *p; + bool in; + struct libusb_transfer *xfer; + unsigned char *buffer; + unsigned char *cbuf; + unsigned int clen; + bool usb3ep0quirk; + QTAILQ_ENTRY(USBHostRequest) next; +}; + +struct USBHostIsoXfer { + USBHostIsoRing *ring; + struct libusb_transfer *xfer; + bool copy_complete; + unsigned int packet; + QTAILQ_ENTRY(USBHostIsoXfer) next; +}; + +struct USBHostIsoRing { + USBHostDevice *host; + USBEndpoint *ep; + QTAILQ_HEAD(, USBHostIsoXfer) unused; + QTAILQ_HEAD(, USBHostIsoXfer) inflight; + QTAILQ_HEAD(, USBHostIsoXfer) copy; + QTAILQ_ENTRY(USBHostIsoRing) next; +}; + +static QTAILQ_HEAD(, USBHostDevice) hostdevs = + QTAILQ_HEAD_INITIALIZER(hostdevs); + +static void usb_host_auto_check(void *unused); +static void usb_host_release_interfaces(USBHostDevice *s); +static void usb_host_nodev(USBHostDevice *s); +static void usb_host_detach_kernel(USBHostDevice *s); +static void usb_host_attach_kernel(USBHostDevice *s); + +/* ------------------------------------------------------------------------ */ + +#ifndef LIBUSB_LOG_LEVEL_WARNING /* older libusb didn't define these */ +#define LIBUSB_LOG_LEVEL_WARNING 2 +#endif + +/* ------------------------------------------------------------------------ */ + +#define CONTROL_TIMEOUT 10000 /* 10 sec */ +#define BULK_TIMEOUT 0 /* unlimited */ +#define INTR_TIMEOUT 0 /* unlimited */ + +#ifndef LIBUSB_API_VERSION +# define LIBUSB_API_VERSION LIBUSBX_API_VERSION +#endif +#if LIBUSB_API_VERSION >= 0x01000103 +# define HAVE_STREAMS 1 +#endif +#if LIBUSB_API_VERSION >= 0x01000106 +# define HAVE_SUPER_PLUS 1 +#endif + +static const char *speed_name[] = { + [LIBUSB_SPEED_UNKNOWN] = "?", + [LIBUSB_SPEED_LOW] = "1.5", + [LIBUSB_SPEED_FULL] = "12", + [LIBUSB_SPEED_HIGH] = "480", + [LIBUSB_SPEED_SUPER] = "5000", +#ifdef HAVE_SUPER_PLUS + [LIBUSB_SPEED_SUPER_PLUS] = "5000+", +#endif +}; + +static const unsigned int speed_map[] = { + [LIBUSB_SPEED_LOW] = USB_SPEED_LOW, + [LIBUSB_SPEED_FULL] = USB_SPEED_FULL, + [LIBUSB_SPEED_HIGH] = USB_SPEED_HIGH, + [LIBUSB_SPEED_SUPER] = USB_SPEED_SUPER, +#ifdef HAVE_SUPER_PLUS + [LIBUSB_SPEED_SUPER_PLUS] = USB_SPEED_SUPER, +#endif +}; + +static const unsigned int status_map[] = { + [LIBUSB_TRANSFER_COMPLETED] = USB_RET_SUCCESS, + [LIBUSB_TRANSFER_ERROR] = USB_RET_IOERROR, + [LIBUSB_TRANSFER_TIMED_OUT] = USB_RET_IOERROR, + [LIBUSB_TRANSFER_CANCELLED] = USB_RET_IOERROR, + [LIBUSB_TRANSFER_STALL] = USB_RET_STALL, + [LIBUSB_TRANSFER_NO_DEVICE] = USB_RET_NODEV, + [LIBUSB_TRANSFER_OVERFLOW] = USB_RET_BABBLE, +}; + +static const char *err_names[] = { + [-LIBUSB_ERROR_IO] = "IO", + [-LIBUSB_ERROR_INVALID_PARAM] = "INVALID_PARAM", + [-LIBUSB_ERROR_ACCESS] = "ACCESS", + [-LIBUSB_ERROR_NO_DEVICE] = "NO_DEVICE", + [-LIBUSB_ERROR_NOT_FOUND] = "NOT_FOUND", + [-LIBUSB_ERROR_BUSY] = "BUSY", + [-LIBUSB_ERROR_TIMEOUT] = "TIMEOUT", + [-LIBUSB_ERROR_OVERFLOW] = "OVERFLOW", + [-LIBUSB_ERROR_PIPE] = "PIPE", + [-LIBUSB_ERROR_INTERRUPTED] = "INTERRUPTED", + [-LIBUSB_ERROR_NO_MEM] = "NO_MEM", + [-LIBUSB_ERROR_NOT_SUPPORTED] = "NOT_SUPPORTED", + [-LIBUSB_ERROR_OTHER] = "OTHER", +}; + +static libusb_context *ctx; +static uint32_t loglevel; + +#ifndef CONFIG_WIN32 + +static void usb_host_handle_fd(void *opaque) +{ + struct timeval tv = { 0, 0 }; + libusb_handle_events_timeout(ctx, &tv); +} + +static void usb_host_add_fd(int fd, short events, void *user_data) +{ + qemu_set_fd_handler(fd, + (events & POLLIN) ? usb_host_handle_fd : NULL, + (events & POLLOUT) ? usb_host_handle_fd : NULL, + ctx); +} + +static void usb_host_del_fd(int fd, void *user_data) +{ + qemu_set_fd_handler(fd, NULL, NULL, NULL); +} + +#else + +static QEMUTimer *poll_timer; +static uint32_t request_count; + +static void usb_host_timer_kick(void) +{ + int64_t delay_ns; + + delay_ns = request_count + ? (NANOSECONDS_PER_SECOND / 100) /* 10 ms interval with active req */ + : (NANOSECONDS_PER_SECOND); /* 1 sec interval otherwise */ + timer_mod(poll_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delay_ns); +} + +static void usb_host_timer(void *opaque) +{ + struct timeval tv = { 0, 0 }; + + libusb_handle_events_timeout(ctx, &tv); + usb_host_timer_kick(); +} + +#endif /* !CONFIG_WIN32 */ + +static int usb_host_init(void) +{ +#ifndef CONFIG_WIN32 + const struct libusb_pollfd **poll; +#endif + int rc; + + if (ctx) { + return 0; + } + rc = libusb_init(&ctx); + if (rc != 0) { + return -1; + } +#if LIBUSB_API_VERSION >= 0x01000106 + libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, loglevel); +#else + libusb_set_debug(ctx, loglevel); +#endif +#ifdef CONFIG_WIN32 + poll_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, usb_host_timer, NULL); + usb_host_timer_kick(); +#else + libusb_set_pollfd_notifiers(ctx, usb_host_add_fd, + usb_host_del_fd, + ctx); + poll = libusb_get_pollfds(ctx); + if (poll) { + int i; + for (i = 0; poll[i] != NULL; i++) { + usb_host_add_fd(poll[i]->fd, poll[i]->events, ctx); + } + } + free(poll); +#endif + return 0; +} + +static int usb_host_get_port(libusb_device *dev, char *port, size_t len) +{ + uint8_t path[7]; + size_t off; + int rc, i; + +#if LIBUSB_API_VERSION >= 0x01000102 + rc = libusb_get_port_numbers(dev, path, 7); +#else + rc = libusb_get_port_path(ctx, dev, path, 7); +#endif + if (rc < 0) { + return 0; + } + off = snprintf(port, len, "%d", path[0]); + for (i = 1; i < rc; i++) { + off += snprintf(port+off, len-off, ".%d", path[i]); + } + return off; +} + +static void usb_host_libusb_error(const char *func, int rc) +{ + const char *errname; + + if (rc >= 0) { + return; + } + + if (-rc < ARRAY_SIZE(err_names) && err_names[-rc]) { + errname = err_names[-rc]; + } else { + errname = "?"; + } + error_report("%s: %d [%s]", func, rc, errname); +} + +/* ------------------------------------------------------------------------ */ + +static bool usb_host_use_combining(USBEndpoint *ep) +{ + int type; + + if (!ep->pipeline) { + return false; + } + if (ep->pid != USB_TOKEN_IN) { + return false; + } + type = usb_ep_get_type(ep->dev, ep->pid, ep->nr); + if (type != USB_ENDPOINT_XFER_BULK) { + return false; + } + return true; +} + +/* ------------------------------------------------------------------------ */ + +static USBHostRequest *usb_host_req_alloc(USBHostDevice *s, USBPacket *p, + bool in, size_t bufsize) +{ + USBHostRequest *r = g_new0(USBHostRequest, 1); + + r->host = s; + r->p = p; + r->in = in; + r->xfer = libusb_alloc_transfer(0); + if (bufsize) { + r->buffer = g_malloc(bufsize); + } + QTAILQ_INSERT_TAIL(&s->requests, r, next); +#ifdef CONFIG_WIN32 + request_count++; + usb_host_timer_kick(); +#endif + return r; +} + +static void usb_host_req_free(USBHostRequest *r) +{ +#ifdef CONFIG_WIN32 + request_count--; +#endif + QTAILQ_REMOVE(&r->host->requests, r, next); + libusb_free_transfer(r->xfer); + g_free(r->buffer); + g_free(r); +} + +static USBHostRequest *usb_host_req_find(USBHostDevice *s, USBPacket *p) +{ + USBHostRequest *r; + + QTAILQ_FOREACH(r, &s->requests, next) { + if (r->p == p) { + return r; + } + } + return NULL; +} + +static void LIBUSB_CALL usb_host_req_complete_ctrl(struct libusb_transfer *xfer) +{ + USBHostRequest *r = xfer->user_data; + USBHostDevice *s = r->host; + bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE); + + if (r->p == NULL) { + goto out; /* request was canceled */ + } + + r->p->status = status_map[xfer->status]; + r->p->actual_length = xfer->actual_length; + if (r->in && xfer->actual_length) { + USBDevice *udev = USB_DEVICE(s); + struct libusb_config_descriptor *conf = (void *)r->cbuf; + memcpy(r->cbuf, r->buffer + 8, xfer->actual_length); + + /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices + * to work redirected to a not superspeed capable hcd */ + if (r->usb3ep0quirk && xfer->actual_length >= 18 && + r->cbuf[7] == 9) { + r->cbuf[7] = 64; + } + /* + *If this is GET_DESCRIPTOR request for configuration descriptor, + * remove 'remote wakeup' flag from it to prevent idle power down + * in Windows guest + */ + if (s->suppress_remote_wake && + udev->setup_buf[0] == USB_DIR_IN && + udev->setup_buf[1] == USB_REQ_GET_DESCRIPTOR && + udev->setup_buf[3] == USB_DT_CONFIG && udev->setup_buf[2] == 0 && + xfer->actual_length > + offsetof(struct libusb_config_descriptor, bmAttributes) && + (conf->bmAttributes & USB_CFG_ATT_WAKEUP)) { + trace_usb_host_remote_wakeup_removed(s->bus_num, s->addr); + conf->bmAttributes &= ~USB_CFG_ATT_WAKEUP; + } + } + trace_usb_host_req_complete(s->bus_num, s->addr, r->p, + r->p->status, r->p->actual_length); + usb_generic_async_ctrl_complete(USB_DEVICE(s), r->p); + +out: + usb_host_req_free(r); + if (disconnect) { + usb_host_nodev(s); + } +} + +static void LIBUSB_CALL usb_host_req_complete_data(struct libusb_transfer *xfer) +{ + USBHostRequest *r = xfer->user_data; + USBHostDevice *s = r->host; + bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE); + + if (r->p == NULL) { + goto out; /* request was canceled */ + } + + r->p->status = status_map[xfer->status]; + if (r->in && xfer->actual_length) { + usb_packet_copy(r->p, r->buffer, xfer->actual_length); + } + trace_usb_host_req_complete(s->bus_num, s->addr, r->p, + r->p->status, r->p->actual_length); + if (usb_host_use_combining(r->p->ep)) { + usb_combined_input_packet_complete(USB_DEVICE(s), r->p); + } else { + usb_packet_complete(USB_DEVICE(s), r->p); + } + +out: + usb_host_req_free(r); + if (disconnect) { + usb_host_nodev(s); + } +} + +static void usb_host_req_abort(USBHostRequest *r) +{ + USBHostDevice *s = r->host; + bool inflight = (r->p && r->p->state == USB_PACKET_ASYNC); + + if (inflight) { + r->p->status = USB_RET_NODEV; + trace_usb_host_req_complete(s->bus_num, s->addr, r->p, + r->p->status, r->p->actual_length); + if (r->p->ep->nr == 0) { + usb_generic_async_ctrl_complete(USB_DEVICE(s), r->p); + } else { + usb_packet_complete(USB_DEVICE(s), r->p); + } + r->p = NULL; + + libusb_cancel_transfer(r->xfer); + } +} + +/* ------------------------------------------------------------------------ */ + +static void LIBUSB_CALL +usb_host_req_complete_iso(struct libusb_transfer *transfer) +{ + USBHostIsoXfer *xfer = transfer->user_data; + + if (!xfer) { + /* USBHostIsoXfer released while inflight */ + g_free(transfer->buffer); + libusb_free_transfer(transfer); + return; + } + + QTAILQ_REMOVE(&xfer->ring->inflight, xfer, next); + if (QTAILQ_EMPTY(&xfer->ring->inflight)) { + USBHostDevice *s = xfer->ring->host; + trace_usb_host_iso_stop(s->bus_num, s->addr, xfer->ring->ep->nr); + } + if (xfer->ring->ep->pid == USB_TOKEN_IN) { + QTAILQ_INSERT_TAIL(&xfer->ring->copy, xfer, next); + usb_wakeup(xfer->ring->ep, 0); + } else { + QTAILQ_INSERT_TAIL(&xfer->ring->unused, xfer, next); + } +} + +static USBHostIsoRing *usb_host_iso_alloc(USBHostDevice *s, USBEndpoint *ep) +{ + USBHostIsoRing *ring = g_new0(USBHostIsoRing, 1); + USBHostIsoXfer *xfer; + /* FIXME: check interval (for now assume one xfer per frame) */ + int packets = s->iso_urb_frames; + int i; + + ring->host = s; + ring->ep = ep; + QTAILQ_INIT(&ring->unused); + QTAILQ_INIT(&ring->inflight); + QTAILQ_INIT(&ring->copy); + QTAILQ_INSERT_TAIL(&s->isorings, ring, next); + + for (i = 0; i < s->iso_urb_count; i++) { + xfer = g_new0(USBHostIsoXfer, 1); + xfer->ring = ring; + xfer->xfer = libusb_alloc_transfer(packets); + xfer->xfer->dev_handle = s->dh; + xfer->xfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + + xfer->xfer->endpoint = ring->ep->nr; + if (ring->ep->pid == USB_TOKEN_IN) { + xfer->xfer->endpoint |= USB_DIR_IN; + } + xfer->xfer->callback = usb_host_req_complete_iso; + xfer->xfer->user_data = xfer; + + xfer->xfer->num_iso_packets = packets; + xfer->xfer->length = ring->ep->max_packet_size * packets; + xfer->xfer->buffer = g_malloc0(xfer->xfer->length); + + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + } + + return ring; +} + +static USBHostIsoRing *usb_host_iso_find(USBHostDevice *s, USBEndpoint *ep) +{ + USBHostIsoRing *ring; + + QTAILQ_FOREACH(ring, &s->isorings, next) { + if (ring->ep == ep) { + return ring; + } + } + return NULL; +} + +static void usb_host_iso_reset_xfer(USBHostIsoXfer *xfer) +{ + libusb_set_iso_packet_lengths(xfer->xfer, + xfer->ring->ep->max_packet_size); + xfer->packet = 0; + xfer->copy_complete = false; +} + +static void usb_host_iso_free_xfer(USBHostIsoXfer *xfer, bool inflight) +{ + if (inflight) { + xfer->xfer->user_data = NULL; + } else { + g_free(xfer->xfer->buffer); + libusb_free_transfer(xfer->xfer); + } + g_free(xfer); +} + +static void usb_host_iso_free(USBHostIsoRing *ring) +{ + USBHostIsoXfer *xfer; + + while ((xfer = QTAILQ_FIRST(&ring->inflight)) != NULL) { + QTAILQ_REMOVE(&ring->inflight, xfer, next); + usb_host_iso_free_xfer(xfer, true); + } + while ((xfer = QTAILQ_FIRST(&ring->unused)) != NULL) { + QTAILQ_REMOVE(&ring->unused, xfer, next); + usb_host_iso_free_xfer(xfer, false); + } + while ((xfer = QTAILQ_FIRST(&ring->copy)) != NULL) { + QTAILQ_REMOVE(&ring->copy, xfer, next); + usb_host_iso_free_xfer(xfer, false); + } + + QTAILQ_REMOVE(&ring->host->isorings, ring, next); + g_free(ring); +} + +static void usb_host_iso_free_all(USBHostDevice *s) +{ + USBHostIsoRing *ring; + + while ((ring = QTAILQ_FIRST(&s->isorings)) != NULL) { + usb_host_iso_free(ring); + } +} + +static bool usb_host_iso_data_copy(USBHostIsoXfer *xfer, USBPacket *p) +{ + unsigned int psize; + unsigned char *buf; + + buf = libusb_get_iso_packet_buffer_simple(xfer->xfer, xfer->packet); + if (p->pid == USB_TOKEN_OUT) { + psize = p->iov.size; + if (psize > xfer->ring->ep->max_packet_size) { + /* should not happen (guest bug) */ + psize = xfer->ring->ep->max_packet_size; + } + xfer->xfer->iso_packet_desc[xfer->packet].length = psize; + } else { + psize = xfer->xfer->iso_packet_desc[xfer->packet].actual_length; + if (psize > p->iov.size) { + /* should not happen (guest bug) */ + psize = p->iov.size; + } + } + usb_packet_copy(p, buf, psize); + xfer->packet++; + xfer->copy_complete = (xfer->packet == xfer->xfer->num_iso_packets); + return xfer->copy_complete; +} + +static void usb_host_iso_data_in(USBHostDevice *s, USBPacket *p) +{ + USBHostIsoRing *ring; + USBHostIsoXfer *xfer; + bool disconnect = false; + int rc; + + ring = usb_host_iso_find(s, p->ep); + if (ring == NULL) { + ring = usb_host_iso_alloc(s, p->ep); + } + + /* copy data to guest */ + xfer = QTAILQ_FIRST(&ring->copy); + if (xfer != NULL) { + if (usb_host_iso_data_copy(xfer, p)) { + QTAILQ_REMOVE(&ring->copy, xfer, next); + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + } + } + + /* submit empty bufs to host */ + while ((xfer = QTAILQ_FIRST(&ring->unused)) != NULL) { + QTAILQ_REMOVE(&ring->unused, xfer, next); + usb_host_iso_reset_xfer(xfer); + rc = libusb_submit_transfer(xfer->xfer); + if (rc != 0) { + usb_host_libusb_error("libusb_submit_transfer [iso]", rc); + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + disconnect = true; + } + break; + } + if (QTAILQ_EMPTY(&ring->inflight)) { + trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr); + } + QTAILQ_INSERT_TAIL(&ring->inflight, xfer, next); + } + + if (disconnect) { + usb_host_nodev(s); + } +} + +static void usb_host_iso_data_out(USBHostDevice *s, USBPacket *p) +{ + USBHostIsoRing *ring; + USBHostIsoXfer *xfer; + bool disconnect = false; + int rc, filled = 0; + + ring = usb_host_iso_find(s, p->ep); + if (ring == NULL) { + ring = usb_host_iso_alloc(s, p->ep); + } + + /* copy data from guest */ + xfer = QTAILQ_FIRST(&ring->copy); + while (xfer != NULL && xfer->copy_complete) { + filled++; + xfer = QTAILQ_NEXT(xfer, next); + } + if (xfer == NULL) { + xfer = QTAILQ_FIRST(&ring->unused); + if (xfer == NULL) { + trace_usb_host_iso_out_of_bufs(s->bus_num, s->addr, p->ep->nr); + return; + } + QTAILQ_REMOVE(&ring->unused, xfer, next); + usb_host_iso_reset_xfer(xfer); + QTAILQ_INSERT_TAIL(&ring->copy, xfer, next); + } + usb_host_iso_data_copy(xfer, p); + + if (QTAILQ_EMPTY(&ring->inflight)) { + /* wait until half of our buffers are filled + before kicking the iso out stream */ + if (filled*2 < s->iso_urb_count) { + return; + } + } + + /* submit filled bufs to host */ + while ((xfer = QTAILQ_FIRST(&ring->copy)) != NULL && + xfer->copy_complete) { + QTAILQ_REMOVE(&ring->copy, xfer, next); + rc = libusb_submit_transfer(xfer->xfer); + if (rc != 0) { + usb_host_libusb_error("libusb_submit_transfer [iso]", rc); + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + disconnect = true; + } + break; + } + if (QTAILQ_EMPTY(&ring->inflight)) { + trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr); + } + QTAILQ_INSERT_TAIL(&ring->inflight, xfer, next); + } + + if (disconnect) { + usb_host_nodev(s); + } +} + +/* ------------------------------------------------------------------------ */ + +static void usb_host_speed_compat(USBHostDevice *s) +{ + USBDevice *udev = USB_DEVICE(s); + struct libusb_config_descriptor *conf; + const struct libusb_interface_descriptor *intf; + const struct libusb_endpoint_descriptor *endp; +#ifdef HAVE_STREAMS + struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp; +#endif + bool compat_high = true; + bool compat_full = true; + uint8_t type; + int rc, c, i, a, e; + + for (c = 0;; c++) { + rc = libusb_get_config_descriptor(s->dev, c, &conf); + if (rc != 0) { + break; + } + for (i = 0; i < conf->bNumInterfaces; i++) { + for (a = 0; a < conf->interface[i].num_altsetting; a++) { + intf = &conf->interface[i].altsetting[a]; + + if (intf->bInterfaceClass == LIBUSB_CLASS_MASS_STORAGE && + intf->bInterfaceSubClass == 6) { /* SCSI */ + udev->flags |= (1 << USB_DEV_FLAG_IS_SCSI_STORAGE); + break; + } + + for (e = 0; e < intf->bNumEndpoints; e++) { + endp = &intf->endpoint[e]; + type = endp->bmAttributes & 0x3; + switch (type) { + case 0x01: /* ISO */ + compat_full = false; + compat_high = false; + break; + case 0x02: /* BULK */ +#ifdef HAVE_STREAMS + rc = libusb_get_ss_endpoint_companion_descriptor + (ctx, endp, &endp_ss_comp); + if (rc == LIBUSB_SUCCESS) { + int streams = endp_ss_comp->bmAttributes & 0x1f; + if (streams) { + compat_full = false; + compat_high = false; + } + libusb_free_ss_endpoint_companion_descriptor + (endp_ss_comp); + } +#endif + break; + case 0x03: /* INTERRUPT */ + if (endp->wMaxPacketSize > 64) { + compat_full = false; + } + if (endp->wMaxPacketSize > 1024) { + compat_high = false; + } + break; + } + } + } + } + libusb_free_config_descriptor(conf); + } + + udev->speedmask = (1 << udev->speed); + if (udev->speed == USB_SPEED_SUPER && compat_high) { + udev->speedmask |= USB_SPEED_MASK_HIGH; + } + if (udev->speed == USB_SPEED_SUPER && compat_full) { + udev->speedmask |= USB_SPEED_MASK_FULL; + } + if (udev->speed == USB_SPEED_HIGH && compat_full) { + udev->speedmask |= USB_SPEED_MASK_FULL; + } +} + +static void usb_host_ep_update(USBHostDevice *s) +{ + static const char *tname[] = { + [USB_ENDPOINT_XFER_CONTROL] = "control", + [USB_ENDPOINT_XFER_ISOC] = "isoc", + [USB_ENDPOINT_XFER_BULK] = "bulk", + [USB_ENDPOINT_XFER_INT] = "int", + }; + USBDevice *udev = USB_DEVICE(s); + struct libusb_config_descriptor *conf; + const struct libusb_interface_descriptor *intf; + const struct libusb_endpoint_descriptor *endp; +#ifdef HAVE_STREAMS + struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp; +#endif + uint8_t devep, type; + int pid, ep, alt; + int rc, i, e; + + usb_ep_reset(udev); + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + return; + } + trace_usb_host_parse_config(s->bus_num, s->addr, + conf->bConfigurationValue, true); + + for (i = 0; i < conf->bNumInterfaces; i++) { + /* + * The udev->altsetting array indexes alternate settings + * by the interface number. Get the 0th alternate setting + * first so that we can grab the interface number, and + * then correct the alternate setting value if necessary. + */ + intf = &conf->interface[i].altsetting[0]; + alt = udev->altsetting[intf->bInterfaceNumber]; + + if (alt != 0) { + assert(alt < conf->interface[i].num_altsetting); + intf = &conf->interface[i].altsetting[alt]; + } + + trace_usb_host_parse_interface(s->bus_num, s->addr, + intf->bInterfaceNumber, + intf->bAlternateSetting, true); + for (e = 0; e < intf->bNumEndpoints; e++) { + endp = &intf->endpoint[e]; + + devep = endp->bEndpointAddress; + pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT; + ep = devep & 0xf; + type = endp->bmAttributes & 0x3; + + if (ep == 0) { + trace_usb_host_parse_error(s->bus_num, s->addr, + "invalid endpoint address"); + return; + } + if (usb_ep_get_type(udev, pid, ep) != USB_ENDPOINT_XFER_INVALID) { + trace_usb_host_parse_error(s->bus_num, s->addr, + "duplicate endpoint address"); + return; + } + + trace_usb_host_parse_endpoint(s->bus_num, s->addr, ep, + (devep & USB_DIR_IN) ? "in" : "out", + tname[type], true); + usb_ep_set_max_packet_size(udev, pid, ep, + endp->wMaxPacketSize); + usb_ep_set_type(udev, pid, ep, type); + usb_ep_set_ifnum(udev, pid, ep, i); + usb_ep_set_halted(udev, pid, ep, 0); +#ifdef HAVE_STREAMS + if (type == LIBUSB_TRANSFER_TYPE_BULK && + libusb_get_ss_endpoint_companion_descriptor(ctx, endp, + &endp_ss_comp) == LIBUSB_SUCCESS) { + usb_ep_set_max_streams(udev, pid, ep, + endp_ss_comp->bmAttributes); + libusb_free_ss_endpoint_companion_descriptor(endp_ss_comp); + } +#endif + } + } + + libusb_free_config_descriptor(conf); +} + +static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd) +{ + USBDevice *udev = USB_DEVICE(s); + int libusb_speed; + int bus_num = 0; + int addr = 0; + int rc; + Error *local_err = NULL; + + if (s->bh_postld_pending) { + return -1; + } + if (s->dh != NULL) { + goto fail; + } + + if (dev) { + bus_num = libusb_get_bus_number(dev); + addr = libusb_get_device_address(dev); + trace_usb_host_open_started(bus_num, addr); + + rc = libusb_open(dev, &s->dh); + if (rc != 0) { + goto fail; + } + } else { +#if LIBUSB_API_VERSION >= 0x01000107 && !defined(CONFIG_WIN32) + trace_usb_host_open_hostfd(hostfd); + + rc = libusb_wrap_sys_device(ctx, hostfd, &s->dh); + if (rc != 0) { + goto fail; + } + s->hostfd = hostfd; + dev = libusb_get_device(s->dh); + bus_num = libusb_get_bus_number(dev); + addr = libusb_get_device_address(dev); +#else + g_assert_not_reached(); +#endif + } + + s->dev = dev; + s->bus_num = bus_num; + s->addr = addr; + + usb_host_detach_kernel(s); + + libusb_get_device_descriptor(dev, &s->ddesc); + usb_host_get_port(s->dev, s->port, sizeof(s->port)); + + usb_ep_init(udev); + usb_host_ep_update(s); + + libusb_speed = libusb_get_device_speed(dev); +#if LIBUSB_API_VERSION >= 0x01000107 && defined(CONFIG_LINUX) && \ + defined(USBDEVFS_GET_SPEED) + if (hostfd && libusb_speed == 0) { + /* + * Workaround libusb bug: libusb_get_device_speed() does not + * work for libusb_wrap_sys_device() devices in v1.0.23. + * + * Speeds are defined in linux/usb/ch9.h, file not included + * due to name conflicts. + */ + int rc = ioctl(hostfd, USBDEVFS_GET_SPEED, NULL); + switch (rc) { + case 1: /* low */ + libusb_speed = LIBUSB_SPEED_LOW; + break; + case 2: /* full */ + libusb_speed = LIBUSB_SPEED_FULL; + break; + case 3: /* high */ + case 4: /* wireless */ + libusb_speed = LIBUSB_SPEED_HIGH; + break; + case 5: /* super */ + libusb_speed = LIBUSB_SPEED_SUPER; + break; + case 6: /* super plus */ +#ifdef HAVE_SUPER_PLUS + libusb_speed = LIBUSB_SPEED_SUPER_PLUS; +#else + libusb_speed = LIBUSB_SPEED_SUPER; +#endif + break; + } + } +#endif + udev->speed = speed_map[libusb_speed]; + usb_host_speed_compat(s); + + if (s->ddesc.iProduct) { + libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct, + (unsigned char *)udev->product_desc, + sizeof(udev->product_desc)); + } else { + snprintf(udev->product_desc, sizeof(udev->product_desc), + "host:%d.%d", bus_num, addr); + } + + usb_device_attach(udev, &local_err); + if (local_err) { + error_report_err(local_err); + goto fail; + } + + trace_usb_host_open_success(bus_num, addr); + return 0; + +fail: + trace_usb_host_open_failure(bus_num, addr); + if (s->dh != NULL) { + usb_host_release_interfaces(s); + libusb_reset_device(s->dh); + usb_host_attach_kernel(s); + libusb_close(s->dh); + s->dh = NULL; + s->dev = NULL; + } + return -1; +} + +static void usb_host_abort_xfers(USBHostDevice *s) +{ + USBHostRequest *r, *rtmp; + int limit = 100; + + QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) { + usb_host_req_abort(r); + } + + while (QTAILQ_FIRST(&s->requests) != NULL) { + struct timeval tv; + memset(&tv, 0, sizeof(tv)); + tv.tv_usec = 2500; + libusb_handle_events_timeout(ctx, &tv); + if (--limit == 0) { + /* + * Don't wait forever for libusb calling the complete + * callback (which will unlink and free the request). + * + * Leaking memory here, to make sure libusb will not + * access memory which we have released already. + */ + QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) { + QTAILQ_REMOVE(&s->requests, r, next); + } + return; + } + } +} + +static int usb_host_close(USBHostDevice *s) +{ + USBDevice *udev = USB_DEVICE(s); + + if (s->dh == NULL) { + return -1; + } + + trace_usb_host_close(s->bus_num, s->addr); + + usb_host_abort_xfers(s); + usb_host_iso_free_all(s); + + if (udev->attached) { + usb_device_detach(udev); + } + + usb_host_release_interfaces(s); + libusb_reset_device(s->dh); + usb_host_attach_kernel(s); + libusb_close(s->dh); + s->dh = NULL; + s->dev = NULL; + + if (s->hostfd != -1) { + close(s->hostfd); + s->hostfd = -1; + } + + usb_host_auto_check(NULL); + return 0; +} + +static void usb_host_nodev_bh(void *opaque) +{ + USBHostDevice *s = opaque; + usb_host_close(s); +} + +static void usb_host_nodev(USBHostDevice *s) +{ + if (!s->bh_nodev) { + s->bh_nodev = qemu_bh_new(usb_host_nodev_bh, s); + } + qemu_bh_schedule(s->bh_nodev); +} + +static void usb_host_exit_notifier(struct Notifier *n, void *data) +{ + USBHostDevice *s = container_of(n, USBHostDevice, exit); + + if (s->dh) { + usb_host_abort_xfers(s); + usb_host_release_interfaces(s); + libusb_reset_device(s->dh); + usb_host_attach_kernel(s); + libusb_close(s->dh); + } +} + +static libusb_device *usb_host_find_ref(int bus, int addr) +{ + libusb_device **devs = NULL; + libusb_device *ret = NULL; + int i, n; + + n = libusb_get_device_list(ctx, &devs); + for (i = 0; i < n; i++) { + if (libusb_get_bus_number(devs[i]) == bus && + libusb_get_device_address(devs[i]) == addr) { + ret = libusb_ref_device(devs[i]); + break; + } + } + libusb_free_device_list(devs, 1); + return ret; +} + +static void usb_host_realize(USBDevice *udev, Error **errp) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + libusb_device *ldev; + int rc; + + if (usb_host_init() != 0) { + error_setg(errp, "failed to init libusb"); + return; + } + if (s->match.vendor_id > 0xffff) { + error_setg(errp, "vendorid out of range"); + return; + } + if (s->match.product_id > 0xffff) { + error_setg(errp, "productid out of range"); + return; + } + if (s->match.addr > 127) { + error_setg(errp, "hostaddr out of range"); + return; + } + + loglevel = s->loglevel; + udev->flags |= (1 << USB_DEV_FLAG_IS_HOST); + udev->auto_attach = 0; + QTAILQ_INIT(&s->requests); + QTAILQ_INIT(&s->isorings); + s->hostfd = -1; + +#if LIBUSB_API_VERSION >= 0x01000107 && !defined(CONFIG_WIN32) + if (s->hostdevice) { + int fd; + s->needs_autoscan = false; + fd = qemu_open_old(s->hostdevice, O_RDWR); + if (fd < 0) { + error_setg_errno(errp, errno, "failed to open %s", s->hostdevice); + return; + } + rc = usb_host_open(s, NULL, fd); + if (rc < 0) { + error_setg(errp, "failed to open host usb device %s", s->hostdevice); + return; + } + } else +#endif + if (s->match.addr && s->match.bus_num && + !s->match.vendor_id && + !s->match.product_id && + !s->match.port) { + s->needs_autoscan = false; + ldev = usb_host_find_ref(s->match.bus_num, + s->match.addr); + if (!ldev) { + error_setg(errp, "failed to find host usb device %d:%d", + s->match.bus_num, s->match.addr); + return; + } + rc = usb_host_open(s, ldev, 0); + libusb_unref_device(ldev); + if (rc < 0) { + error_setg(errp, "failed to open host usb device %d:%d", + s->match.bus_num, s->match.addr); + return; + } + } else { + s->needs_autoscan = true; + QTAILQ_INSERT_TAIL(&hostdevs, s, next); + usb_host_auto_check(NULL); + } + + s->exit.notify = usb_host_exit_notifier; + qemu_add_exit_notifier(&s->exit); +} + +static void usb_host_instance_init(Object *obj) +{ + USBDevice *udev = USB_DEVICE(obj); + USBHostDevice *s = USB_HOST_DEVICE(udev); + + device_add_bootindex_property(obj, &s->bootindex, + "bootindex", NULL, + &udev->qdev); +} + +static void usb_host_unrealize(USBDevice *udev) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + + qemu_remove_exit_notifier(&s->exit); + if (s->needs_autoscan) { + QTAILQ_REMOVE(&hostdevs, s, next); + } + usb_host_close(s); +} + +static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + USBHostRequest *r; + + if (p->combined) { + usb_combined_packet_cancel(udev, p); + return; + } + + trace_usb_host_req_canceled(s->bus_num, s->addr, p); + + r = usb_host_req_find(s, p); + if (r && r->p) { + r->p = NULL; /* mark as dead */ + libusb_cancel_transfer(r->xfer); + } +} + +static void usb_host_detach_kernel(USBHostDevice *s) +{ + struct libusb_config_descriptor *conf; + int rc, i; + + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + return; + } + for (i = 0; i < USB_MAX_INTERFACES; i++) { + rc = libusb_kernel_driver_active(s->dh, i); + usb_host_libusb_error("libusb_kernel_driver_active", rc); + if (rc != 1) { + if (rc == 0) { + s->ifs[i].detached = true; + } + continue; + } + trace_usb_host_detach_kernel(s->bus_num, s->addr, i); + rc = libusb_detach_kernel_driver(s->dh, i); + usb_host_libusb_error("libusb_detach_kernel_driver", rc); + s->ifs[i].detached = true; + } + libusb_free_config_descriptor(conf); +} + +static void usb_host_attach_kernel(USBHostDevice *s) +{ + struct libusb_config_descriptor *conf; + int rc, i; + + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + return; + } + for (i = 0; i < USB_MAX_INTERFACES; i++) { + if (!s->ifs[i].detached) { + continue; + } + trace_usb_host_attach_kernel(s->bus_num, s->addr, i); + libusb_attach_kernel_driver(s->dh, i); + s->ifs[i].detached = false; + } + libusb_free_config_descriptor(conf); +} + +static int usb_host_claim_interfaces(USBHostDevice *s, int configuration) +{ + USBDevice *udev = USB_DEVICE(s); + struct libusb_config_descriptor *conf; + int rc, i, claimed; + + for (i = 0; i < USB_MAX_INTERFACES; i++) { + udev->altsetting[i] = 0; + } + udev->ninterfaces = 0; + udev->configuration = 0; + + usb_host_detach_kernel(s); + + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + if (rc == LIBUSB_ERROR_NOT_FOUND) { + /* address state - ignore */ + return USB_RET_SUCCESS; + } + return USB_RET_STALL; + } + + claimed = 0; + for (i = 0; i < USB_MAX_INTERFACES; i++) { + trace_usb_host_claim_interface(s->bus_num, s->addr, configuration, i); + rc = libusb_claim_interface(s->dh, i); + if (rc == 0) { + s->ifs[i].claimed = true; + if (++claimed == conf->bNumInterfaces) { + break; + } + } + } + if (claimed != conf->bNumInterfaces) { + return USB_RET_STALL; + } + + udev->ninterfaces = conf->bNumInterfaces; + udev->configuration = configuration; + + libusb_free_config_descriptor(conf); + return USB_RET_SUCCESS; +} + +static void usb_host_release_interfaces(USBHostDevice *s) +{ + int i, rc; + + for (i = 0; i < USB_MAX_INTERFACES; i++) { + if (!s->ifs[i].claimed) { + continue; + } + trace_usb_host_release_interface(s->bus_num, s->addr, i); + rc = libusb_release_interface(s->dh, i); + usb_host_libusb_error("libusb_release_interface", rc); + s->ifs[i].claimed = false; + } +} + +static void usb_host_set_address(USBHostDevice *s, int addr) +{ + USBDevice *udev = USB_DEVICE(s); + + trace_usb_host_set_address(s->bus_num, s->addr, addr); + udev->addr = addr; +} + +static void usb_host_set_config(USBHostDevice *s, int config, USBPacket *p) +{ + int rc = 0; + + trace_usb_host_set_config(s->bus_num, s->addr, config); + + usb_host_release_interfaces(s); + if (s->ddesc.bNumConfigurations != 1) { + rc = libusb_set_configuration(s->dh, config); + if (rc != 0) { + usb_host_libusb_error("libusb_set_configuration", rc); + p->status = USB_RET_STALL; + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + } + p->status = usb_host_claim_interfaces(s, config); + if (p->status != USB_RET_SUCCESS) { + return; + } + usb_host_ep_update(s); +} + +static void usb_host_set_interface(USBHostDevice *s, int iface, int alt, + USBPacket *p) +{ + USBDevice *udev = USB_DEVICE(s); + int rc; + + trace_usb_host_set_interface(s->bus_num, s->addr, iface, alt); + + usb_host_iso_free_all(s); + + if (iface >= USB_MAX_INTERFACES) { + p->status = USB_RET_STALL; + return; + } + + rc = libusb_set_interface_alt_setting(s->dh, iface, alt); + if (rc != 0) { + usb_host_libusb_error("libusb_set_interface_alt_setting", rc); + p->status = USB_RET_STALL; + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + + udev->altsetting[iface] = alt; + usb_host_ep_update(s); +} + +static void usb_host_handle_control(USBDevice *udev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + USBHostRequest *r; + int rc; + + trace_usb_host_req_control(s->bus_num, s->addr, p, request, value, index); + + if (s->dh == NULL) { + p->status = USB_RET_NODEV; + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + + switch (request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + usb_host_set_address(s, value); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + usb_host_set_config(s, value & 0xff, p); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + usb_host_set_interface(s, index, value, p); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0) { /* clear halt */ + int pid = (index & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT; + libusb_clear_halt(s->dh, index); + usb_ep_set_halted(udev, pid, index & 0x0f, 0); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + } + + r = usb_host_req_alloc(s, p, (request >> 8) & USB_DIR_IN, length + 8); + r->cbuf = data; + r->clen = length; + memcpy(r->buffer, udev->setup_buf, 8); + if (!r->in) { + memcpy(r->buffer + 8, r->cbuf, r->clen); + } + + /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices + * to work redirected to a not superspeed capable hcd */ + if ((udev->speedmask & USB_SPEED_MASK_SUPER) && + !(udev->port->speedmask & USB_SPEED_MASK_SUPER) && + request == 0x8006 && value == 0x100 && index == 0) { + r->usb3ep0quirk = true; + } + + libusb_fill_control_transfer(r->xfer, s->dh, r->buffer, + usb_host_req_complete_ctrl, r, + CONTROL_TIMEOUT); + rc = libusb_submit_transfer(r->xfer); + if (rc != 0) { + p->status = USB_RET_NODEV; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + + p->status = USB_RET_ASYNC; +} + +static void usb_host_handle_data(USBDevice *udev, USBPacket *p) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + USBHostRequest *r; + size_t size; + int ep, rc; + + if (usb_host_use_combining(p->ep) && p->state == USB_PACKET_SETUP) { + p->status = USB_RET_ADD_TO_QUEUE; + return; + } + + trace_usb_host_req_data(s->bus_num, s->addr, p, + p->pid == USB_TOKEN_IN, + p->ep->nr, p->iov.size); + + if (s->dh == NULL) { + p->status = USB_RET_NODEV; + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + if (p->ep->halted) { + p->status = USB_RET_STALL; + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + + switch (usb_ep_get_type(udev, p->pid, p->ep->nr)) { + case USB_ENDPOINT_XFER_BULK: + size = usb_packet_size(p); + r = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN, size); + if (!r->in) { + usb_packet_copy(p, r->buffer, size); + } + ep = p->ep->nr | (r->in ? USB_DIR_IN : 0); + if (p->stream) { +#ifdef HAVE_STREAMS + libusb_fill_bulk_stream_transfer(r->xfer, s->dh, ep, p->stream, + r->buffer, size, + usb_host_req_complete_data, r, + BULK_TIMEOUT); +#else + usb_host_req_free(r); + p->status = USB_RET_STALL; + return; +#endif + } else { + libusb_fill_bulk_transfer(r->xfer, s->dh, ep, + r->buffer, size, + usb_host_req_complete_data, r, + BULK_TIMEOUT); + } + break; + case USB_ENDPOINT_XFER_INT: + r = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN, p->iov.size); + if (!r->in) { + usb_packet_copy(p, r->buffer, p->iov.size); + } + ep = p->ep->nr | (r->in ? USB_DIR_IN : 0); + libusb_fill_interrupt_transfer(r->xfer, s->dh, ep, + r->buffer, p->iov.size, + usb_host_req_complete_data, r, + INTR_TIMEOUT); + break; + case USB_ENDPOINT_XFER_ISOC: + if (p->pid == USB_TOKEN_IN) { + usb_host_iso_data_in(s, p); + } else { + usb_host_iso_data_out(s, p); + } + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + return; + default: + p->status = USB_RET_STALL; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + return; + } + + rc = libusb_submit_transfer(r->xfer); + if (rc != 0) { + p->status = USB_RET_NODEV; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + + p->status = USB_RET_ASYNC; +} + +static void usb_host_flush_ep_queue(USBDevice *dev, USBEndpoint *ep) +{ + if (usb_host_use_combining(ep)) { + usb_ep_combine_input_packets(ep); + } +} + +static void usb_host_handle_reset(USBDevice *udev) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + int rc; + + if (!s->allow_one_guest_reset && !s->allow_all_guest_resets) { + return; + } + if (!s->allow_all_guest_resets && udev->addr == 0) { + return; + } + + trace_usb_host_reset(s->bus_num, s->addr); + + rc = libusb_reset_device(s->dh); + if (rc != 0) { + usb_host_nodev(s); + } +} + +static int usb_host_alloc_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps, int streams) +{ +#ifdef HAVE_STREAMS + USBHostDevice *s = USB_HOST_DEVICE(udev); + unsigned char endpoints[30]; + int i, rc; + + for (i = 0; i < nr_eps; i++) { + endpoints[i] = eps[i]->nr; + if (eps[i]->pid == USB_TOKEN_IN) { + endpoints[i] |= 0x80; + } + } + rc = libusb_alloc_streams(s->dh, streams, endpoints, nr_eps); + if (rc < 0) { + usb_host_libusb_error("libusb_alloc_streams", rc); + } else if (rc != streams) { + error_report("libusb_alloc_streams: got less streams " + "then requested %d < %d", rc, streams); + } + + return (rc == streams) ? 0 : -1; +#else + error_report("libusb_alloc_streams: error not implemented"); + return -1; +#endif +} + +static void usb_host_free_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps) +{ +#ifdef HAVE_STREAMS + USBHostDevice *s = USB_HOST_DEVICE(udev); + unsigned char endpoints[30]; + int i; + + for (i = 0; i < nr_eps; i++) { + endpoints[i] = eps[i]->nr; + if (eps[i]->pid == USB_TOKEN_IN) { + endpoints[i] |= 0x80; + } + } + libusb_free_streams(s->dh, endpoints, nr_eps); +#endif +} + +/* + * This is *NOT* about restoring state. We have absolutely no idea + * what state the host device is in at the moment and whenever it is + * still present in the first place. Attempting to continue where we + * left off is impossible. + * + * What we are going to do here is emulate a surprise removal of + * the usb device passed through, then kick host scan so the device + * will get re-attached (and re-initialized by the guest) in case it + * is still present. + * + * As the device removal will change the state of other devices (usb + * host controller, most likely interrupt controller too) we have to + * wait with it until *all* vmstate is loaded. Thus post_load just + * kicks a bottom half which then does the actual work. + */ +static void usb_host_post_load_bh(void *opaque) +{ + USBHostDevice *dev = opaque; + USBDevice *udev = USB_DEVICE(dev); + + if (dev->dh != NULL) { + usb_host_close(dev); + } + if (udev->attached) { + usb_device_detach(udev); + } + dev->bh_postld_pending = false; + usb_host_auto_check(NULL); +} + +static int usb_host_post_load(void *opaque, int version_id) +{ + USBHostDevice *dev = opaque; + + if (!dev->bh_postld) { + dev->bh_postld = qemu_bh_new(usb_host_post_load_bh, dev); + } + qemu_bh_schedule(dev->bh_postld); + dev->bh_postld_pending = true; + return 0; +} + +static const VMStateDescription vmstate_usb_host = { + .name = "usb-host", + .version_id = 1, + .minimum_version_id = 1, + .post_load = usb_host_post_load, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(parent_obj, USBHostDevice), + VMSTATE_END_OF_LIST() + } +}; + +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_UINT32("vendorid", USBHostDevice, match.vendor_id, 0), + DEFINE_PROP_UINT32("productid", USBHostDevice, match.product_id, 0), +#if LIBUSB_API_VERSION >= 0x01000107 + DEFINE_PROP_STRING("hostdevice", USBHostDevice, hostdevice), +#endif + DEFINE_PROP_UINT32("isobufs", USBHostDevice, iso_urb_count, 4), + DEFINE_PROP_UINT32("isobsize", USBHostDevice, iso_urb_frames, 32), + DEFINE_PROP_BOOL("guest-reset", USBHostDevice, + allow_one_guest_reset, true), + DEFINE_PROP_BOOL("guest-resets-all", USBHostDevice, + allow_all_guest_resets, false), + DEFINE_PROP_UINT32("loglevel", USBHostDevice, loglevel, + LIBUSB_LOG_LEVEL_WARNING), + DEFINE_PROP_BIT("pipeline", USBHostDevice, options, + USB_HOST_OPT_PIPELINE, true), + DEFINE_PROP_BOOL("suppress-remote-wake", USBHostDevice, + suppress_remote_wake, true), + 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->realize = usb_host_realize; + uc->product_desc = "USB Host Device"; + uc->cancel_packet = usb_host_cancel_packet; + uc->handle_data = usb_host_handle_data; + uc->handle_control = usb_host_handle_control; + uc->handle_reset = usb_host_handle_reset; + uc->unrealize = usb_host_unrealize; + uc->flush_ep_queue = usb_host_flush_ep_queue; + uc->alloc_streams = usb_host_alloc_streams; + uc->free_streams = usb_host_free_streams; + dc->vmsd = &vmstate_usb_host; + device_class_set_props(dc, usb_host_dev_properties); + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); +} + +static TypeInfo usb_host_dev_info = { + .name = TYPE_USB_HOST_DEVICE, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHostDevice), + .class_init = usb_host_class_initfn, + .instance_init = usb_host_instance_init, +}; +module_obj(TYPE_USB_HOST_DEVICE); + +static void usb_host_register_types(void) +{ + type_register_static(&usb_host_dev_info); + monitor_register_hmp("usbhost", true, hmp_info_usbhost); +} + +type_init(usb_host_register_types) + +/* ------------------------------------------------------------------------ */ + +static QEMUTimer *usb_auto_timer; +static VMChangeStateEntry *usb_vmstate; + +static void usb_host_vm_state(void *unused, bool running, RunState state) +{ + if (running) { + usb_host_auto_check(unused); + } +} + +static void usb_host_auto_check(void *unused) +{ + struct USBHostDevice *s; + struct USBAutoFilter *f; + libusb_device **devs = NULL; + struct libusb_device_descriptor ddesc; + int unconnected = 0; + int i, n; + + if (usb_host_init() != 0) { + return; + } + + if (runstate_is_running()) { + n = libusb_get_device_list(ctx, &devs); + for (i = 0; i < n; i++) { + if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) { + continue; + } + if (ddesc.bDeviceClass == LIBUSB_CLASS_HUB) { + continue; + } + QTAILQ_FOREACH(s, &hostdevs, next) { + f = &s->match; + if (f->bus_num > 0 && + f->bus_num != libusb_get_bus_number(devs[i])) { + continue; + } + if (f->addr > 0 && + f->addr != libusb_get_device_address(devs[i])) { + continue; + } + if (f->port != NULL) { + char port[16] = "-"; + usb_host_get_port(devs[i], port, sizeof(port)); + if (strcmp(f->port, port) != 0) { + continue; + } + } + if (f->vendor_id > 0 && + f->vendor_id != ddesc.idVendor) { + continue; + } + if (f->product_id > 0 && + f->product_id != ddesc.idProduct) { + continue; + } + + /* We got a match */ + s->seen++; + if (s->errcount >= 3) { + continue; + } + if (s->dh != NULL) { + continue; + } + if (usb_host_open(s, devs[i], 0) < 0) { + s->errcount++; + continue; + } + break; + } + } + libusb_free_device_list(devs, 1); + + QTAILQ_FOREACH(s, &hostdevs, next) { + if (s->dh == NULL) { + unconnected++; + } + if (s->seen == 0) { + if (s->dh) { + usb_host_close(s); + } + s->errcount = 0; + } + s->seen = 0; + } + +#if 0 + if (unconnected == 0) { + /* nothing to watch */ + if (usb_auto_timer) { + timer_del(usb_auto_timer); + trace_usb_host_auto_scan_disabled(); + } + return; + } +#endif + } + + if (!usb_vmstate) { + usb_vmstate = qemu_add_vm_change_state_handler(usb_host_vm_state, NULL); + } + if (!usb_auto_timer) { + usb_auto_timer = timer_new_ms(QEMU_CLOCK_REALTIME, usb_host_auto_check, NULL); + if (!usb_auto_timer) { + return; + } + trace_usb_host_auto_scan_enabled(); + } + timer_mod(usb_auto_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000); +} + +void hmp_info_usbhost(Monitor *mon, const QDict *qdict) +{ + libusb_device **devs = NULL; + struct libusb_device_descriptor ddesc; + char port[16]; + int i, n; + + if (usb_host_init() != 0) { + return; + } + + n = libusb_get_device_list(ctx, &devs); + for (i = 0; i < n; i++) { + if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) { + continue; + } + if (ddesc.bDeviceClass == LIBUSB_CLASS_HUB) { + continue; + } + usb_host_get_port(devs[i], port, sizeof(port)); + monitor_printf(mon, " Bus %d, Addr %d, Port %s, Speed %s Mb/s\n", + libusb_get_bus_number(devs[i]), + libusb_get_device_address(devs[i]), + port, + speed_name[libusb_get_device_speed(devs[i])]); + monitor_printf(mon, " Class %02x:", ddesc.bDeviceClass); + monitor_printf(mon, " USB device %04x:%04x", + ddesc.idVendor, ddesc.idProduct); + if (ddesc.iProduct) { + libusb_device_handle *handle; + if (libusb_open(devs[i], &handle) == 0) { + unsigned char name[64] = ""; + libusb_get_string_descriptor_ascii(handle, + ddesc.iProduct, + name, sizeof(name)); + libusb_close(handle); + monitor_printf(mon, ", %s", name); + } + } + monitor_printf(mon, "\n"); + } + libusb_free_device_list(devs, 1); +} diff --git a/hw/usb/host.h b/hw/usb/host.h new file mode 100644 index 000000000..048ff3b48 --- /dev/null +++ b/hw/usb/host.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef QEMU_USB_HOST_H +#define QEMU_USB_HOST_H + +struct USBAutoFilter { + uint32_t bus_num; + uint32_t addr; + char *port; + uint32_t vendor_id; + uint32_t product_id; +}; + +#endif /* QEMU_USB_HOST_H */ diff --git a/hw/usb/imx-usb-phy.c b/hw/usb/imx-usb-phy.c new file mode 100644 index 000000000..5d7a549e3 --- /dev/null +++ b/hw/usb/imx-usb-phy.c @@ -0,0 +1,224 @@ +/* + * i.MX USB PHY + * + * Copyright (c) 2020 Guenter Roeck <linux@roeck-us.net> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * We need to implement basic reset control in the PHY control register. + * For everything else, it is sufficient to set whatever is written. + */ + +#include "qemu/osdep.h" +#include "hw/usb/imx-usb-phy.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +static const VMStateDescription vmstate_imx_usbphy = { + .name = TYPE_IMX_USBPHY, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(usbphy, IMXUSBPHYState, USBPHY_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx_usbphy_softreset(IMXUSBPHYState *s) +{ + s->usbphy[USBPHY_PWD] = 0x001e1c00; + s->usbphy[USBPHY_TX] = 0x10060607; + s->usbphy[USBPHY_RX] = 0x00000000; + s->usbphy[USBPHY_CTRL] = 0xc0200000; +} + +static void imx_usbphy_reset(DeviceState *dev) +{ + IMXUSBPHYState *s = IMX_USBPHY(dev); + + s->usbphy[USBPHY_STATUS] = 0x00000000; + s->usbphy[USBPHY_DEBUG] = 0x7f180000; + s->usbphy[USBPHY_DEBUG0_STATUS] = 0x00000000; + s->usbphy[USBPHY_DEBUG1] = 0x00001000; + s->usbphy[USBPHY_VERSION] = 0x04020000; + + imx_usbphy_softreset(s); +} + +static uint64_t imx_usbphy_read(void *opaque, hwaddr offset, unsigned size) +{ + IMXUSBPHYState *s = (IMXUSBPHYState *)opaque; + uint32_t index = offset >> 2; + uint32_t value; + + switch (index) { + case USBPHY_PWD_SET: + case USBPHY_TX_SET: + case USBPHY_RX_SET: + case USBPHY_CTRL_SET: + case USBPHY_DEBUG_SET: + case USBPHY_DEBUG1_SET: + /* + * All REG_NAME_SET register access are in fact targeting the + * REG_NAME register. + */ + value = s->usbphy[index - 1]; + break; + case USBPHY_PWD_CLR: + case USBPHY_TX_CLR: + case USBPHY_RX_CLR: + case USBPHY_CTRL_CLR: + case USBPHY_DEBUG_CLR: + case USBPHY_DEBUG1_CLR: + /* + * All REG_NAME_CLR register access are in fact targeting the + * REG_NAME register. + */ + value = s->usbphy[index - 2]; + break; + case USBPHY_PWD_TOG: + case USBPHY_TX_TOG: + case USBPHY_RX_TOG: + case USBPHY_CTRL_TOG: + case USBPHY_DEBUG_TOG: + case USBPHY_DEBUG1_TOG: + /* + * All REG_NAME_TOG register access are in fact targeting the + * REG_NAME register. + */ + value = s->usbphy[index - 3]; + break; + default: + value = s->usbphy[index]; + break; + } + return (uint64_t)value; +} + +static void imx_usbphy_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + IMXUSBPHYState *s = (IMXUSBPHYState *)opaque; + uint32_t index = offset >> 2; + + switch (index) { + case USBPHY_CTRL: + s->usbphy[index] = value; + if (value & USBPHY_CTRL_SFTRST) { + imx_usbphy_softreset(s); + } + break; + case USBPHY_PWD: + case USBPHY_TX: + case USBPHY_RX: + case USBPHY_STATUS: + case USBPHY_DEBUG: + case USBPHY_DEBUG1: + s->usbphy[index] = value; + break; + case USBPHY_CTRL_SET: + s->usbphy[index - 1] |= value; + if (value & USBPHY_CTRL_SFTRST) { + imx_usbphy_softreset(s); + } + break; + case USBPHY_PWD_SET: + case USBPHY_TX_SET: + case USBPHY_RX_SET: + case USBPHY_DEBUG_SET: + case USBPHY_DEBUG1_SET: + /* + * All REG_NAME_SET register access are in fact targeting the + * REG_NAME register. So we change the value of the REG_NAME + * register, setting bits passed in the value. + */ + s->usbphy[index - 1] |= value; + break; + case USBPHY_PWD_CLR: + case USBPHY_TX_CLR: + case USBPHY_RX_CLR: + case USBPHY_CTRL_CLR: + case USBPHY_DEBUG_CLR: + case USBPHY_DEBUG1_CLR: + /* + * All REG_NAME_CLR register access are in fact targeting the + * REG_NAME register. So we change the value of the REG_NAME + * register, unsetting bits passed in the value. + */ + s->usbphy[index - 2] &= ~value; + break; + case USBPHY_CTRL_TOG: + s->usbphy[index - 3] ^= value; + if ((value & USBPHY_CTRL_SFTRST) && + (s->usbphy[index - 3] & USBPHY_CTRL_SFTRST)) { + imx_usbphy_softreset(s); + } + break; + case USBPHY_PWD_TOG: + case USBPHY_TX_TOG: + case USBPHY_RX_TOG: + case USBPHY_DEBUG_TOG: + case USBPHY_DEBUG1_TOG: + /* + * All REG_NAME_TOG register access are in fact targeting the + * REG_NAME register. So we change the value of the REG_NAME + * register, toggling bits passed in the value. + */ + s->usbphy[index - 3] ^= value; + break; + default: + /* Other registers are read-only */ + break; + } +} + +static const struct MemoryRegionOps imx_usbphy_ops = { + .read = imx_usbphy_read, + .write = imx_usbphy_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void imx_usbphy_realize(DeviceState *dev, Error **errp) +{ + IMXUSBPHYState *s = IMX_USBPHY(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &imx_usbphy_ops, s, + "imx-usbphy", 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); +} + +static void imx_usbphy_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = imx_usbphy_reset; + dc->vmsd = &vmstate_imx_usbphy; + dc->desc = "i.MX USB PHY Module"; + dc->realize = imx_usbphy_realize; +} + +static const TypeInfo imx_usbphy_info = { + .name = TYPE_IMX_USBPHY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMXUSBPHYState), + .class_init = imx_usbphy_class_init, +}; + +static void imx_usbphy_register_types(void) +{ + type_register_static(&imx_usbphy_info); +} + +type_init(imx_usbphy_register_types) diff --git a/hw/usb/libhw.c b/hw/usb/libhw.c new file mode 100644 index 000000000..9c33a1640 --- /dev/null +++ b/hw/usb/libhw.c @@ -0,0 +1,69 @@ +/* + * 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/osdep.h" +#include "hw/usb.h" +#include "sysemu/dma.h" + +int usb_packet_map(USBPacket *p, QEMUSGList *sgl) +{ + DMADirection dir = (p->pid == USB_TOKEN_IN) ? + DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE; + void *mem; + int i; + + for (i = 0; i < sgl->nsg; i++) { + dma_addr_t base = sgl->sg[i].base; + dma_addr_t len = sgl->sg[i].len; + + while (len) { + dma_addr_t xlen = len; + mem = dma_memory_map(sgl->as, base, &xlen, dir); + if (!mem) { + goto err; + } + if (xlen > len) { + xlen = len; + } + qemu_iovec_add(&p->iov, mem, xlen); + len -= xlen; + base += xlen; + } + } + return 0; + +err: + usb_packet_unmap(p, sgl); + return -1; +} + +void usb_packet_unmap(USBPacket *p, QEMUSGList *sgl) +{ + DMADirection dir = (p->pid == USB_TOKEN_IN) ? + DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE; + int i; + + for (i = 0; i < p->iov.niov; i++) { + dma_memory_unmap(sgl->as, p->iov.iov[i].iov_base, + p->iov.iov[i].iov_len, dir, + p->iov.iov[i].iov_len); + } +} diff --git a/hw/usb/meson.build b/hw/usb/meson.build new file mode 100644 index 000000000..de853d780 --- /dev/null +++ b/hw/usb/meson.build @@ -0,0 +1,84 @@ +hw_usb_modules = {} + +# usb subsystem core +softmmu_ss.add(when: 'CONFIG_USB', if_true: files( + 'bus.c', + 'combined-packet.c', + 'core.c', + 'desc.c', + 'desc-msos.c', + 'libhw.c', + 'pcap.c', +)) + +# usb host adapters +softmmu_ss.add(when: 'CONFIG_USB_UHCI', if_true: files('hcd-uhci.c')) +softmmu_ss.add(when: 'CONFIG_USB_OHCI', if_true: files('hcd-ohci.c')) +softmmu_ss.add(when: 'CONFIG_USB_OHCI_PCI', if_true: files('hcd-ohci-pci.c')) +softmmu_ss.add(when: 'CONFIG_USB_EHCI', if_true: files('hcd-ehci.c')) +softmmu_ss.add(when: 'CONFIG_USB_EHCI_PCI', if_true: files('hcd-ehci-pci.c')) +softmmu_ss.add(when: 'CONFIG_USB_EHCI_SYSBUS', if_true: files('hcd-ehci.c', 'hcd-ehci-sysbus.c')) +softmmu_ss.add(when: 'CONFIG_USB_XHCI', if_true: files('hcd-xhci.c')) +softmmu_ss.add(when: 'CONFIG_USB_XHCI_PCI', if_true: files('hcd-xhci-pci.c')) +softmmu_ss.add(when: 'CONFIG_USB_XHCI_SYSBUS', if_true: files('hcd-xhci-sysbus.c')) +softmmu_ss.add(when: 'CONFIG_USB_XHCI_NEC', if_true: files('hcd-xhci-nec.c')) +softmmu_ss.add(when: 'CONFIG_USB_MUSB', if_true: files('hcd-musb.c')) +softmmu_ss.add(when: 'CONFIG_USB_DWC2', if_true: files('hcd-dwc2.c')) +softmmu_ss.add(when: 'CONFIG_USB_DWC3', if_true: files('hcd-dwc3.c')) + +softmmu_ss.add(when: 'CONFIG_TUSB6010', if_true: files('tusb6010.c')) +softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('chipidea.c')) +softmmu_ss.add(when: 'CONFIG_IMX_USBPHY', if_true: files('imx-usb-phy.c')) +softmmu_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c')) +specific_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-usb2-ctrl-regs.c')) +specific_ss.add(when: 'CONFIG_XLNX_USB_SUBSYS', if_true: files('xlnx-usb-subsystem.c')) + +# emulated usb devices +softmmu_ss.add(when: 'CONFIG_USB', if_true: files('dev-hub.c')) +softmmu_ss.add(when: 'CONFIG_USB', if_true: files('dev-hid.c')) +softmmu_ss.add(when: 'CONFIG_USB_TABLET_WACOM', if_true: files('dev-wacom.c')) +softmmu_ss.add(when: 'CONFIG_USB_STORAGE_CORE', if_true: files('dev-storage.c')) +softmmu_ss.add(when: 'CONFIG_USB_STORAGE_BOT', if_true: files('dev-storage-bot.c')) +softmmu_ss.add(when: 'CONFIG_USB_STORAGE_CLASSIC', if_true: files('dev-storage-classic.c')) +softmmu_ss.add(when: 'CONFIG_USB_STORAGE_UAS', if_true: files('dev-uas.c')) +softmmu_ss.add(when: 'CONFIG_USB_AUDIO', if_true: files('dev-audio.c')) +softmmu_ss.add(when: 'CONFIG_USB_SERIAL', if_true: files('dev-serial.c')) +softmmu_ss.add(when: 'CONFIG_USB_NETWORK', if_true: files('dev-network.c')) +softmmu_ss.add(when: ['CONFIG_POSIX', 'CONFIG_USB_STORAGE_MTP'], if_true: files('dev-mtp.c')) + +# smartcard +softmmu_ss.add(when: 'CONFIG_USB_SMARTCARD', if_true: files('dev-smartcard-reader.c')) + +if cacard.found() + usbsmartcard_ss = ss.source_set() + usbsmartcard_ss.add(when: 'CONFIG_USB_SMARTCARD', + if_true: [cacard, files('ccid-card-emulated.c', 'ccid-card-passthru.c')]) + hw_usb_modules += {'smartcard': usbsmartcard_ss} +endif + +# U2F +softmmu_ss.add(when: 'CONFIG_USB_U2F', if_true: files('u2f.c')) +softmmu_ss.add(when: ['CONFIG_LINUX', 'CONFIG_USB_U2F'], if_true: [libudev, files('u2f-passthru.c')]) +if u2f.found() + softmmu_ss.add(when: 'CONFIG_USB_U2F', if_true: [u2f, files('u2f-emulated.c')]) +endif + +# usb redirect +if usbredir.found() + usbredir_ss = ss.source_set() + usbredir_ss.add(when: 'CONFIG_USB', + if_true: [usbredir, files('redirect.c', 'quirks.c')]) + hw_usb_modules += {'redirect': usbredir_ss} +endif + +# usb pass-through +if libusb.found() + usbhost_ss = ss.source_set() + usbhost_ss.add(when: ['CONFIG_USB', libusb], + if_true: files('host-libusb.c')) + hw_usb_modules += {'host': usbhost_ss} +endif + +softmmu_ss.add(when: ['CONFIG_USB', 'CONFIG_XEN', libusb], if_true: files('xen-usb.c')) + +modules += { 'hw-usb': hw_usb_modules } diff --git a/hw/usb/pcap.c b/hw/usb/pcap.c new file mode 100644 index 000000000..dbff00be2 --- /dev/null +++ b/hw/usb/pcap.c @@ -0,0 +1,253 @@ +/* + * usb packet capture + * + * Copyright (c) 2021 Gerd Hoffmann <kraxel@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/usb.h" + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_MAJOR 2 +#define PCAP_MINOR 4 + +/* https://wiki.wireshark.org/Development/LibpcapFileFormat */ + +struct pcap_hdr { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +}; + +struct pcaprec_hdr { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +/* https://www.tcpdump.org/linktypes.html */ +/* linux: Documentation/usb/usbmon.rst */ +/* linux: drivers/usb/mon/mon_bin.c */ + +#define LINKTYPE_USB_LINUX 189 /* first 48 bytes only */ +#define LINKTYPE_USB_LINUX_MMAPPED 220 /* full 64 byte header */ + +struct usbmon_packet { + uint64_t id; /* 0: URB ID - from submission to callback */ + unsigned char type; /* 8: Same as text; extensible. */ + unsigned char xfer_type; /* ISO (0), Intr, Control, Bulk (3) */ + unsigned char epnum; /* Endpoint number and transfer direction */ + unsigned char devnum; /* Device address */ + uint16_t busnum; /* 12: Bus number */ + char flag_setup; /* 14: Same as text */ + char flag_data; /* 15: Same as text; Binary zero is OK. */ + int64_t ts_sec; /* 16: gettimeofday */ + int32_t ts_usec; /* 24: gettimeofday */ + int32_t status; /* 28: */ + unsigned int length; /* 32: Length of data (submitted or actual) */ + unsigned int len_cap; /* 36: Delivered length */ + union { /* 40: */ + unsigned char setup[8]; /* Only for Control S-type */ + struct iso_rec { /* Only for ISO */ + int32_t error_count; + int32_t numdesc; + } iso; + } s; + int32_t interval; /* 48: Only for Interrupt and ISO */ + int32_t start_frame; /* 52: For ISO */ + uint32_t xfer_flags; /* 56: copy of URB's transfer_flags */ + uint32_t ndesc; /* 60: Actual number of ISO descriptors */ +}; /* 64 total length */ + +/* ------------------------------------------------------------------------ */ + +#define CTRL_LEN 4096 +#define DATA_LEN 256 + +static int usbmon_status(USBPacket *p) +{ + switch (p->status) { + case USB_RET_SUCCESS: + return 0; + case USB_RET_NODEV: + return -19; /* -ENODEV */ + default: + return -121; /* -EREMOTEIO */ + } +} + +static unsigned int usbmon_epnum(USBPacket *p) +{ + unsigned epnum = 0; + + epnum |= p->ep->nr; + epnum |= (p->pid == USB_TOKEN_IN) ? 0x80 : 0; + return epnum; +} + +static unsigned char usbmon_xfer_type[] = { + [USB_ENDPOINT_XFER_CONTROL] = 2, + [USB_ENDPOINT_XFER_ISOC] = 0, + [USB_ENDPOINT_XFER_BULK] = 3, + [USB_ENDPOINT_XFER_INT] = 1, +}; + +static void do_usb_pcap_header(FILE *fp, struct usbmon_packet *packet) +{ + struct pcaprec_hdr header; + struct timeval tv; + + gettimeofday(&tv, NULL); + packet->ts_sec = tv.tv_sec; + packet->ts_usec = tv.tv_usec; + + header.ts_sec = packet->ts_sec; + header.ts_usec = packet->ts_usec; + header.incl_len = packet->len_cap; + header.orig_len = packet->length + sizeof(*packet); + fwrite(&header, sizeof(header), 1, fp); + fwrite(packet, sizeof(*packet), 1, fp); +} + +static void do_usb_pcap_ctrl(FILE *fp, USBPacket *p, bool setup) +{ + USBDevice *dev = p->ep->dev; + bool in = dev->setup_buf[0] & USB_DIR_IN; + struct usbmon_packet packet = { + .id = 0, + .type = setup ? 'S' : 'C', + .xfer_type = usbmon_xfer_type[USB_ENDPOINT_XFER_CONTROL], + .epnum = in ? 0x80 : 0, + .devnum = dev->addr, + .flag_setup = setup ? 0 : '-', + .flag_data = '=', + .length = dev->setup_len, + }; + int data_len = dev->setup_len; + + if (data_len > CTRL_LEN) { + data_len = CTRL_LEN; + } + if (setup) { + memcpy(packet.s.setup, dev->setup_buf, 8); + } else { + packet.status = usbmon_status(p); + } + + if (in && setup) { + packet.flag_data = '<'; + packet.length = 0; + data_len = 0; + } + if (!in && !setup) { + packet.flag_data = '>'; + packet.length = 0; + data_len = 0; + } + + packet.len_cap = data_len + sizeof(packet); + do_usb_pcap_header(fp, &packet); + if (data_len) { + fwrite(dev->data_buf, data_len, 1, fp); + } + + fflush(fp); +} + +static void do_usb_pcap_data(FILE *fp, USBPacket *p, bool setup) +{ + struct usbmon_packet packet = { + .id = p->id, + .type = setup ? 'S' : 'C', + .xfer_type = usbmon_xfer_type[p->ep->type], + .epnum = usbmon_epnum(p), + .devnum = p->ep->dev->addr, + .flag_setup = '-', + .flag_data = '=', + .length = p->iov.size, + }; + int data_len = p->iov.size; + + if (p->ep->nr == 0) { + /* ignore control pipe packets */ + return; + } + + if (data_len > DATA_LEN) { + data_len = DATA_LEN; + } + if (!setup) { + packet.status = usbmon_status(p); + if (packet.length > p->actual_length) { + packet.length = p->actual_length; + } + if (data_len > p->actual_length) { + data_len = p->actual_length; + } + } + + if (p->pid == USB_TOKEN_IN && setup) { + packet.flag_data = '<'; + packet.length = 0; + data_len = 0; + } + if (p->pid == USB_TOKEN_OUT && !setup) { + packet.flag_data = '>'; + packet.length = 0; + data_len = 0; + } + + packet.len_cap = data_len + sizeof(packet); + do_usb_pcap_header(fp, &packet); + if (data_len) { + void *buf = g_malloc(data_len); + iov_to_buf(p->iov.iov, p->iov.niov, 0, buf, data_len); + fwrite(buf, data_len, 1, fp); + g_free(buf); + } + + fflush(fp); +} + +void usb_pcap_init(FILE *fp) +{ + struct pcap_hdr header = { + .magic_number = PCAP_MAGIC, + .version_major = 2, + .version_minor = 4, + .snaplen = MAX(CTRL_LEN, DATA_LEN) + sizeof(struct usbmon_packet), + .network = LINKTYPE_USB_LINUX_MMAPPED, + }; + + fwrite(&header, sizeof(header), 1, fp); +} + +void usb_pcap_ctrl(USBPacket *p, bool setup) +{ + FILE *fp = p->ep->dev->pcap; + + if (!fp) { + return; + } + + do_usb_pcap_ctrl(fp, p, setup); +} + +void usb_pcap_data(USBPacket *p, bool setup) +{ + FILE *fp = p->ep->dev->pcap; + + if (!fp) { + return; + } + + do_usb_pcap_data(fp, p, setup); +} diff --git a/hw/usb/quirks-ftdi-ids.h b/hw/usb/quirks-ftdi-ids.h new file mode 100644 index 000000000..f3cb157d6 --- /dev/null +++ b/hw/usb/quirks-ftdi-ids.h @@ -0,0 +1,1249 @@ +/* + * vendor/product IDs (VID/PID) of devices using FTDI USB serial converters. + * Please keep numerically sorted within individual areas, thanks! + * + * Philipp Gühring - pg@futureware.at - added the Device ID of the USB relais + * from Rudolf Gugler + * + */ + + +/**********************************/ +/***** devices using FTDI VID *****/ +/**********************************/ + + +#define FTDI_VID 0x0403 /* Vendor Id */ + + +/*** "original" FTDI device PIDs ***/ + +#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */ +#define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */ +#define FTDI_8U2232C_PID 0x6010 /* Dual channel device */ +#define FTDI_4232H_PID 0x6011 /* Quad channel hi-speed device */ +#define FTDI_232H_PID 0x6014 /* Single channel hi-speed device */ +#define FTDI_FTX_PID 0x6015 /* FT-X series (FT201X, FT230X, FT231X, etc) */ +#define FTDI_SIO_PID 0x8372 /* Product Id SIO application of 8U100AX */ +#define FTDI_232RL_PID 0xFBFA /* Product ID for FT232RL */ + + +/*** third-party PIDs (using FTDI_VID) ***/ + +#define FTDI_LUMEL_PD12_PID 0x6002 + +/* + * Marvell OpenRD Base, Client + * http://www.open-rd.org + * OpenRD Base, Client use VID 0x0403 + */ +#define MARVELL_OPENRD_PID 0x9e90 + +/* www.candapter.com Ewert Energy Systems CANdapter device */ +#define FTDI_CANDAPTER_PID 0x9F80 /* Product Id */ + +/* + * Texas Instruments XDS100v2 JTAG / BeagleBone A3 + * http://processors.wiki.ti.com/index.php/XDS100 + * http://beagleboard.org/bone + */ +#define TI_XDS100V2_PID 0xa6d0 + +#define FTDI_NXTCAM_PID 0xABB8 /* NXTCam for Mindstorms NXT */ + +/* US Interface Navigator (http://www.usinterface.com/) */ +#define FTDI_USINT_CAT_PID 0xb810 /* Navigator CAT and 2nd PTT lines */ +#define FTDI_USINT_WKEY_PID 0xb811 /* Navigator WKEY and FSK lines */ +#define FTDI_USINT_RS232_PID 0xb812 /* Navigator RS232 and CONFIG lines */ + +/* OOCDlink by Joern Kaipf <joernk@web.de> + * (http://www.joernonline.de/) */ +#define FTDI_OOCDLINK_PID 0xbaf8 /* Amontec JTAGkey */ + +/* Luminary Micro Stellaris Boards, VID = FTDI_VID */ +/* FTDI 2332C Dual channel device, side A=245 FIFO (JTAG), Side B=RS232 UART */ +#define LMI_LM3S_DEVEL_BOARD_PID 0xbcd8 +#define LMI_LM3S_EVAL_BOARD_PID 0xbcd9 +#define LMI_LM3S_ICDI_BOARD_PID 0xbcda + +#define FTDI_TURTELIZER_PID 0xBDC8 /* JTAG/RS-232 adapter by egnite GmbH */ + +/* OpenDCC (www.opendcc.de) product id */ +#define FTDI_OPENDCC_PID 0xBFD8 +#define FTDI_OPENDCC_SNIFFER_PID 0xBFD9 +#define FTDI_OPENDCC_THROTTLE_PID 0xBFDA +#define FTDI_OPENDCC_GATEWAY_PID 0xBFDB +#define FTDI_OPENDCC_GBM_PID 0xBFDC + +/* NZR SEM 16+ USB (http://www.nzr.de) */ +#define FTDI_NZR_SEM_USB_PID 0xC1E0 /* NZR SEM-LOG16+ */ + +/* + * RR-CirKits LocoBuffer USB (http://www.rr-cirkits.com) + */ +#define FTDI_RRCIRKITS_LOCOBUFFER_PID 0xc7d0 /* LocoBuffer USB */ + +/* DMX4ALL DMX Interfaces */ +#define FTDI_DMX4ALL 0xC850 + +/* + * ASK.fr devices + */ +#define FTDI_ASK_RDR400_PID 0xC991 /* ASK RDR 400 series card reader */ + +/* www.starting-point-systems.com µChameleon device */ +#define FTDI_MICRO_CHAMELEON_PID 0xCAA0 /* Product Id */ + +/* + * Tactrix OpenPort (ECU) devices. + * OpenPort 1.3M submitted by Donour Sizemore. + * OpenPort 1.3S and 1.3U submitted by Ian Abbott. + */ +#define FTDI_TACTRIX_OPENPORT_13M_PID 0xCC48 /* OpenPort 1.3 Mitsubishi */ +#define FTDI_TACTRIX_OPENPORT_13S_PID 0xCC49 /* OpenPort 1.3 Subaru */ +#define FTDI_TACTRIX_OPENPORT_13U_PID 0xCC4A /* OpenPort 1.3 Universal */ + +#define FTDI_DISTORTEC_JTAG_LOCK_PICK_PID 0xCFF8 + +/* SCS HF Radio Modems PID's (http://www.scs-ptc.com) */ +/* the VID is the standard ftdi vid (FTDI_VID) */ +#define FTDI_SCS_DEVICE_0_PID 0xD010 /* SCS PTC-IIusb */ +#define FTDI_SCS_DEVICE_1_PID 0xD011 /* SCS Tracker / DSP TNC */ +#define FTDI_SCS_DEVICE_2_PID 0xD012 +#define FTDI_SCS_DEVICE_3_PID 0xD013 +#define FTDI_SCS_DEVICE_4_PID 0xD014 +#define FTDI_SCS_DEVICE_5_PID 0xD015 +#define FTDI_SCS_DEVICE_6_PID 0xD016 +#define FTDI_SCS_DEVICE_7_PID 0xD017 + +/* iPlus device */ +#define FTDI_IPLUS_PID 0xD070 /* Product Id */ +#define FTDI_IPLUS2_PID 0xD071 /* Product Id */ + +/* + * Gamma Scout (http://gamma-scout.com/). Submitted by rsc@runtux.com. + */ +#define FTDI_GAMMA_SCOUT_PID 0xD678 /* Gamma Scout online */ + +/* Propox devices */ +#define FTDI_PROPOX_JTAGCABLEII_PID 0xD738 +#define FTDI_PROPOX_ISPCABLEIII_PID 0xD739 + +/* Lenz LI-USB Computer Interface. */ +#define FTDI_LENZ_LIUSB_PID 0xD780 + +/* Vardaan Enterprises Serial Interface VEUSB422R3 */ +#define FTDI_VARDAAN_PID 0xF070 + +/* + * Xsens Technologies BV products (http://www.xsens.com). + */ +#define XSENS_CONVERTER_0_PID 0xD388 +#define XSENS_CONVERTER_1_PID 0xD389 +#define XSENS_CONVERTER_2_PID 0xD38A +#define XSENS_CONVERTER_3_PID 0xD38B +#define XSENS_CONVERTER_4_PID 0xD38C +#define XSENS_CONVERTER_5_PID 0xD38D +#define XSENS_CONVERTER_6_PID 0xD38E +#define XSENS_CONVERTER_7_PID 0xD38F + +/* + * NDI (www.ndigital.com) product ids + */ +#define FTDI_NDI_HUC_PID 0xDA70 /* NDI Host USB Converter */ +#define FTDI_NDI_SPECTRA_SCU_PID 0xDA71 /* NDI Spectra SCU */ +#define FTDI_NDI_FUTURE_2_PID 0xDA72 /* NDI future device #2 */ +#define FTDI_NDI_FUTURE_3_PID 0xDA73 /* NDI future device #3 */ +#define FTDI_NDI_AURORA_SCU_PID 0xDA74 /* NDI Aurora SCU */ + +/* + * ChamSys Limited (www.chamsys.co.uk) USB wing/interface product IDs + */ +#define FTDI_CHAMSYS_24_MASTER_WING_PID 0xDAF8 +#define FTDI_CHAMSYS_PC_WING_PID 0xDAF9 +#define FTDI_CHAMSYS_USB_DMX_PID 0xDAFA +#define FTDI_CHAMSYS_MIDI_TIMECODE_PID 0xDAFB +#define FTDI_CHAMSYS_MINI_WING_PID 0xDAFC +#define FTDI_CHAMSYS_MAXI_WING_PID 0xDAFD +#define FTDI_CHAMSYS_MEDIA_WING_PID 0xDAFE +#define FTDI_CHAMSYS_WING_PID 0xDAFF + +/* + * Westrex International devices submitted by Cory Lee + */ +#define FTDI_WESTREX_MODEL_777_PID 0xDC00 /* Model 777 */ +#define FTDI_WESTREX_MODEL_8900F_PID 0xDC01 /* Model 8900F */ + +/* + * ACG Identification Technologies GmbH products (http://www.acg.de/). + * Submitted by anton -at- goto10 -dot- org. + */ +#define FTDI_ACG_HFDUAL_PID 0xDD20 /* HF Dual ISO Reader (RFID) */ + +/* + * Definitions for Artemis astronomical USB based cameras + * Check it at http://www.artemisccd.co.uk/ + */ +#define FTDI_ARTEMIS_PID 0xDF28 /* All Artemis Cameras */ + +/* + * Definitions for ATIK Instruments astronomical USB based cameras + * Check it at http://www.atik-instruments.com/ + */ +#define FTDI_ATIK_ATK16_PID 0xDF30 /* ATIK ATK-16 Grayscale Camera */ +#define FTDI_ATIK_ATK16C_PID 0xDF32 /* ATIK ATK-16C Colour Camera */ +#define FTDI_ATIK_ATK16HR_PID 0xDF31 /* ATIK ATK-16HR Grayscale Camera */ +#define FTDI_ATIK_ATK16HRC_PID 0xDF33 /* ATIK ATK-16HRC Colour Camera */ +#define FTDI_ATIK_ATK16IC_PID 0xDF35 /* ATIK ATK-16IC Grayscale Camera */ + +/* + * Yost Engineering, Inc. products (www.yostengineering.com). + * PID 0xE050 submitted by Aaron Prose. + */ +#define FTDI_YEI_SERVOCENTER31_PID 0xE050 /* YEI ServoCenter3.1 USB */ + +/* + * ELV USB devices submitted by Christian Abt of ELV (www.elv.de). + * All of these devices use FTDI's vendor ID (0x0403). + * Further IDs taken from ELV Windows .inf file. + * + * The previously included PID for the UO 100 module was incorrect. + * In fact, that PID was for ELV's UR 100 USB-RS232 converter (0xFB58). + * + * Armin Laeuger originally sent the PID for the UM 100 module. + */ +#define FTDI_ELV_USR_PID 0xE000 /* ELV Universal-Sound-Recorder */ +#define FTDI_ELV_MSM1_PID 0xE001 /* ELV Mini-Sound-Modul */ +#define FTDI_ELV_KL100_PID 0xE002 /* ELV Kfz-Leistungsmesser KL 100 */ +#define FTDI_ELV_WS550_PID 0xE004 /* WS 550 */ +#define FTDI_ELV_EC3000_PID 0xE006 /* ENERGY CONTROL 3000 USB */ +#define FTDI_ELV_WS888_PID 0xE008 /* WS 888 */ +#define FTDI_ELV_TWS550_PID 0xE009 /* Technoline WS 550 */ +#define FTDI_ELV_FEM_PID 0xE00A /* Funk Energie Monitor */ +#define FTDI_ELV_FHZ1300PC_PID 0xE0E8 /* FHZ 1300 PC */ +#define FTDI_ELV_WS500_PID 0xE0E9 /* PC-Wetterstation (WS 500) */ +#define FTDI_ELV_HS485_PID 0xE0EA /* USB to RS-485 adapter */ +#define FTDI_ELV_UMS100_PID 0xE0EB /* ELV USB Master-Slave Schaltsteckdose UMS 100 */ +#define FTDI_ELV_TFD128_PID 0xE0EC /* ELV Temperatur-Feuchte-Datenlogger TFD 128 */ +#define FTDI_ELV_FM3RX_PID 0xE0ED /* ELV Messwertuebertragung FM3 RX */ +#define FTDI_ELV_WS777_PID 0xE0EE /* Conrad WS 777 */ +#define FTDI_ELV_EM1010PC_PID 0xE0EF /* Energy monitor EM 1010 PC */ +#define FTDI_ELV_CSI8_PID 0xE0F0 /* Computer-Schalt-Interface (CSI 8) */ +#define FTDI_ELV_EM1000DL_PID 0xE0F1 /* PC-Datenlogger fuer Energiemonitor (EM 1000 DL) */ +#define FTDI_ELV_PCK100_PID 0xE0F2 /* PC-Kabeltester (PCK 100) */ +#define FTDI_ELV_RFP500_PID 0xE0F3 /* HF-Leistungsmesser (RFP 500) */ +#define FTDI_ELV_FS20SIG_PID 0xE0F4 /* Signalgeber (FS 20 SIG) */ +#define FTDI_ELV_UTP8_PID 0xE0F5 /* ELV UTP 8 */ +#define FTDI_ELV_WS300PC_PID 0xE0F6 /* PC-Wetterstation (WS 300 PC) */ +#define FTDI_ELV_WS444PC_PID 0xE0F7 /* Conrad WS 444 PC */ +#define FTDI_PHI_FISCO_PID 0xE40B /* PHI Fisco USB to Serial cable */ +#define FTDI_ELV_UAD8_PID 0xF068 /* USB-AD-Wandler (UAD 8) */ +#define FTDI_ELV_UDA7_PID 0xF069 /* USB-DA-Wandler (UDA 7) */ +#define FTDI_ELV_USI2_PID 0xF06A /* USB-Schrittmotoren-Interface (USI 2) */ +#define FTDI_ELV_T1100_PID 0xF06B /* Thermometer (T 1100) */ +#define FTDI_ELV_PCD200_PID 0xF06C /* PC-Datenlogger (PCD 200) */ +#define FTDI_ELV_ULA200_PID 0xF06D /* USB-LCD-Ansteuerung (ULA 200) */ +#define FTDI_ELV_ALC8500_PID 0xF06E /* ALC 8500 Expert */ +#define FTDI_ELV_FHZ1000PC_PID 0xF06F /* FHZ 1000 PC */ +#define FTDI_ELV_UR100_PID 0xFB58 /* USB-RS232-Umsetzer (UR 100) */ +#define FTDI_ELV_UM100_PID 0xFB5A /* USB-Modul UM 100 */ +#define FTDI_ELV_UO100_PID 0xFB5B /* USB-Modul UO 100 */ +/* Additional ELV PIDs that default to using the FTDI D2XX drivers on + * MS Windows, rather than the FTDI Virtual Com Port drivers. + * Maybe these will be easier to use with the libftdi/libusb user-space + * drivers, or possibly the Comedi drivers in some cases. */ +#define FTDI_ELV_CLI7000_PID 0xFB59 /* Computer-Light-Interface (CLI 7000) */ +#define FTDI_ELV_PPS7330_PID 0xFB5C /* Processor-Power-Supply (PPS 7330) */ +#define FTDI_ELV_TFM100_PID 0xFB5D /* Temperatur-Feuchte-Messgeraet (TFM 100) */ +#define FTDI_ELV_UDF77_PID 0xFB5E /* USB DCF Funkuhr (UDF 77) */ +#define FTDI_ELV_UIO88_PID 0xFB5F /* USB-I/O Interface (UIO 88) */ + +/* + * EVER Eco Pro UPS (http://www.ever.com.pl/) + */ + +#define EVER_ECO_PRO_CDS 0xe520 /* RS-232 converter */ + +/* + * Active Robots product ids. + */ +#define FTDI_ACTIVE_ROBOTS_PID 0xE548 /* USB comms board */ + +/* Pyramid Computer GmbH */ +#define FTDI_PYRAMID_PID 0xE6C8 /* Pyramid Appliance Display */ + +/* www.elsterelectricity.com Elster Unicom III Optical Probe */ +#define FTDI_ELSTER_UNICOM_PID 0xE700 /* Product Id */ + +/* + * Gude Analog- und Digitalsysteme GmbH + */ +#define FTDI_GUDEADS_E808_PID 0xE808 +#define FTDI_GUDEADS_E809_PID 0xE809 +#define FTDI_GUDEADS_E80A_PID 0xE80A +#define FTDI_GUDEADS_E80B_PID 0xE80B +#define FTDI_GUDEADS_E80C_PID 0xE80C +#define FTDI_GUDEADS_E80D_PID 0xE80D +#define FTDI_GUDEADS_E80E_PID 0xE80E +#define FTDI_GUDEADS_E80F_PID 0xE80F +#define FTDI_GUDEADS_E888_PID 0xE888 /* Expert ISDN Control USB */ +#define FTDI_GUDEADS_E889_PID 0xE889 /* USB RS-232 OptoBridge */ +#define FTDI_GUDEADS_E88A_PID 0xE88A +#define FTDI_GUDEADS_E88B_PID 0xE88B +#define FTDI_GUDEADS_E88C_PID 0xE88C +#define FTDI_GUDEADS_E88D_PID 0xE88D +#define FTDI_GUDEADS_E88E_PID 0xE88E +#define FTDI_GUDEADS_E88F_PID 0xE88F + +/* + * Eclo (http://www.eclo.pt/) product IDs. + * PID 0xEA90 submitted by Martin Grill. + */ +#define FTDI_ECLO_COM_1WIRE_PID 0xEA90 /* COM to 1-Wire USB adaptor */ + +/* TNC-X USB-to-packet-radio adapter, versions prior to 3.0 (DLP module) */ +#define FTDI_TNC_X_PID 0xEBE0 + +/* + * Teratronik product ids. + * Submitted by O. Wölfelschneider. + */ +#define FTDI_TERATRONIK_VCP_PID 0xEC88 /* Teratronik device (preferring VCP driver on windows) */ +#define FTDI_TERATRONIK_D2XX_PID 0xEC89 /* Teratronik device (preferring D2XX driver on windows) */ + +/* Rig Expert Ukraine devices */ +#define FTDI_REU_TINY_PID 0xED22 /* RigExpert Tiny */ + +/* + * Hameg HO820 and HO870 interface (using VID 0x0403) + */ +#define HAMEG_HO820_PID 0xed74 +#define HAMEG_HO730_PID 0xed73 +#define HAMEG_HO720_PID 0xed72 +#define HAMEG_HO870_PID 0xed71 + +/* + * MaxStream devices www.maxstream.net + */ +#define FTDI_MAXSTREAM_PID 0xEE18 /* Xbee PKG-U Module */ + +/* + * microHAM product IDs (http://www.microham.com). + * Submitted by Justin Burket (KL1RL) <zorton@jtan.com> + * and Mike Studer (K6EEP) <k6eep@hamsoftware.org>. + * Ian Abbott <abbotti@mev.co.uk> added a few more from the driver INF file. + */ +#define FTDI_MHAM_KW_PID 0xEEE8 /* USB-KW interface */ +#define FTDI_MHAM_YS_PID 0xEEE9 /* USB-YS interface */ +#define FTDI_MHAM_Y6_PID 0xEEEA /* USB-Y6 interface */ +#define FTDI_MHAM_Y8_PID 0xEEEB /* USB-Y8 interface */ +#define FTDI_MHAM_IC_PID 0xEEEC /* USB-IC interface */ +#define FTDI_MHAM_DB9_PID 0xEEED /* USB-DB9 interface */ +#define FTDI_MHAM_RS232_PID 0xEEEE /* USB-RS232 interface */ +#define FTDI_MHAM_Y9_PID 0xEEEF /* USB-Y9 interface */ + +/* Domintell products http://www.domintell.com */ +#define FTDI_DOMINTELL_DGQG_PID 0xEF50 /* Master */ +#define FTDI_DOMINTELL_DUSB_PID 0xEF51 /* DUSB01 module */ + +/* + * The following are the values for the Perle Systems + * UltraPort USB serial converters + */ +#define FTDI_PERLE_ULTRAPORT_PID 0xF0C0 /* Perle UltraPort Product Id */ + +/* Sprog II (Andrew Crosland's SprogII DCC interface) */ +#define FTDI_SPROG_II 0xF0C8 + +/* an infrared receiver for user access control with IR tags */ +#define FTDI_PIEGROUP_PID 0xF208 /* Product Id */ + +/* ACT Solutions HomePro ZWave interface + (http://www.act-solutions.com/HomePro-Product-Matrix.html) */ +#define FTDI_ACTZWAVE_PID 0xF2D0 + +/* + * 4N-GALAXY.DE PIDs for CAN-USB, USB-RS232, USB-RS422, USB-RS485, + * USB-TTY aktiv, USB-TTY passiv. Some PIDs are used by several devices + * and I'm not entirely sure which are used by which. + */ +#define FTDI_4N_GALAXY_DE_1_PID 0xF3C0 +#define FTDI_4N_GALAXY_DE_2_PID 0xF3C1 +#define FTDI_4N_GALAXY_DE_3_PID 0xF3C2 + +/* + * Linx Technologies product ids + */ +#define LINX_SDMUSBQSS_PID 0xF448 /* Linx SDM-USB-QS-S */ +#define LINX_MASTERDEVEL2_PID 0xF449 /* Linx Master Development 2.0 */ +#define LINX_FUTURE_0_PID 0xF44A /* Linx future device */ +#define LINX_FUTURE_1_PID 0xF44B /* Linx future device */ +#define LINX_FUTURE_2_PID 0xF44C /* Linx future device */ + +/* + * Oceanic product ids + */ +#define FTDI_OCEANIC_PID 0xF460 /* Oceanic dive instrument */ + +/* + * SUUNTO product ids + */ +#define FTDI_SUUNTO_SPORTS_PID 0xF680 /* Suunto Sports instrument */ + +/* USB-UIRT - An infrared receiver and transmitter using the 8U232AM chip */ +/* http://www.usbuirt.com/ */ +#define FTDI_USB_UIRT_PID 0xF850 /* Product Id */ + +/* CCS Inc. ICDU/ICDU40 product ID - + * the FT232BM is used in an in-circuit-debugger unit for PIC16's/PIC18's */ +#define FTDI_CCSICDU20_0_PID 0xF9D0 +#define FTDI_CCSICDU40_1_PID 0xF9D1 +#define FTDI_CCSMACHX_2_PID 0xF9D2 +#define FTDI_CCSLOAD_N_GO_3_PID 0xF9D3 +#define FTDI_CCSICDU64_4_PID 0xF9D4 +#define FTDI_CCSPRIME8_5_PID 0xF9D5 + +/* + * The following are the values for the Matrix Orbital LCD displays, + * which are the FT232BM ( similar to the 8U232AM ) + */ +#define FTDI_MTXORB_0_PID 0xFA00 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_1_PID 0xFA01 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_2_PID 0xFA02 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_3_PID 0xFA03 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_4_PID 0xFA04 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_5_PID 0xFA05 /* Matrix Orbital Product Id */ +#define FTDI_MTXORB_6_PID 0xFA06 /* Matrix Orbital Product Id */ + +/* + * Home Electronics (www.home-electro.com) USB gadgets + */ +#define FTDI_HE_TIRA1_PID 0xFA78 /* Tira-1 IR transceiver */ + +/* Inside Accesso contactless reader (http://www.insidecontactless.com/) */ +#define INSIDE_ACCESSO 0xFAD0 + +/* + * ThorLabs USB motor drivers + */ +#define FTDI_THORLABS_PID 0xfaf0 /* ThorLabs USB motor drivers */ + +/* + * Protego product ids + */ +#define PROTEGO_SPECIAL_1 0xFC70 /* special/unknown device */ +#define PROTEGO_R2X0 0xFC71 /* R200-USB TRNG unit (R210, R220, and R230) */ +#define PROTEGO_SPECIAL_3 0xFC72 /* special/unknown device */ +#define PROTEGO_SPECIAL_4 0xFC73 /* special/unknown device */ + +/* + * Sony Ericsson product ids + */ +#define FTDI_DSS20_PID 0xFC82 /* DSS-20 Sync Station for Sony Ericsson P800 */ +#define FTDI_URBAN_0_PID 0xFC8A /* Sony Ericsson Urban, uart #0 */ +#define FTDI_URBAN_1_PID 0xFC8B /* Sony Ericsson Urban, uart #1 */ + +/* www.irtrans.de device */ +#define FTDI_IRTRANS_PID 0xFC60 /* Product Id */ + +/* + * RM Michaelides CANview USB (http://www.rmcan.com) (FTDI_VID) + * CAN fieldbus interface adapter, added by port GmbH www.port.de) + * Ian Abbott changed the macro names for consistency. + */ +#define FTDI_RM_CANVIEW_PID 0xfd60 /* Product Id */ +/* www.thoughttechnology.com/ TT-USB provide with procomp use ftdi_sio */ +#define FTDI_TTUSB_PID 0xFF20 /* Product Id */ + +#define FTDI_USBX_707_PID 0xF857 /* ADSTech IR Blaster USBX-707 (FTDI_VID) */ + +#define FTDI_RELAIS_PID 0xFA10 /* Relais device from Rudolf Gugler */ + +/* + * PCDJ use ftdi based dj-controllers. The following PID is + * for their DAC-2 device http://www.pcdjhardware.com/DAC2.asp + * (the VID is the standard ftdi vid (FTDI_VID), PID sent by Wouter Paesen) + */ +#define FTDI_PCDJ_DAC2_PID 0xFA88 + +#define FTDI_R2000KU_TRUE_RNG 0xFB80 /* R2000KU TRUE RNG (FTDI_VID) */ + +/* + * DIEBOLD BCS SE923 (FTDI_VID) + */ +#define DIEBOLD_BCS_SE923_PID 0xfb99 + +/* www.crystalfontz.com devices + * - thanx for providing free devices for evaluation ! + * they use the ftdi chipset for the USB interface + * and the vendor id is the same + */ +#define FTDI_XF_632_PID 0xFC08 /* 632: 16x2 Character Display */ +#define FTDI_XF_634_PID 0xFC09 /* 634: 20x4 Character Display */ +#define FTDI_XF_547_PID 0xFC0A /* 547: Two line Display */ +#define FTDI_XF_633_PID 0xFC0B /* 633: 16x2 Character Display with Keys */ +#define FTDI_XF_631_PID 0xFC0C /* 631: 20x2 Character Display */ +#define FTDI_XF_635_PID 0xFC0D /* 635: 20x4 Character Display */ +#define FTDI_XF_640_PID 0xFC0E /* 640: Two line Display */ +#define FTDI_XF_642_PID 0xFC0F /* 642: Two line Display */ + +/* + * Video Networks Limited / Homechoice in the UK use an ftdi-based device + * for their 1Mb broadband internet service. The following PID is exhibited + * by the usb device supplied (the VID is the standard ftdi vid (FTDI_VID) + */ +#define FTDI_VNHCPCUSB_D_PID 0xfe38 /* Product Id */ + +/* AlphaMicro Components AMC-232USB01 device (FTDI_VID) */ +#define FTDI_AMC232_PID 0xFF00 /* Product Id */ + +/* + * IBS elektronik product ids (FTDI_VID) + * Submitted by Thomas Schleusener + */ +#define FTDI_IBS_US485_PID 0xff38 /* IBS US485 (USB<-->RS422/485 interface) */ +#define FTDI_IBS_PICPRO_PID 0xff39 /* IBS PIC-Programmer */ +#define FTDI_IBS_PCMCIA_PID 0xff3a /* IBS Card reader for PCMCIA SRAM-cards */ +#define FTDI_IBS_PK1_PID 0xff3b /* IBS PK1 - Particel counter */ +#define FTDI_IBS_RS232MON_PID 0xff3c /* IBS RS232 - Monitor */ +#define FTDI_IBS_APP70_PID 0xff3d /* APP 70 (dust monitoring system) */ +#define FTDI_IBS_PEDO_PID 0xff3e /* IBS PEDO-Modem (RF modem 868.35 MHz) */ +#define FTDI_IBS_PROD_PID 0xff3f /* future device */ +/* www.canusb.com Lawicel CANUSB device (FTDI_VID) */ +#define FTDI_CANUSB_PID 0xFFA8 /* Product Id */ + +/* + * TavIR AVR product ids (FTDI_VID) + */ +#define FTDI_TAVIR_STK500_PID 0xFA33 /* STK500 AVR programmer */ + +/* + * TIAO product ids (FTDI_VID) + * http://www.tiaowiki.com/w/Main_Page + */ +#define FTDI_TIAO_UMPA_PID 0x8a98 /* TIAO/DIYGADGET USB Multi-Protocol Adapter */ + + +/********************************/ +/** third-party VID/PID combos **/ +/********************************/ + + + +/* + * Atmel STK541 + */ +#define ATMEL_VID 0x03eb /* Vendor ID */ +#define STK541_PID 0x2109 /* Zigbee Controller */ + +/* + * Blackfin gnICE JTAG + * http://docs.blackfin.uclinux.org/doku.php?id=hw:jtag:gnice + */ +#define ADI_VID 0x0456 +#define ADI_GNICE_PID 0xF000 +#define ADI_GNICEPLUS_PID 0xF001 + +/* + * Microchip Technology, Inc. + * + * MICROCHIP_VID (0x04D8) and MICROCHIP_USB_BOARD_PID (0x000A) are + * used by single function CDC ACM class based firmware demo + * applications. The VID/PID has also been used in firmware + * emulating FTDI serial chips by: + * Hornby Elite - Digital Command Control Console + * http://www.hornby.com/hornby-dcc/controllers/ + */ +#define MICROCHIP_VID 0x04D8 +#define MICROCHIP_USB_BOARD_PID 0x000A /* CDC RS-232 Emulation Demo */ + +/* + * RATOC REX-USB60F + */ +#define RATOC_VENDOR_ID 0x0584 +#define RATOC_PRODUCT_ID_USB60F 0xb020 + +/* + * Acton Research Corp. + */ +#define ACTON_VID 0x0647 /* Vendor ID */ +#define ACTON_SPECTRAPRO_PID 0x0100 + +/* + * Contec products (http://www.contec.com) + * Submitted by Daniel Sangorrin + */ +#define CONTEC_VID 0x06CE /* Vendor ID */ +#define CONTEC_COM1USBH_PID 0x8311 /* COM-1(USB)H */ + +/* + * Definitions for B&B Electronics products. + */ +#define BANDB_VID 0x0856 /* B&B Electronics Vendor ID */ +#define BANDB_USOTL4_PID 0xAC01 /* USOTL4 Isolated RS-485 Converter */ +#define BANDB_USTL4_PID 0xAC02 /* USTL4 RS-485 Converter */ +#define BANDB_USO9ML2_PID 0xAC03 /* USO9ML2 Isolated RS-232 Converter */ +#define BANDB_USOPTL4_PID 0xAC11 +#define BANDB_USPTL4_PID 0xAC12 +#define BANDB_USO9ML2DR_2_PID 0xAC16 +#define BANDB_USO9ML2DR_PID 0xAC17 +#define BANDB_USOPTL4DR2_PID 0xAC18 /* USOPTL4R-2 2-port Isolated RS-232 Converter */ +#define BANDB_USOPTL4DR_PID 0xAC19 +#define BANDB_485USB9F_2W_PID 0xAC25 +#define BANDB_485USB9F_4W_PID 0xAC26 +#define BANDB_232USB9M_PID 0xAC27 +#define BANDB_485USBTB_2W_PID 0xAC33 +#define BANDB_485USBTB_4W_PID 0xAC34 +#define BANDB_TTL5USB9M_PID 0xAC49 +#define BANDB_TTL3USB9M_PID 0xAC50 +#define BANDB_ZZ_PROG1_USB_PID 0xBA02 + +/* + * Intrepid Control Systems (http://www.intrepidcs.com/) ValueCAN and NeoVI + */ +#define INTREPID_VID 0x093C +#define INTREPID_VALUECAN_PID 0x0601 +#define INTREPID_NEOVI_PID 0x0701 + +/* + * Definitions for ID TECH (www.idt-net.com) devices + */ +#define IDTECH_VID 0x0ACD /* ID TECH Vendor ID */ +#define IDTECH_IDT1221U_PID 0x0300 /* IDT1221U USB to RS-232 adapter */ + +/* + * Definitions for Omnidirectional Control Technology, Inc. devices + */ +#define OCT_VID 0x0B39 /* OCT vendor ID */ +/* Note: OCT US101 is also rebadged as Dick Smith Electronics (NZ) XH6381 */ +/* Also rebadged as Dick Smith Electronics (Aus) XH6451 */ +/* Also rebadged as SIIG Inc. model US2308 hardware version 1 */ +#define OCT_DK201_PID 0x0103 /* OCT DK201 USB docking station */ +#define OCT_US101_PID 0x0421 /* OCT US101 USB to RS-232 */ + +/* + * Definitions for Icom Inc. devices + */ +#define ICOM_VID 0x0C26 /* Icom vendor ID */ +/* Note: ID-1 is a communications transceiver for HAM-radio operators */ +#define ICOM_ID_1_PID 0x0004 /* ID-1 USB to RS-232 */ +/* Note: OPC is an Optional cable to connect an Icom Transceiver */ +#define ICOM_OPC_U_UC_PID 0x0018 /* OPC-478UC, OPC-1122U cloning cable */ +/* Note: ID-RP* devices are Icom Repeater Devices for HAM-radio */ +#define ICOM_ID_RP2C1_PID 0x0009 /* ID-RP2C Asset 1 to RS-232 */ +#define ICOM_ID_RP2C2_PID 0x000A /* ID-RP2C Asset 2 to RS-232 */ +#define ICOM_ID_RP2D_PID 0x000B /* ID-RP2D configuration port*/ +#define ICOM_ID_RP2VT_PID 0x000C /* ID-RP2V Transmit config port */ +#define ICOM_ID_RP2VR_PID 0x000D /* ID-RP2V Receive config port */ +#define ICOM_ID_RP4KVT_PID 0x0010 /* ID-RP4000V Transmit config port */ +#define ICOM_ID_RP4KVR_PID 0x0011 /* ID-RP4000V Receive config port */ +#define ICOM_ID_RP2KVT_PID 0x0012 /* ID-RP2000V Transmit config port */ +#define ICOM_ID_RP2KVR_PID 0x0013 /* ID-RP2000V Receive config port */ + +/* + * GN Otometrics (http://www.otometrics.com) + * Submitted by Ville Sundberg. + */ +#define GN_OTOMETRICS_VID 0x0c33 /* Vendor ID */ +#define AURICAL_USB_PID 0x0010 /* Aurical USB Audiometer */ + +/* + * The following are the values for the Sealevel SeaLINK+ adapters. + * (Original list sent by Tuan Hoang. Ian Abbott renamed the macros and + * removed some PIDs that don't seem to match any existing products.) + */ +#define SEALEVEL_VID 0x0c52 /* Sealevel Vendor ID */ +#define SEALEVEL_2101_PID 0x2101 /* SeaLINK+232 (2101/2105) */ +#define SEALEVEL_2102_PID 0x2102 /* SeaLINK+485 (2102) */ +#define SEALEVEL_2103_PID 0x2103 /* SeaLINK+232I (2103) */ +#define SEALEVEL_2104_PID 0x2104 /* SeaLINK+485I (2104) */ +#define SEALEVEL_2106_PID 0x9020 /* SeaLINK+422 (2106) */ +#define SEALEVEL_2201_1_PID 0x2211 /* SeaPORT+2/232 (2201) Port 1 */ +#define SEALEVEL_2201_2_PID 0x2221 /* SeaPORT+2/232 (2201) Port 2 */ +#define SEALEVEL_2202_1_PID 0x2212 /* SeaPORT+2/485 (2202) Port 1 */ +#define SEALEVEL_2202_2_PID 0x2222 /* SeaPORT+2/485 (2202) Port 2 */ +#define SEALEVEL_2203_1_PID 0x2213 /* SeaPORT+2 (2203) Port 1 */ +#define SEALEVEL_2203_2_PID 0x2223 /* SeaPORT+2 (2203) Port 2 */ +#define SEALEVEL_2401_1_PID 0x2411 /* SeaPORT+4/232 (2401) Port 1 */ +#define SEALEVEL_2401_2_PID 0x2421 /* SeaPORT+4/232 (2401) Port 2 */ +#define SEALEVEL_2401_3_PID 0x2431 /* SeaPORT+4/232 (2401) Port 3 */ +#define SEALEVEL_2401_4_PID 0x2441 /* SeaPORT+4/232 (2401) Port 4 */ +#define SEALEVEL_2402_1_PID 0x2412 /* SeaPORT+4/485 (2402) Port 1 */ +#define SEALEVEL_2402_2_PID 0x2422 /* SeaPORT+4/485 (2402) Port 2 */ +#define SEALEVEL_2402_3_PID 0x2432 /* SeaPORT+4/485 (2402) Port 3 */ +#define SEALEVEL_2402_4_PID 0x2442 /* SeaPORT+4/485 (2402) Port 4 */ +#define SEALEVEL_2403_1_PID 0x2413 /* SeaPORT+4 (2403) Port 1 */ +#define SEALEVEL_2403_2_PID 0x2423 /* SeaPORT+4 (2403) Port 2 */ +#define SEALEVEL_2403_3_PID 0x2433 /* SeaPORT+4 (2403) Port 3 */ +#define SEALEVEL_2403_4_PID 0x2443 /* SeaPORT+4 (2403) Port 4 */ +#define SEALEVEL_2801_1_PID 0X2811 /* SeaLINK+8/232 (2801) Port 1 */ +#define SEALEVEL_2801_2_PID 0X2821 /* SeaLINK+8/232 (2801) Port 2 */ +#define SEALEVEL_2801_3_PID 0X2831 /* SeaLINK+8/232 (2801) Port 3 */ +#define SEALEVEL_2801_4_PID 0X2841 /* SeaLINK+8/232 (2801) Port 4 */ +#define SEALEVEL_2801_5_PID 0X2851 /* SeaLINK+8/232 (2801) Port 5 */ +#define SEALEVEL_2801_6_PID 0X2861 /* SeaLINK+8/232 (2801) Port 6 */ +#define SEALEVEL_2801_7_PID 0X2871 /* SeaLINK+8/232 (2801) Port 7 */ +#define SEALEVEL_2801_8_PID 0X2881 /* SeaLINK+8/232 (2801) Port 8 */ +#define SEALEVEL_2802_1_PID 0X2812 /* SeaLINK+8/485 (2802) Port 1 */ +#define SEALEVEL_2802_2_PID 0X2822 /* SeaLINK+8/485 (2802) Port 2 */ +#define SEALEVEL_2802_3_PID 0X2832 /* SeaLINK+8/485 (2802) Port 3 */ +#define SEALEVEL_2802_4_PID 0X2842 /* SeaLINK+8/485 (2802) Port 4 */ +#define SEALEVEL_2802_5_PID 0X2852 /* SeaLINK+8/485 (2802) Port 5 */ +#define SEALEVEL_2802_6_PID 0X2862 /* SeaLINK+8/485 (2802) Port 6 */ +#define SEALEVEL_2802_7_PID 0X2872 /* SeaLINK+8/485 (2802) Port 7 */ +#define SEALEVEL_2802_8_PID 0X2882 /* SeaLINK+8/485 (2802) Port 8 */ +#define SEALEVEL_2803_1_PID 0X2813 /* SeaLINK+8 (2803) Port 1 */ +#define SEALEVEL_2803_2_PID 0X2823 /* SeaLINK+8 (2803) Port 2 */ +#define SEALEVEL_2803_3_PID 0X2833 /* SeaLINK+8 (2803) Port 3 */ +#define SEALEVEL_2803_4_PID 0X2843 /* SeaLINK+8 (2803) Port 4 */ +#define SEALEVEL_2803_5_PID 0X2853 /* SeaLINK+8 (2803) Port 5 */ +#define SEALEVEL_2803_6_PID 0X2863 /* SeaLINK+8 (2803) Port 6 */ +#define SEALEVEL_2803_7_PID 0X2873 /* SeaLINK+8 (2803) Port 7 */ +#define SEALEVEL_2803_8_PID 0X2883 /* SeaLINK+8 (2803) Port 8 */ +#define SEALEVEL_2803R_1_PID 0Xa02a /* SeaLINK+8 (2803-ROHS) Port 1+2 */ +#define SEALEVEL_2803R_2_PID 0Xa02b /* SeaLINK+8 (2803-ROHS) Port 3+4 */ +#define SEALEVEL_2803R_3_PID 0Xa02c /* SeaLINK+8 (2803-ROHS) Port 5+6 */ +#define SEALEVEL_2803R_4_PID 0Xa02d /* SeaLINK+8 (2803-ROHS) Port 7+8 */ + +/* + * JETI SPECTROMETER SPECBOS 1201 + * http://www.jeti.com/cms/index.php/instruments/other-instruments/specbos-2101 + */ +#define JETI_VID 0x0c6c +#define JETI_SPC1201_PID 0x04b2 + +/* + * FTDI USB UART chips used in construction projects from the + * Elektor Electronics magazine (http://www.elektor.com/) + */ +#define ELEKTOR_VID 0x0C7D +#define ELEKTOR_FT323R_PID 0x0005 /* RFID-Reader, issue 09-2006 */ + +/* + * Posiflex inc retail equipment (http://www.posiflex.com.tw) + */ +#define POSIFLEX_VID 0x0d3a /* Vendor ID */ +#define POSIFLEX_PP7000_PID 0x0300 /* PP-7000II thermal printer */ + +/* + * The following are the values for two KOBIL chipcard terminals. + */ +#define KOBIL_VID 0x0d46 /* KOBIL Vendor ID */ +#define KOBIL_CONV_B1_PID 0x2020 /* KOBIL Konverter for B1 */ +#define KOBIL_CONV_KAAN_PID 0x2021 /* KOBIL_Konverter for KAAN */ + +#define FTDI_NF_RIC_VID 0x0DCD /* Vendor Id */ +#define FTDI_NF_RIC_PID 0x0001 /* Product Id */ + +/* + * Falcom Wireless Communications GmbH + */ +#define FALCOM_VID 0x0F94 /* Vendor Id */ +#define FALCOM_TWIST_PID 0x0001 /* Falcom Twist USB GPRS modem */ +#define FALCOM_SAMBA_PID 0x0005 /* Falcom Samba USB GPRS modem */ + +/* Larsen and Brusgaard AltiTrack/USBtrack */ +#define LARSENBRUSGAARD_VID 0x0FD8 +#define LB_ALTITRACK_PID 0x0001 + +/* + * TTi (Thurlby Thandar Instruments) + */ +#define TTI_VID 0x103E /* Vendor Id */ +#define TTI_QL355P_PID 0x03E8 /* TTi QL355P power supply */ + +/* Interbiometrics USB I/O Board */ +/* Developed for Interbiometrics by Rudolf Gugler */ +#define INTERBIOMETRICS_VID 0x1209 +#define INTERBIOMETRICS_IOBOARD_PID 0x1002 +#define INTERBIOMETRICS_MINI_IOBOARD_PID 0x1006 + +/* + * Testo products (http://www.testo.com/) + * Submitted by Colin Leroy + */ +#define TESTO_VID 0x128D +#define TESTO_USB_INTERFACE_PID 0x0001 + +/* + * Mobility Electronics products. + */ +#define MOBILITY_VID 0x1342 +#define MOBILITY_USB_SERIAL_PID 0x0202 /* EasiDock USB 200 serial */ + +/* + * FIC / OpenMoko, Inc. http://wiki.openmoko.org/wiki/Neo1973_Debug_Board_v3 + * Submitted by Harald Welte <laforge@openmoko.org> + */ +#define FIC_VID 0x1457 +#define FIC_NEO1973_DEBUG_PID 0x5118 + +/* Olimex */ +#define OLIMEX_VID 0x15BA +#define OLIMEX_ARM_USB_OCD_PID 0x0003 +#define OLIMEX_ARM_USB_OCD_H_PID 0x002b + +/* + * Telldus Technologies + */ +#define TELLDUS_VID 0x1781 /* Vendor ID */ +#define TELLDUS_TELLSTICK_PID 0x0C30 /* RF control dongle 433 MHz using FT232RL */ + +/* + * RT Systems programming cables for various ham radios + */ +#define RTSYSTEMS_VID 0x2100 /* Vendor ID */ +#define RTSYSTEMS_SERIAL_VX7_PID 0x9e52 /* Serial converter for VX-7 Radios using FT232RL */ +#define RTSYSTEMS_CT29B_PID 0x9e54 /* CT29B Radio Cable */ +#define RTSYSTEMS_RTS01_PID 0x9e57 /* USB-RTS01 Radio Cable */ + + +/* + * Physik Instrumente + * http://www.physikinstrumente.com/en/products/ + */ +/* These two devices use the VID of FTDI */ +#define PI_C865_PID 0xe0a0 /* PI C-865 Piezomotor Controller */ +#define PI_C857_PID 0xe0a1 /* PI Encoder Trigger Box */ + +#define PI_VID 0x1a72 /* Vendor ID */ +#define PI_C866_PID 0x1000 /* PI C-866 Piezomotor Controller */ +#define PI_C663_PID 0x1001 /* PI C-663 Mercury-Step */ +#define PI_C725_PID 0x1002 /* PI C-725 Piezomotor Controller */ +#define PI_E517_PID 0x1005 /* PI E-517 Digital Piezo Controller Operation Module */ +#define PI_C863_PID 0x1007 /* PI C-863 */ +#define PI_E861_PID 0x1008 /* PI E-861 Piezomotor Controller */ +#define PI_C867_PID 0x1009 /* PI C-867 Piezomotor Controller */ +#define PI_E609_PID 0x100D /* PI E-609 Digital Piezo Controller */ +#define PI_E709_PID 0x100E /* PI E-709 Digital Piezo Controller */ +#define PI_100F_PID 0x100F /* PI Digital Piezo Controller */ +#define PI_1011_PID 0x1011 /* PI Digital Piezo Controller */ +#define PI_1012_PID 0x1012 /* PI Motion Controller */ +#define PI_1013_PID 0x1013 /* PI Motion Controller */ +#define PI_1014_PID 0x1014 /* PI Device */ +#define PI_1015_PID 0x1015 /* PI Device */ +#define PI_1016_PID 0x1016 /* PI Digital Servo Module */ + +/* + * Kondo Kagaku Co.Ltd. + * http://www.kondo-robot.com/EN + */ +#define KONDO_VID 0x165c +#define KONDO_USB_SERIAL_PID 0x0002 + +/* + * Bayer Ascensia Contour blood glucose meter USB-converter cable. + * http://winglucofacts.com/cables/ + */ +#define BAYER_VID 0x1A79 +#define BAYER_CONTOUR_CABLE_PID 0x6001 + +/* + * The following are the values for the Matrix Orbital FTDI Range + * Anything in this range will use an FT232RL. + */ +#define MTXORB_VID 0x1B3D +#define MTXORB_FTDI_RANGE_0100_PID 0x0100 +#define MTXORB_FTDI_RANGE_0101_PID 0x0101 +#define MTXORB_FTDI_RANGE_0102_PID 0x0102 +#define MTXORB_FTDI_RANGE_0103_PID 0x0103 +#define MTXORB_FTDI_RANGE_0104_PID 0x0104 +#define MTXORB_FTDI_RANGE_0105_PID 0x0105 +#define MTXORB_FTDI_RANGE_0106_PID 0x0106 +#define MTXORB_FTDI_RANGE_0107_PID 0x0107 +#define MTXORB_FTDI_RANGE_0108_PID 0x0108 +#define MTXORB_FTDI_RANGE_0109_PID 0x0109 +#define MTXORB_FTDI_RANGE_010A_PID 0x010A +#define MTXORB_FTDI_RANGE_010B_PID 0x010B +#define MTXORB_FTDI_RANGE_010C_PID 0x010C +#define MTXORB_FTDI_RANGE_010D_PID 0x010D +#define MTXORB_FTDI_RANGE_010E_PID 0x010E +#define MTXORB_FTDI_RANGE_010F_PID 0x010F +#define MTXORB_FTDI_RANGE_0110_PID 0x0110 +#define MTXORB_FTDI_RANGE_0111_PID 0x0111 +#define MTXORB_FTDI_RANGE_0112_PID 0x0112 +#define MTXORB_FTDI_RANGE_0113_PID 0x0113 +#define MTXORB_FTDI_RANGE_0114_PID 0x0114 +#define MTXORB_FTDI_RANGE_0115_PID 0x0115 +#define MTXORB_FTDI_RANGE_0116_PID 0x0116 +#define MTXORB_FTDI_RANGE_0117_PID 0x0117 +#define MTXORB_FTDI_RANGE_0118_PID 0x0118 +#define MTXORB_FTDI_RANGE_0119_PID 0x0119 +#define MTXORB_FTDI_RANGE_011A_PID 0x011A +#define MTXORB_FTDI_RANGE_011B_PID 0x011B +#define MTXORB_FTDI_RANGE_011C_PID 0x011C +#define MTXORB_FTDI_RANGE_011D_PID 0x011D +#define MTXORB_FTDI_RANGE_011E_PID 0x011E +#define MTXORB_FTDI_RANGE_011F_PID 0x011F +#define MTXORB_FTDI_RANGE_0120_PID 0x0120 +#define MTXORB_FTDI_RANGE_0121_PID 0x0121 +#define MTXORB_FTDI_RANGE_0122_PID 0x0122 +#define MTXORB_FTDI_RANGE_0123_PID 0x0123 +#define MTXORB_FTDI_RANGE_0124_PID 0x0124 +#define MTXORB_FTDI_RANGE_0125_PID 0x0125 +#define MTXORB_FTDI_RANGE_0126_PID 0x0126 +#define MTXORB_FTDI_RANGE_0127_PID 0x0127 +#define MTXORB_FTDI_RANGE_0128_PID 0x0128 +#define MTXORB_FTDI_RANGE_0129_PID 0x0129 +#define MTXORB_FTDI_RANGE_012A_PID 0x012A +#define MTXORB_FTDI_RANGE_012B_PID 0x012B +#define MTXORB_FTDI_RANGE_012C_PID 0x012C +#define MTXORB_FTDI_RANGE_012D_PID 0x012D +#define MTXORB_FTDI_RANGE_012E_PID 0x012E +#define MTXORB_FTDI_RANGE_012F_PID 0x012F +#define MTXORB_FTDI_RANGE_0130_PID 0x0130 +#define MTXORB_FTDI_RANGE_0131_PID 0x0131 +#define MTXORB_FTDI_RANGE_0132_PID 0x0132 +#define MTXORB_FTDI_RANGE_0133_PID 0x0133 +#define MTXORB_FTDI_RANGE_0134_PID 0x0134 +#define MTXORB_FTDI_RANGE_0135_PID 0x0135 +#define MTXORB_FTDI_RANGE_0136_PID 0x0136 +#define MTXORB_FTDI_RANGE_0137_PID 0x0137 +#define MTXORB_FTDI_RANGE_0138_PID 0x0138 +#define MTXORB_FTDI_RANGE_0139_PID 0x0139 +#define MTXORB_FTDI_RANGE_013A_PID 0x013A +#define MTXORB_FTDI_RANGE_013B_PID 0x013B +#define MTXORB_FTDI_RANGE_013C_PID 0x013C +#define MTXORB_FTDI_RANGE_013D_PID 0x013D +#define MTXORB_FTDI_RANGE_013E_PID 0x013E +#define MTXORB_FTDI_RANGE_013F_PID 0x013F +#define MTXORB_FTDI_RANGE_0140_PID 0x0140 +#define MTXORB_FTDI_RANGE_0141_PID 0x0141 +#define MTXORB_FTDI_RANGE_0142_PID 0x0142 +#define MTXORB_FTDI_RANGE_0143_PID 0x0143 +#define MTXORB_FTDI_RANGE_0144_PID 0x0144 +#define MTXORB_FTDI_RANGE_0145_PID 0x0145 +#define MTXORB_FTDI_RANGE_0146_PID 0x0146 +#define MTXORB_FTDI_RANGE_0147_PID 0x0147 +#define MTXORB_FTDI_RANGE_0148_PID 0x0148 +#define MTXORB_FTDI_RANGE_0149_PID 0x0149 +#define MTXORB_FTDI_RANGE_014A_PID 0x014A +#define MTXORB_FTDI_RANGE_014B_PID 0x014B +#define MTXORB_FTDI_RANGE_014C_PID 0x014C +#define MTXORB_FTDI_RANGE_014D_PID 0x014D +#define MTXORB_FTDI_RANGE_014E_PID 0x014E +#define MTXORB_FTDI_RANGE_014F_PID 0x014F +#define MTXORB_FTDI_RANGE_0150_PID 0x0150 +#define MTXORB_FTDI_RANGE_0151_PID 0x0151 +#define MTXORB_FTDI_RANGE_0152_PID 0x0152 +#define MTXORB_FTDI_RANGE_0153_PID 0x0153 +#define MTXORB_FTDI_RANGE_0154_PID 0x0154 +#define MTXORB_FTDI_RANGE_0155_PID 0x0155 +#define MTXORB_FTDI_RANGE_0156_PID 0x0156 +#define MTXORB_FTDI_RANGE_0157_PID 0x0157 +#define MTXORB_FTDI_RANGE_0158_PID 0x0158 +#define MTXORB_FTDI_RANGE_0159_PID 0x0159 +#define MTXORB_FTDI_RANGE_015A_PID 0x015A +#define MTXORB_FTDI_RANGE_015B_PID 0x015B +#define MTXORB_FTDI_RANGE_015C_PID 0x015C +#define MTXORB_FTDI_RANGE_015D_PID 0x015D +#define MTXORB_FTDI_RANGE_015E_PID 0x015E +#define MTXORB_FTDI_RANGE_015F_PID 0x015F +#define MTXORB_FTDI_RANGE_0160_PID 0x0160 +#define MTXORB_FTDI_RANGE_0161_PID 0x0161 +#define MTXORB_FTDI_RANGE_0162_PID 0x0162 +#define MTXORB_FTDI_RANGE_0163_PID 0x0163 +#define MTXORB_FTDI_RANGE_0164_PID 0x0164 +#define MTXORB_FTDI_RANGE_0165_PID 0x0165 +#define MTXORB_FTDI_RANGE_0166_PID 0x0166 +#define MTXORB_FTDI_RANGE_0167_PID 0x0167 +#define MTXORB_FTDI_RANGE_0168_PID 0x0168 +#define MTXORB_FTDI_RANGE_0169_PID 0x0169 +#define MTXORB_FTDI_RANGE_016A_PID 0x016A +#define MTXORB_FTDI_RANGE_016B_PID 0x016B +#define MTXORB_FTDI_RANGE_016C_PID 0x016C +#define MTXORB_FTDI_RANGE_016D_PID 0x016D +#define MTXORB_FTDI_RANGE_016E_PID 0x016E +#define MTXORB_FTDI_RANGE_016F_PID 0x016F +#define MTXORB_FTDI_RANGE_0170_PID 0x0170 +#define MTXORB_FTDI_RANGE_0171_PID 0x0171 +#define MTXORB_FTDI_RANGE_0172_PID 0x0172 +#define MTXORB_FTDI_RANGE_0173_PID 0x0173 +#define MTXORB_FTDI_RANGE_0174_PID 0x0174 +#define MTXORB_FTDI_RANGE_0175_PID 0x0175 +#define MTXORB_FTDI_RANGE_0176_PID 0x0176 +#define MTXORB_FTDI_RANGE_0177_PID 0x0177 +#define MTXORB_FTDI_RANGE_0178_PID 0x0178 +#define MTXORB_FTDI_RANGE_0179_PID 0x0179 +#define MTXORB_FTDI_RANGE_017A_PID 0x017A +#define MTXORB_FTDI_RANGE_017B_PID 0x017B +#define MTXORB_FTDI_RANGE_017C_PID 0x017C +#define MTXORB_FTDI_RANGE_017D_PID 0x017D +#define MTXORB_FTDI_RANGE_017E_PID 0x017E +#define MTXORB_FTDI_RANGE_017F_PID 0x017F +#define MTXORB_FTDI_RANGE_0180_PID 0x0180 +#define MTXORB_FTDI_RANGE_0181_PID 0x0181 +#define MTXORB_FTDI_RANGE_0182_PID 0x0182 +#define MTXORB_FTDI_RANGE_0183_PID 0x0183 +#define MTXORB_FTDI_RANGE_0184_PID 0x0184 +#define MTXORB_FTDI_RANGE_0185_PID 0x0185 +#define MTXORB_FTDI_RANGE_0186_PID 0x0186 +#define MTXORB_FTDI_RANGE_0187_PID 0x0187 +#define MTXORB_FTDI_RANGE_0188_PID 0x0188 +#define MTXORB_FTDI_RANGE_0189_PID 0x0189 +#define MTXORB_FTDI_RANGE_018A_PID 0x018A +#define MTXORB_FTDI_RANGE_018B_PID 0x018B +#define MTXORB_FTDI_RANGE_018C_PID 0x018C +#define MTXORB_FTDI_RANGE_018D_PID 0x018D +#define MTXORB_FTDI_RANGE_018E_PID 0x018E +#define MTXORB_FTDI_RANGE_018F_PID 0x018F +#define MTXORB_FTDI_RANGE_0190_PID 0x0190 +#define MTXORB_FTDI_RANGE_0191_PID 0x0191 +#define MTXORB_FTDI_RANGE_0192_PID 0x0192 +#define MTXORB_FTDI_RANGE_0193_PID 0x0193 +#define MTXORB_FTDI_RANGE_0194_PID 0x0194 +#define MTXORB_FTDI_RANGE_0195_PID 0x0195 +#define MTXORB_FTDI_RANGE_0196_PID 0x0196 +#define MTXORB_FTDI_RANGE_0197_PID 0x0197 +#define MTXORB_FTDI_RANGE_0198_PID 0x0198 +#define MTXORB_FTDI_RANGE_0199_PID 0x0199 +#define MTXORB_FTDI_RANGE_019A_PID 0x019A +#define MTXORB_FTDI_RANGE_019B_PID 0x019B +#define MTXORB_FTDI_RANGE_019C_PID 0x019C +#define MTXORB_FTDI_RANGE_019D_PID 0x019D +#define MTXORB_FTDI_RANGE_019E_PID 0x019E +#define MTXORB_FTDI_RANGE_019F_PID 0x019F +#define MTXORB_FTDI_RANGE_01A0_PID 0x01A0 +#define MTXORB_FTDI_RANGE_01A1_PID 0x01A1 +#define MTXORB_FTDI_RANGE_01A2_PID 0x01A2 +#define MTXORB_FTDI_RANGE_01A3_PID 0x01A3 +#define MTXORB_FTDI_RANGE_01A4_PID 0x01A4 +#define MTXORB_FTDI_RANGE_01A5_PID 0x01A5 +#define MTXORB_FTDI_RANGE_01A6_PID 0x01A6 +#define MTXORB_FTDI_RANGE_01A7_PID 0x01A7 +#define MTXORB_FTDI_RANGE_01A8_PID 0x01A8 +#define MTXORB_FTDI_RANGE_01A9_PID 0x01A9 +#define MTXORB_FTDI_RANGE_01AA_PID 0x01AA +#define MTXORB_FTDI_RANGE_01AB_PID 0x01AB +#define MTXORB_FTDI_RANGE_01AC_PID 0x01AC +#define MTXORB_FTDI_RANGE_01AD_PID 0x01AD +#define MTXORB_FTDI_RANGE_01AE_PID 0x01AE +#define MTXORB_FTDI_RANGE_01AF_PID 0x01AF +#define MTXORB_FTDI_RANGE_01B0_PID 0x01B0 +#define MTXORB_FTDI_RANGE_01B1_PID 0x01B1 +#define MTXORB_FTDI_RANGE_01B2_PID 0x01B2 +#define MTXORB_FTDI_RANGE_01B3_PID 0x01B3 +#define MTXORB_FTDI_RANGE_01B4_PID 0x01B4 +#define MTXORB_FTDI_RANGE_01B5_PID 0x01B5 +#define MTXORB_FTDI_RANGE_01B6_PID 0x01B6 +#define MTXORB_FTDI_RANGE_01B7_PID 0x01B7 +#define MTXORB_FTDI_RANGE_01B8_PID 0x01B8 +#define MTXORB_FTDI_RANGE_01B9_PID 0x01B9 +#define MTXORB_FTDI_RANGE_01BA_PID 0x01BA +#define MTXORB_FTDI_RANGE_01BB_PID 0x01BB +#define MTXORB_FTDI_RANGE_01BC_PID 0x01BC +#define MTXORB_FTDI_RANGE_01BD_PID 0x01BD +#define MTXORB_FTDI_RANGE_01BE_PID 0x01BE +#define MTXORB_FTDI_RANGE_01BF_PID 0x01BF +#define MTXORB_FTDI_RANGE_01C0_PID 0x01C0 +#define MTXORB_FTDI_RANGE_01C1_PID 0x01C1 +#define MTXORB_FTDI_RANGE_01C2_PID 0x01C2 +#define MTXORB_FTDI_RANGE_01C3_PID 0x01C3 +#define MTXORB_FTDI_RANGE_01C4_PID 0x01C4 +#define MTXORB_FTDI_RANGE_01C5_PID 0x01C5 +#define MTXORB_FTDI_RANGE_01C6_PID 0x01C6 +#define MTXORB_FTDI_RANGE_01C7_PID 0x01C7 +#define MTXORB_FTDI_RANGE_01C8_PID 0x01C8 +#define MTXORB_FTDI_RANGE_01C9_PID 0x01C9 +#define MTXORB_FTDI_RANGE_01CA_PID 0x01CA +#define MTXORB_FTDI_RANGE_01CB_PID 0x01CB +#define MTXORB_FTDI_RANGE_01CC_PID 0x01CC +#define MTXORB_FTDI_RANGE_01CD_PID 0x01CD +#define MTXORB_FTDI_RANGE_01CE_PID 0x01CE +#define MTXORB_FTDI_RANGE_01CF_PID 0x01CF +#define MTXORB_FTDI_RANGE_01D0_PID 0x01D0 +#define MTXORB_FTDI_RANGE_01D1_PID 0x01D1 +#define MTXORB_FTDI_RANGE_01D2_PID 0x01D2 +#define MTXORB_FTDI_RANGE_01D3_PID 0x01D3 +#define MTXORB_FTDI_RANGE_01D4_PID 0x01D4 +#define MTXORB_FTDI_RANGE_01D5_PID 0x01D5 +#define MTXORB_FTDI_RANGE_01D6_PID 0x01D6 +#define MTXORB_FTDI_RANGE_01D7_PID 0x01D7 +#define MTXORB_FTDI_RANGE_01D8_PID 0x01D8 +#define MTXORB_FTDI_RANGE_01D9_PID 0x01D9 +#define MTXORB_FTDI_RANGE_01DA_PID 0x01DA +#define MTXORB_FTDI_RANGE_01DB_PID 0x01DB +#define MTXORB_FTDI_RANGE_01DC_PID 0x01DC +#define MTXORB_FTDI_RANGE_01DD_PID 0x01DD +#define MTXORB_FTDI_RANGE_01DE_PID 0x01DE +#define MTXORB_FTDI_RANGE_01DF_PID 0x01DF +#define MTXORB_FTDI_RANGE_01E0_PID 0x01E0 +#define MTXORB_FTDI_RANGE_01E1_PID 0x01E1 +#define MTXORB_FTDI_RANGE_01E2_PID 0x01E2 +#define MTXORB_FTDI_RANGE_01E3_PID 0x01E3 +#define MTXORB_FTDI_RANGE_01E4_PID 0x01E4 +#define MTXORB_FTDI_RANGE_01E5_PID 0x01E5 +#define MTXORB_FTDI_RANGE_01E6_PID 0x01E6 +#define MTXORB_FTDI_RANGE_01E7_PID 0x01E7 +#define MTXORB_FTDI_RANGE_01E8_PID 0x01E8 +#define MTXORB_FTDI_RANGE_01E9_PID 0x01E9 +#define MTXORB_FTDI_RANGE_01EA_PID 0x01EA +#define MTXORB_FTDI_RANGE_01EB_PID 0x01EB +#define MTXORB_FTDI_RANGE_01EC_PID 0x01EC +#define MTXORB_FTDI_RANGE_01ED_PID 0x01ED +#define MTXORB_FTDI_RANGE_01EE_PID 0x01EE +#define MTXORB_FTDI_RANGE_01EF_PID 0x01EF +#define MTXORB_FTDI_RANGE_01F0_PID 0x01F0 +#define MTXORB_FTDI_RANGE_01F1_PID 0x01F1 +#define MTXORB_FTDI_RANGE_01F2_PID 0x01F2 +#define MTXORB_FTDI_RANGE_01F3_PID 0x01F3 +#define MTXORB_FTDI_RANGE_01F4_PID 0x01F4 +#define MTXORB_FTDI_RANGE_01F5_PID 0x01F5 +#define MTXORB_FTDI_RANGE_01F6_PID 0x01F6 +#define MTXORB_FTDI_RANGE_01F7_PID 0x01F7 +#define MTXORB_FTDI_RANGE_01F8_PID 0x01F8 +#define MTXORB_FTDI_RANGE_01F9_PID 0x01F9 +#define MTXORB_FTDI_RANGE_01FA_PID 0x01FA +#define MTXORB_FTDI_RANGE_01FB_PID 0x01FB +#define MTXORB_FTDI_RANGE_01FC_PID 0x01FC +#define MTXORB_FTDI_RANGE_01FD_PID 0x01FD +#define MTXORB_FTDI_RANGE_01FE_PID 0x01FE +#define MTXORB_FTDI_RANGE_01FF_PID 0x01FF + + + +/* + * The Mobility Lab (TML) + * Submitted by Pierre Castella + */ +#define TML_VID 0x1B91 /* Vendor ID */ +#define TML_USB_SERIAL_PID 0x0064 /* USB - Serial Converter */ + +/* Alti-2 products http://www.alti-2.com */ +#define ALTI2_VID 0x1BC9 +#define ALTI2_N3_PID 0x6001 /* Neptune 3 */ + +/* + * Ionics PlugComputer + */ +#define IONICS_VID 0x1c0c +#define IONICS_PLUGCOMPUTER_PID 0x0102 + +/* + * Dresden Elektronik Sensor Terminal Board + */ +#define DE_VID 0x1cf1 /* Vendor ID */ +#define STB_PID 0x0001 /* Sensor Terminal Board */ +#define WHT_PID 0x0004 /* Wireless Handheld Terminal */ + +/* + * STMicroelectonics + */ +#define ST_VID 0x0483 +#define ST_STMCLT1030_PID 0x3747 /* ST Micro Connect Lite STMCLT1030 */ + +/* + * Papouch products (http://www.papouch.com/) + * Submitted by Folkert van Heusden + */ + +#define PAPOUCH_VID 0x5050 /* Vendor ID */ +#define PAPOUCH_SB485_PID 0x0100 /* Papouch SB485 USB-485/422 Converter */ +#define PAPOUCH_AP485_PID 0x0101 /* AP485 USB-RS485 Converter */ +#define PAPOUCH_SB422_PID 0x0102 /* Papouch SB422 USB-RS422 Converter */ +#define PAPOUCH_SB485_2_PID 0x0103 /* Papouch SB485 USB-485/422 Converter */ +#define PAPOUCH_AP485_2_PID 0x0104 /* AP485 USB-RS485 Converter */ +#define PAPOUCH_SB422_2_PID 0x0105 /* Papouch SB422 USB-RS422 Converter */ +#define PAPOUCH_SB485S_PID 0x0106 /* Papouch SB485S USB-485/422 Converter */ +#define PAPOUCH_SB485C_PID 0x0107 /* Papouch SB485C USB-485/422 Converter */ +#define PAPOUCH_LEC_PID 0x0300 /* LEC USB Converter */ +#define PAPOUCH_SB232_PID 0x0301 /* Papouch SB232 USB-RS232 Converter */ +#define PAPOUCH_TMU_PID 0x0400 /* TMU USB Thermometer */ +#define PAPOUCH_IRAMP_PID 0x0500 /* Papouch IRAmp Duplex */ +#define PAPOUCH_DRAK5_PID 0x0700 /* Papouch DRAK5 */ +#define PAPOUCH_QUIDO8x8_PID 0x0800 /* Papouch Quido 8/8 Module */ +#define PAPOUCH_QUIDO4x4_PID 0x0900 /* Papouch Quido 4/4 Module */ +#define PAPOUCH_QUIDO2x2_PID 0x0a00 /* Papouch Quido 2/2 Module */ +#define PAPOUCH_QUIDO10x1_PID 0x0b00 /* Papouch Quido 10/1 Module */ +#define PAPOUCH_QUIDO30x3_PID 0x0c00 /* Papouch Quido 30/3 Module */ +#define PAPOUCH_QUIDO60x3_PID 0x0d00 /* Papouch Quido 60(100)/3 Module */ +#define PAPOUCH_QUIDO2x16_PID 0x0e00 /* Papouch Quido 2/16 Module */ +#define PAPOUCH_QUIDO3x32_PID 0x0f00 /* Papouch Quido 3/32 Module */ +#define PAPOUCH_DRAK6_PID 0x1000 /* Papouch DRAK6 */ +#define PAPOUCH_UPSUSB_PID 0x8000 /* Papouch UPS-USB adapter */ +#define PAPOUCH_MU_PID 0x8001 /* MU controller */ +#define PAPOUCH_SIMUKEY_PID 0x8002 /* Papouch SimuKey */ +#define PAPOUCH_AD4USB_PID 0x8003 /* AD4USB Measurement Module */ +#define PAPOUCH_GMUX_PID 0x8004 /* Papouch GOLIATH MUX */ +#define PAPOUCH_GMSR_PID 0x8005 /* Papouch GOLIATH MSR */ + +/* + * Marvell SheevaPlug + */ +#define MARVELL_VID 0x9e88 +#define MARVELL_SHEEVAPLUG_PID 0x9e8f + +/* + * Evolution Robotics products (http://www.evolution.com/). + * Submitted by Shawn M. Lavelle. + */ +#define EVOLUTION_VID 0xDEEE /* Vendor ID */ +#define EVOLUTION_ER1_PID 0x0300 /* ER1 Control Module */ +#define EVO_8U232AM_PID 0x02FF /* Evolution robotics RCM2 (FT232AM)*/ +#define EVO_HYBRID_PID 0x0302 /* Evolution robotics RCM4 PID (FT232BM)*/ +#define EVO_RCM4_PID 0x0303 /* Evolution robotics RCM4 PID */ + +/* + * MJS Gadgets HD Radio / XM Radio / Sirius Radio interfaces (using VID 0x0403) + */ +#define MJSG_GENERIC_PID 0x9378 +#define MJSG_SR_RADIO_PID 0x9379 +#define MJSG_XM_RADIO_PID 0x937A +#define MJSG_HD_RADIO_PID 0x937C + +/* + * D.O.Tec products (http://www.directout.eu) + */ +#define FTDI_DOTEC_PID 0x9868 + +/* + * Xverve Signalyzer tools (http://www.signalyzer.com/) + */ +#define XVERVE_SIGNALYZER_ST_PID 0xBCA0 +#define XVERVE_SIGNALYZER_SLITE_PID 0xBCA1 +#define XVERVE_SIGNALYZER_SH2_PID 0xBCA2 +#define XVERVE_SIGNALYZER_SH4_PID 0xBCA4 + +/* + * Segway Robotic Mobility Platform USB interface (using VID 0x0403) + * Submitted by John G. Rogers + */ +#define SEGWAY_RMP200_PID 0xe729 + + +/* + * Accesio USB Data Acquisition products (http://www.accesio.com/) + */ +#define ACCESIO_COM4SM_PID 0xD578 + +/* www.sciencescope.co.uk educational dataloggers */ +#define FTDI_SCIENCESCOPE_LOGBOOKML_PID 0xFF18 +#define FTDI_SCIENCESCOPE_LS_LOGBOOK_PID 0xFF1C +#define FTDI_SCIENCESCOPE_HS_LOGBOOK_PID 0xFF1D + +/* + * CTI GmbH RS485 Converter http://www.cti-lean.com/ + */ +/* USB-485-Mini*/ +#define FTDI_CTI_MINI_PID 0xF608 +/* USB-Nano-485*/ +#define FTDI_CTI_NANO_PID 0xF60B + +/* + * ZeitControl cardsystems GmbH rfid-readers http://zeitconrol.de + */ +/* TagTracer MIFARE*/ +#define FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID 0xF7C0 + +/* + * Rainforest Automation + */ +/* ZigBee controller */ +#define FTDI_RF_R106 0x8A28 + +/* + * Product: HCP HIT GPRS modem + * Manufacturer: HCP d.o.o. + * ATI command output: Cinterion MC55i + */ +#define FTDI_CINTERION_MC55I_PID 0xA951 diff --git a/hw/usb/quirks-pl2303-ids.h b/hw/usb/quirks-pl2303-ids.h new file mode 100644 index 000000000..8dbdb46ff --- /dev/null +++ b/hw/usb/quirks-pl2303-ids.h @@ -0,0 +1,150 @@ +/* + * Prolific PL2303 USB to serial adaptor driver header file + * + * 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 of the License, or + * (at your option) any later version. + * + */ + +#define BENQ_VENDOR_ID 0x04a5 +#define BENQ_PRODUCT_ID_S81 0x4027 + +#define PL2303_VENDOR_ID 0x067b +#define PL2303_PRODUCT_ID 0x2303 +#define PL2303_PRODUCT_ID_RSAQ2 0x04bb +#define PL2303_PRODUCT_ID_DCU11 0x1234 +#define PL2303_PRODUCT_ID_PHAROS 0xaaa0 +#define PL2303_PRODUCT_ID_RSAQ3 0xaaa2 +#define PL2303_PRODUCT_ID_ALDIGA 0x0611 +#define PL2303_PRODUCT_ID_MMX 0x0612 +#define PL2303_PRODUCT_ID_GPRS 0x0609 +#define PL2303_PRODUCT_ID_HCR331 0x331a +#define PL2303_PRODUCT_ID_MOTOROLA 0x0307 + +#define ATEN_VENDOR_ID 0x0557 +#define ATEN_VENDOR_ID2 0x0547 +#define ATEN_PRODUCT_ID 0x2008 + +#define IODATA_VENDOR_ID 0x04bb +#define IODATA_PRODUCT_ID 0x0a03 +#define IODATA_PRODUCT_ID_RSAQ5 0x0a0e + +#define ELCOM_VENDOR_ID 0x056e +#define ELCOM_PRODUCT_ID 0x5003 +#define ELCOM_PRODUCT_ID_UCSGT 0x5004 + +#define ITEGNO_VENDOR_ID 0x0eba +#define ITEGNO_PRODUCT_ID 0x1080 +#define ITEGNO_PRODUCT_ID_2080 0x2080 + +#define MA620_VENDOR_ID 0x0df7 +#define MA620_PRODUCT_ID 0x0620 + +#define RATOC_VENDOR_ID 0x0584 +#define RATOC_PRODUCT_ID 0xb000 + +#define TRIPP_VENDOR_ID 0x2478 +#define TRIPP_PRODUCT_ID 0x2008 + +#define RADIOSHACK_VENDOR_ID 0x1453 +#define RADIOSHACK_PRODUCT_ID 0x4026 + +#define DCU10_VENDOR_ID 0x0731 +#define DCU10_PRODUCT_ID 0x0528 + +#define SITECOM_VENDOR_ID 0x6189 +#define SITECOM_PRODUCT_ID 0x2068 + +/* Alcatel OT535/735 USB cable */ +#define ALCATEL_VENDOR_ID 0x11f7 +#define ALCATEL_PRODUCT_ID 0x02df + +/* Samsung I330 phone cradle */ +#define SAMSUNG_VENDOR_ID 0x04e8 +#define SAMSUNG_PRODUCT_ID 0x8001 + +#define SIEMENS_VENDOR_ID 0x11f5 +#define SIEMENS_PRODUCT_ID_SX1 0x0001 +#define SIEMENS_PRODUCT_ID_X65 0x0003 +#define SIEMENS_PRODUCT_ID_X75 0x0004 +#define SIEMENS_PRODUCT_ID_EF81 0x0005 + +#define SYNTECH_VENDOR_ID 0x0745 +#define SYNTECH_PRODUCT_ID 0x0001 + +/* Nokia CA-42 Cable */ +#define NOKIA_CA42_VENDOR_ID 0x078b +#define NOKIA_CA42_PRODUCT_ID 0x1234 + +/* CA-42 CLONE Cable www.ca-42.com chipset: Prolific Technology Inc */ +#define CA_42_CA42_VENDOR_ID 0x10b5 +#define CA_42_CA42_PRODUCT_ID 0xac70 + +#define SAGEM_VENDOR_ID 0x079b +#define SAGEM_PRODUCT_ID 0x0027 + +/* Leadtek GPS 9531 (ID 0413:2101) */ +#define LEADTEK_VENDOR_ID 0x0413 +#define LEADTEK_9531_PRODUCT_ID 0x2101 + +/* USB GSM cable from Speed Dragon Multimedia, Ltd */ +#define SPEEDDRAGON_VENDOR_ID 0x0e55 +#define SPEEDDRAGON_PRODUCT_ID 0x110b + +/* DATAPILOT Universal-2 Phone Cable */ +#define DATAPILOT_U2_VENDOR_ID 0x0731 +#define DATAPILOT_U2_PRODUCT_ID 0x2003 + +/* Belkin "F5U257" Serial Adapter */ +#define BELKIN_VENDOR_ID 0x050d +#define BELKIN_PRODUCT_ID 0x0257 + +/* Alcor Micro Corp. USB 2.0 TO RS-232 */ +#define ALCOR_VENDOR_ID 0x058F +#define ALCOR_PRODUCT_ID 0x9720 + +/* Willcom WS002IN Data Driver (by NetIndex Inc.) */ +#define WS002IN_VENDOR_ID 0x11f6 +#define WS002IN_PRODUCT_ID 0x2001 + +/* Corega CG-USBRS232R Serial Adapter */ +#define COREGA_VENDOR_ID 0x07aa +#define COREGA_PRODUCT_ID 0x002a + +/* Y.C. Cable U.S.A., Inc - USB to RS-232 */ +#define YCCABLE_VENDOR_ID 0x05ad +#define YCCABLE_PRODUCT_ID 0x0fba + +/* "Superial" USB - Serial */ +#define SUPERIAL_VENDOR_ID 0x5372 +#define SUPERIAL_PRODUCT_ID 0x2303 + +/* Hewlett-Packard LD220-HP POS Pole Display */ +#define HP_VENDOR_ID 0x03f0 +#define HP_LD220_PRODUCT_ID 0x3524 + +/* Cressi Edy (diving computer) PC interface */ +#define CRESSI_VENDOR_ID 0x04b8 +#define CRESSI_EDY_PRODUCT_ID 0x0521 + +/* Zeagle dive computer interface */ +#define ZEAGLE_VENDOR_ID 0x04b8 +#define ZEAGLE_N2ITION3_PRODUCT_ID 0x0522 + +/* Sony, USB data cable for CMD-Jxx mobile phones */ +#define SONY_VENDOR_ID 0x054c +#define SONY_QN3USB_PRODUCT_ID 0x0437 + +/* Sanwa KB-USB2 multimeter cable (ID: 11ad:0001) */ +#define SANWA_VENDOR_ID 0x11ad +#define SANWA_PRODUCT_ID 0x0001 + +/* ADLINK ND-6530 RS232,RS485 and RS422 adapter */ +#define ADLINK_VENDOR_ID 0x0b63 +#define ADLINK_ND6530_PRODUCT_ID 0x6530 + +/* SMART USB Serial Adapter */ +#define SMART_VENDOR_ID 0x0b8c +#define SMART_PRODUCT_ID 0x2303 diff --git a/hw/usb/quirks.c b/hw/usb/quirks.c new file mode 100644 index 000000000..23ea7a23e --- /dev/null +++ b/hw/usb/quirks.c @@ -0,0 +1,54 @@ +/* + * USB quirk handling + * + * Copyright (c) 2012 Red Hat, Inc. + * + * Red Hat Authors: + * Hans de Goede <hdegoede@redhat.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 of the License, or + * (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "quirks.h" +#include "hw/usb.h" + +static bool usb_id_match(const struct usb_device_id *ids, + uint16_t vendor_id, uint16_t product_id, + uint8_t interface_class, uint8_t interface_subclass, + uint8_t interface_protocol) { + int i; + + for (i = 0; ids[i].terminating_entry == 0; i++) { + if (ids[i].vendor_id == vendor_id && + ids[i].product_id == product_id && + (ids[i].interface_protocol_used == 0 || + (ids[i].interface_class == interface_class && + ids[i].interface_subclass == interface_subclass && + ids[i].interface_protocol == interface_protocol))) { + return true; + } + } + return false; +} + +int usb_get_quirks(uint16_t vendor_id, uint16_t product_id, + uint8_t interface_class, uint8_t interface_subclass, + uint8_t interface_protocol) +{ + int quirks = 0; + + if (usb_id_match(usbredir_raw_serial_ids, vendor_id, product_id, + interface_class, interface_subclass, interface_protocol)) { + quirks |= USB_QUIRK_BUFFER_BULK_IN; + } + if (usb_id_match(usbredir_ftdi_serial_ids, vendor_id, product_id, + interface_class, interface_subclass, interface_protocol)) { + quirks |= USB_QUIRK_BUFFER_BULK_IN | USB_QUIRK_IS_FTDI; + } + + return quirks; +} diff --git a/hw/usb/quirks.h b/hw/usb/quirks.h new file mode 100644 index 000000000..c3e595f40 --- /dev/null +++ b/hw/usb/quirks.h @@ -0,0 +1,918 @@ +/* + * USB quirk handling + * + * Copyright (c) 2012 Red Hat, Inc. + * + * Red Hat Authors: + * Hans de Goede <hdegoede@redhat.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 of the License, or + * (at your option) any later version. + */ + +#ifndef HW_USB_QUIRKS_H +#define HW_USB_QUIRKS_H + +/* 1 on 1 copy of linux/drivers/usb/serial/ftdi_sio_ids.h */ +#include "quirks-ftdi-ids.h" +/* 1 on 1 copy of linux/drivers/usb/serial/pl2303.h */ +#include "quirks-pl2303-ids.h" + +struct usb_device_id { + uint16_t vendor_id; + uint16_t product_id; + uint8_t interface_class; + uint8_t interface_subclass; + uint8_t interface_protocol; + uint8_t interface_protocol_used:1, + terminating_entry:1, + reserved:6; +}; + +#define USB_DEVICE(vendor, product) \ + .vendor_id = vendor, .product_id = product, .interface_protocol_used = 0, + +#define USB_DEVICE_AND_INTERFACE_INFO(vend, prod, iclass, isubclass, iproto) \ + .vendor_id = vend, .product_id = prod, .interface_class = iclass, \ + .interface_subclass = isubclass, .interface_protocol = iproto, \ + .interface_protocol_used = 1 + +static const struct usb_device_id usbredir_raw_serial_ids[] = { + /* + * Silicon Laboratories CP210x USB to RS232 serial adapter ids + * copied from linux/drivers/usb/serial/cp210x.c + * + * Copyright (C) 2005 Craig Shelley (craig@microtron.org.uk) + */ + { USB_DEVICE(0x045B, 0x0053) }, /* Renesas RX610 RX-Stick */ + { USB_DEVICE(0x0471, 0x066A) }, /* AKTAKOM ACE-1001 cable */ + { USB_DEVICE(0x0489, 0xE000) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */ + { USB_DEVICE(0x0489, 0xE003) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */ + { USB_DEVICE(0x0745, 0x1000) }, /* CipherLab USB CCD Barcode Scanner 1000 */ + { USB_DEVICE(0x08e6, 0x5501) }, /* Gemalto Prox-PU/CU contactless smartcard reader */ + { USB_DEVICE(0x08FD, 0x000A) }, /* Digianswer A/S , ZigBee/802.15.4 MAC Device */ + { USB_DEVICE(0x0BED, 0x1100) }, /* MEI (TM) Cashflow-SC Bill/Voucher Acceptor */ + { USB_DEVICE(0x0BED, 0x1101) }, /* MEI series 2000 Combo Acceptor */ + { USB_DEVICE(0x0FCF, 0x1003) }, /* Dynastream ANT development board */ + { USB_DEVICE(0x0FCF, 0x1004) }, /* Dynastream ANT2USB */ + { USB_DEVICE(0x0FCF, 0x1006) }, /* Dynastream ANT development board */ + { USB_DEVICE(0x10A6, 0xAA26) }, /* Knock-off DCU-11 cable */ + { USB_DEVICE(0x10AB, 0x10C5) }, /* Siemens MC60 Cable */ + { USB_DEVICE(0x10B5, 0xAC70) }, /* Nokia CA-42 USB */ + { USB_DEVICE(0x10C4, 0x0F91) }, /* Vstabi */ + { USB_DEVICE(0x10C4, 0x1101) }, /* Arkham Technology DS101 Bus Monitor */ + { USB_DEVICE(0x10C4, 0x1601) }, /* Arkham Technology DS101 Adapter */ + { USB_DEVICE(0x10C4, 0x800A) }, /* SPORTident BSM7-D-USB main station */ + { USB_DEVICE(0x10C4, 0x803B) }, /* Pololu USB-serial converter */ + { USB_DEVICE(0x10C4, 0x8044) }, /* Cygnal Debug Adapter */ + { USB_DEVICE(0x10C4, 0x804E) }, /* Software Bisque Paramount ME build-in converter */ + { USB_DEVICE(0x10C4, 0x8053) }, /* Enfora EDG1228 */ + { USB_DEVICE(0x10C4, 0x8054) }, /* Enfora GSM2228 */ + { USB_DEVICE(0x10C4, 0x8066) }, /* Argussoft In-System Programmer */ + { USB_DEVICE(0x10C4, 0x806F) }, /* IMS USB to RS422 Converter Cable */ + { USB_DEVICE(0x10C4, 0x807A) }, /* Crumb128 board */ + { USB_DEVICE(0x10C4, 0x80C4) }, /* Cygnal Integrated Products, Inc., Optris infrared thermometer */ + { USB_DEVICE(0x10C4, 0x80CA) }, /* Degree Controls Inc */ + { USB_DEVICE(0x10C4, 0x80DD) }, /* Tracient RFID */ + { USB_DEVICE(0x10C4, 0x80F6) }, /* Suunto sports instrument */ + { USB_DEVICE(0x10C4, 0x8115) }, /* Arygon NFC/Mifare Reader */ + { USB_DEVICE(0x10C4, 0x813D) }, /* Burnside Telecom Deskmobile */ + { USB_DEVICE(0x10C4, 0x813F) }, /* Tams Master Easy Control */ + { USB_DEVICE(0x10C4, 0x814A) }, /* West Mountain Radio RIGblaster P&P */ + { USB_DEVICE(0x10C4, 0x814B) }, /* West Mountain Radio RIGtalk */ + { USB_DEVICE(0x10C4, 0x8156) }, /* B&G H3000 link cable */ + { USB_DEVICE(0x10C4, 0x815E) }, /* Helicomm IP-Link 1220-DVM */ + { USB_DEVICE(0x10C4, 0x815F) }, /* Timewave HamLinkUSB */ + { USB_DEVICE(0x10C4, 0x818B) }, /* AVIT Research USB to TTL */ + { USB_DEVICE(0x10C4, 0x819F) }, /* MJS USB Toslink Switcher */ + { USB_DEVICE(0x10C4, 0x81A6) }, /* ThinkOptics WavIt */ + { USB_DEVICE(0x10C4, 0x81A9) }, /* Multiplex RC Interface */ + { USB_DEVICE(0x10C4, 0x81AC) }, /* MSD Dash Hawk */ + { USB_DEVICE(0x10C4, 0x81AD) }, /* INSYS USB Modem */ + { USB_DEVICE(0x10C4, 0x81C8) }, /* Lipowsky Industrie Elektronik GmbH, Baby-JTAG */ + { USB_DEVICE(0x10C4, 0x81E2) }, /* Lipowsky Industrie Elektronik GmbH, Baby-LIN */ + { USB_DEVICE(0x10C4, 0x81E7) }, /* Aerocomm Radio */ + { USB_DEVICE(0x10C4, 0x81E8) }, /* Zephyr Bioharness */ + { USB_DEVICE(0x10C4, 0x81F2) }, /* C1007 HF band RFID controller */ + { USB_DEVICE(0x10C4, 0x8218) }, /* Lipowsky Industrie Elektronik GmbH, HARP-1 */ + { USB_DEVICE(0x10C4, 0x822B) }, /* Modem EDGE(GSM) Comander 2 */ + { USB_DEVICE(0x10C4, 0x826B) }, /* Cygnal Integrated Products, Inc., Fasttrax GPS demonstration module */ + { USB_DEVICE(0x10C4, 0x8293) }, /* Telegesis ETRX2USB */ + { USB_DEVICE(0x10C4, 0x82F9) }, /* Procyon AVS */ + { USB_DEVICE(0x10C4, 0x8341) }, /* Siemens MC35PU GPRS Modem */ + { USB_DEVICE(0x10C4, 0x8382) }, /* Cygnal Integrated Products, Inc. */ + { USB_DEVICE(0x10C4, 0x83A8) }, /* Amber Wireless AMB2560 */ + { USB_DEVICE(0x10C4, 0x83D8) }, /* DekTec DTA Plus VHF/UHF Booster/Attenuator */ + { USB_DEVICE(0x10C4, 0x8411) }, /* Kyocera GPS Module */ + { USB_DEVICE(0x10C4, 0x8418) }, /* IRZ Automation Teleport SG-10 GSM/GPRS Modem */ + { USB_DEVICE(0x10C4, 0x846E) }, /* BEI USB Sensor Interface (VCP) */ + { USB_DEVICE(0x10C4, 0x8477) }, /* Balluff RFID */ + { USB_DEVICE(0x10C4, 0x85EA) }, /* AC-Services IBUS-IF */ + { USB_DEVICE(0x10C4, 0x85EB) }, /* AC-Services CIS-IBUS */ + { USB_DEVICE(0x10C4, 0x8664) }, /* AC-Services CAN-IF */ + { USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */ + { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA80) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */ + { USB_DEVICE(0x10C4, 0xF001) }, /* Elan Digital Systems USBscope50 */ + { USB_DEVICE(0x10C4, 0xF002) }, /* Elan Digital Systems USBwave12 */ + { USB_DEVICE(0x10C4, 0xF003) }, /* Elan Digital Systems USBpulse100 */ + { USB_DEVICE(0x10C4, 0xF004) }, /* Elan Digital Systems USBcount50 */ + { USB_DEVICE(0x10C5, 0xEA61) }, /* Silicon Labs MobiData GPRS USB Modem */ + { USB_DEVICE(0x10CE, 0xEA6A) }, /* Silicon Labs MobiData GPRS USB Modem 100EU */ + { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */ + { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */ + { USB_DEVICE(0x166A, 0x0201) }, /* Clipsal 5500PACA C-Bus Pascal Automation Controller */ + { USB_DEVICE(0x166A, 0x0301) }, /* Clipsal 5800PC C-Bus Wireless PC Interface */ + { USB_DEVICE(0x166A, 0x0303) }, /* Clipsal 5500PCU C-Bus USB interface */ + { USB_DEVICE(0x166A, 0x0304) }, /* Clipsal 5000CT2 C-Bus Black and White Touchscreen */ + { USB_DEVICE(0x166A, 0x0305) }, /* Clipsal C-5000CT2 C-Bus Spectrum Colour Touchscreen */ + { USB_DEVICE(0x166A, 0x0401) }, /* Clipsal L51xx C-Bus Architectural Dimmer */ + { USB_DEVICE(0x166A, 0x0101) }, /* Clipsal 5560884 C-Bus Multi-room Audio Matrix Switcher */ + { USB_DEVICE(0x16D6, 0x0001) }, /* Jablotron serial interface */ + { USB_DEVICE(0x16DC, 0x0010) }, /* W-IE-NE-R Plein & Baus GmbH PL512 Power Supply */ + { USB_DEVICE(0x16DC, 0x0011) }, /* W-IE-NE-R Plein & Baus GmbH RCM Remote Control for MARATON Power Supply */ + { USB_DEVICE(0x16DC, 0x0012) }, /* W-IE-NE-R Plein & Baus GmbH MPOD Multi Channel Power Supply */ + { USB_DEVICE(0x16DC, 0x0015) }, /* W-IE-NE-R Plein & Baus GmbH CML Control, Monitoring and Data Logger */ + { USB_DEVICE(0x17A8, 0x0001) }, /* Kamstrup Optical Eye/3-wire */ + { USB_DEVICE(0x17A8, 0x0005) }, /* Kamstrup M-Bus Master MultiPort 250D */ + { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */ + { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */ + { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */ + { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ + { USB_DEVICE(0x1E29, 0x0102) }, /* Festo CPX-USB */ + { USB_DEVICE(0x1E29, 0x0501) }, /* Festo CMSP */ + { USB_DEVICE(0x3195, 0xF190) }, /* Link Instruments MSO-19 */ + { USB_DEVICE(0x3195, 0xF280) }, /* Link Instruments MSO-28 */ + { USB_DEVICE(0x3195, 0xF281) }, /* Link Instruments MSO-28 */ + { USB_DEVICE(0x413C, 0x9500) }, /* DW700 GPS USB interface */ + + /* + * Prolific pl2303 USB to RS232 serial adapter ids + * copied from linux/drivers/usb/serial/pl2303.c + * + * Copyright (C) 2001-2007 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2003 IBM Corp. + */ + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_DCU11) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ3) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_PHAROS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ALDIGA) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MMX) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GPRS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_HCR331) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MOTOROLA) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) }, + { USB_DEVICE(ATEN_VENDOR_ID2, ATEN_PRODUCT_ID) }, + { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID) }, + { USB_DEVICE(ELCOM_VENDOR_ID, ELCOM_PRODUCT_ID_UCSGT) }, + { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID) }, + { USB_DEVICE(ITEGNO_VENDOR_ID, ITEGNO_PRODUCT_ID_2080) }, + { USB_DEVICE(MA620_VENDOR_ID, MA620_PRODUCT_ID) }, + { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID) }, + { USB_DEVICE(TRIPP_VENDOR_ID, TRIPP_PRODUCT_ID) }, + { USB_DEVICE(RADIOSHACK_VENDOR_ID, RADIOSHACK_PRODUCT_ID) }, + { USB_DEVICE(DCU10_VENDOR_ID, DCU10_PRODUCT_ID) }, + { USB_DEVICE(SITECOM_VENDOR_ID, SITECOM_PRODUCT_ID) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_ID) }, + { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_ID) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_SX1) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X65) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75) }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81) }, + { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_ID_S81) }, /* Benq/Siemens S81 */ + { USB_DEVICE(SYNTECH_VENDOR_ID, SYNTECH_PRODUCT_ID) }, + { USB_DEVICE(NOKIA_CA42_VENDOR_ID, NOKIA_CA42_PRODUCT_ID) }, + { USB_DEVICE(CA_42_CA42_VENDOR_ID, CA_42_CA42_PRODUCT_ID) }, + { USB_DEVICE(SAGEM_VENDOR_ID, SAGEM_PRODUCT_ID) }, + { USB_DEVICE(LEADTEK_VENDOR_ID, LEADTEK_9531_PRODUCT_ID) }, + { USB_DEVICE(SPEEDDRAGON_VENDOR_ID, SPEEDDRAGON_PRODUCT_ID) }, + { USB_DEVICE(DATAPILOT_U2_VENDOR_ID, DATAPILOT_U2_PRODUCT_ID) }, + { USB_DEVICE(BELKIN_VENDOR_ID, BELKIN_PRODUCT_ID) }, + { USB_DEVICE(ALCOR_VENDOR_ID, ALCOR_PRODUCT_ID) }, + { USB_DEVICE(WS002IN_VENDOR_ID, WS002IN_PRODUCT_ID) }, + { USB_DEVICE(COREGA_VENDOR_ID, COREGA_PRODUCT_ID) }, + { USB_DEVICE(YCCABLE_VENDOR_ID, YCCABLE_PRODUCT_ID) }, + { USB_DEVICE(SUPERIAL_VENDOR_ID, SUPERIAL_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD220_PRODUCT_ID) }, + { USB_DEVICE(CRESSI_VENDOR_ID, CRESSI_EDY_PRODUCT_ID) }, + { USB_DEVICE(ZEAGLE_VENDOR_ID, ZEAGLE_N2ITION3_PRODUCT_ID) }, + { USB_DEVICE(SONY_VENDOR_ID, SONY_QN3USB_PRODUCT_ID) }, + { USB_DEVICE(SANWA_VENDOR_ID, SANWA_PRODUCT_ID) }, + { USB_DEVICE(ADLINK_VENDOR_ID, ADLINK_ND6530_PRODUCT_ID) }, + { USB_DEVICE(SMART_VENDOR_ID, SMART_PRODUCT_ID) }, + + { .terminating_entry = 1 } /* Terminating Entry */ +}; + +static const struct usb_device_id usbredir_ftdi_serial_ids[] = { + /* + * FTDI USB to RS232 serial adapter ids + * copied from linux/drivers/usb/serial/ftdi_sio.c + * + * Copyright (C) 2009 - 2010 + * Johan Hovold (jhovold@gmail.com) + * Copyright (C) 1999 - 2001 + * Greg Kroah-Hartman (greg@kroah.com) + * Bill Ryder (bryder@sgi.com) + * Copyright (C) 2002 + * Kuba Ober (kuba@mareimbrium.org) + */ + { USB_DEVICE(FTDI_VID, FTDI_ZEITCONTROL_TAGTRACE_MIFARE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CTI_MINI_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CTI_NANO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_AMC232_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CANUSB_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CANDAPTER_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NXTCAM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_5_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_6_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCS_DEVICE_7_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USINT_CAT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USINT_WKEY_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USINT_RS232_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ACTZWAVE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IRTRANS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IPLUS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IPLUS2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DMX4ALL) }, + { USB_DEVICE(FTDI_VID, FTDI_SIO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_8U232AM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_8U232AM_ALT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_232RL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_4232H_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_232H_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FTX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_SNIFFER_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_THROTTLE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GATEWAY_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_GBM_PID) }, + { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_IOBOARD_PID) }, + { USB_DEVICE(INTERBIOMETRICS_VID, INTERBIOMETRICS_MINI_IOBOARD_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SPROG_II) }, + { USB_DEVICE(FTDI_VID, FTDI_LENZ_LIUSB_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_632_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_634_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_547_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_633_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_631_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_635_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_640_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_XF_642_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DSS20_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_URBAN_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_URBAN_1_PID) }, + { USB_DEVICE(FTDI_NF_RIC_VID, FTDI_NF_RIC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_VNHCPCUSB_D_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_5_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MTXORB_6_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_R2000KU_TRUE_RNG) }, + { USB_DEVICE(FTDI_VID, FTDI_VARDAAN_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0100_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0101_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0102_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0103_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0104_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0105_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0106_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0107_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0108_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0109_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_010F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0110_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0111_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0112_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0113_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0114_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0115_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0116_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0117_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0118_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0119_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_011F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0120_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0121_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0122_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0123_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0124_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0125_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0126_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0127_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0128_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0129_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_012F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0130_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0131_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0132_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0133_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0134_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0135_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0136_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0137_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0138_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0139_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_013F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0140_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0141_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0142_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0143_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0144_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0145_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0146_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0147_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0148_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0149_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_014F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0150_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0151_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0152_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0153_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0154_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0155_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0156_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0157_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0158_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0159_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_015F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0160_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0161_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0162_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0163_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0164_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0165_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0166_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0167_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0168_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0169_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_016F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0170_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0171_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0172_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0173_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0174_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0175_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0176_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0177_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0178_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0179_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_017F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0180_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0181_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0182_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0183_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0184_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0185_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0186_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0187_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0188_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0189_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_018F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0190_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0191_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0192_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0193_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0194_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0195_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0196_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0197_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0198_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0199_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019A_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019B_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019C_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019D_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019E_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_019F_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01A9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01AF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01B9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01BF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01C9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01CF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01D9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01DF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01E9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01ED_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01EF_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F0_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F1_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F2_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F3_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F4_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F5_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F6_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F7_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F8_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01F9_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FA_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FB_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FC_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FD_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FE_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_01FF_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PERLE_ULTRAPORT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PIEGROUP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TNC_X_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USBX_707_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2101_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2102_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2103_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2104_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2106_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2201_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2202_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2203_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2401_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2402_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2403_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2801_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2802_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_4_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_5_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_6_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_7_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803_8_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_1_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_2_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_3_PID) }, + { USB_DEVICE(SEALEVEL_VID, SEALEVEL_2803R_4_PID) }, + { USB_DEVICE(IDTECH_VID, IDTECH_IDT1221U_PID) }, + { USB_DEVICE(OCT_VID, OCT_US101_PID) }, + { USB_DEVICE(OCT_VID, OCT_DK201_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_HE_TIRA1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_USB_UIRT_PID) }, + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_1) }, + { USB_DEVICE(FTDI_VID, PROTEGO_R2X0) }, + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) }, + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E808_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E809_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80A_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80B_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80D_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80E_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E80F_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E888_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E889_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88A_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88B_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88D_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88E_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GUDEADS_E88F_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UO100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UM100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UR100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_ALC8500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PYRAMID_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1000PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_US485_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PICPRO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PCMCIA_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PK1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_RS232MON_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_APP70_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PEDO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IBS_PROD_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TAVIR_STK500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TIAO_UMPA_PID) }, + /* + * ELV devices: + */ + { USB_DEVICE(FTDI_VID, FTDI_ELV_USR_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_MSM1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_KL100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS550_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_EC3000_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS888_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_TWS550_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FEM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_CLI7000_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_PPS7330_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_TFM100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UDF77_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UIO88_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UAD8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UDA7_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_USI2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_T1100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_PCD200_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_ULA200_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_CSI8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1000DL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_PCK100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_RFP500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FS20SIG_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UTP8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS300PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS444PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1300PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_EM1010PC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_HS485_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UMS100_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_TFD128_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_FM3RX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_WS777_PID) }, + { USB_DEVICE(FTDI_VID, LINX_SDMUSBQSS_PID) }, + { USB_DEVICE(FTDI_VID, LINX_MASTERDEVEL2_PID) }, + { USB_DEVICE(FTDI_VID, LINX_FUTURE_0_PID) }, + { USB_DEVICE(FTDI_VID, LINX_FUTURE_1_PID) }, + { USB_DEVICE(FTDI_VID, LINX_FUTURE_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU20_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU40_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSMACHX_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSLOAD_N_GO_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU64_4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSPRIME8_5_PID) }, + { USB_DEVICE(FTDI_VID, INSIDE_ACCESSO) }, + { USB_DEVICE(INTREPID_VID, INTREPID_VALUECAN_PID) }, + { USB_DEVICE(INTREPID_VID, INTREPID_NEOVI_PID) }, + { USB_DEVICE(FALCOM_VID, FALCOM_TWIST_PID) }, + { USB_DEVICE(FALCOM_VID, FALCOM_SAMBA_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SUUNTO_SPORTS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OCEANIC_PID) }, + { USB_DEVICE(TTI_VID, TTI_QL355P_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_RM_CANVIEW_PID) }, + { USB_DEVICE(ACTON_VID, ACTON_SPECTRAPRO_PID) }, + { USB_DEVICE(CONTEC_VID, CONTEC_COM1USBH_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USO9ML2_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOPTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USPTL4_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_2_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USO9ML2DR_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR2_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_USOPTL4DR_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USB9F_2W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USB9F_4W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_232USB9M_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USBTB_2W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_485USBTB_4W_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_TTL5USB9M_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_TTL3USB9M_PID) }, + { USB_DEVICE(BANDB_VID, BANDB_ZZ_PROG1_USB_PID) }, + { USB_DEVICE(FTDI_VID, EVER_ECO_PRO_CDS) }, + { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_1_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_4N_GALAXY_DE_3_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_0_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_1_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_2_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_3_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_4_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_5_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_6_PID) }, + { USB_DEVICE(FTDI_VID, XSENS_CONVERTER_7_PID) }, + { USB_DEVICE(MOBILITY_VID, MOBILITY_USB_SERIAL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ACTIVE_ROBOTS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_KW_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_YS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y6_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y8_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_IC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_DB9_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_RS232_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MHAM_Y9_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_VCP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TERATRONIK_D2XX_PID) }, + { USB_DEVICE(EVOLUTION_VID, EVOLUTION_ER1_PID) }, + { USB_DEVICE(EVOLUTION_VID, EVO_HYBRID_PID) }, + { USB_DEVICE(EVOLUTION_VID, EVO_RCM4_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ARTEMIS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HR_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16HRC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ATIK_ATK16IC_PID) }, + { USB_DEVICE(KOBIL_VID, KOBIL_CONV_B1_PID) }, + { USB_DEVICE(KOBIL_VID, KOBIL_CONV_KAAN_PID) }, + { USB_DEVICE(POSIFLEX_VID, POSIFLEX_PP7000_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TTUSB_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ECLO_COM_1WIRE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_777_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_WESTREX_MODEL_8900F_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PCDJ_DAC2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_RRCIRKITS_LOCOBUFFER_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ASK_RDR400_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NZR_SEM_USB_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_1_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_OPC_U_UC_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C1_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C2_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2D_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VT_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2VR_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVT_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP4KVR_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVT_PID) }, + { USB_DEVICE(ICOM_VID, ICOM_ID_RP2KVR_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ACG_HFDUAL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_YEI_SERVOCENTER31_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_THORLABS_PID) }, + { USB_DEVICE(TESTO_VID, TESTO_USB_INTERFACE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_GAMMA_SCOUT_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13M_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13S_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TACTRIX_OPENPORT_13U_PID) }, + { USB_DEVICE(ELEKTOR_VID, ELEKTOR_FT323R_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_HUC_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_SPECTRA_SCU_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_3_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_AURORA_SCU_PID) }, + { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_SERIAL_VX7_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_CT29B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_RTS01_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_MAXSTREAM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PHI_FISCO_PID) }, + { USB_DEVICE(TML_VID, TML_USB_SERIAL_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_ELSTER_UNICOM_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PROPOX_JTAGCABLEII_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_PROPOX_ISPCABLEIII_PID) }, + { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_PID) }, + { USB_DEVICE(OLIMEX_VID, OLIMEX_ARM_USB_OCD_H_PID) }, + { USB_DEVICE(FIC_VID, FIC_NEO1973_DEBUG_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_OOCDLINK_PID) }, + { USB_DEVICE(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID) }, + { USB_DEVICE(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID) }, + { USB_DEVICE(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TURTELIZER_PID) }, + { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) }, + { USB_DEVICE(FTDI_VID, FTDI_REU_TINY_PID) }, + + /* Papouch devices based on FTDI chip */ + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485_2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AP485_2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB422_2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485S_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB485C_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_LEC_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SB232_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_TMU_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_IRAMP_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK5_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO8x8_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO4x4_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x2_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO10x1_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO30x3_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO60x3_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO2x16_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_QUIDO3x32_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_DRAK6_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_UPSUSB_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_MU_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_SIMUKEY_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_AD4USB_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMUX_PID) }, + { USB_DEVICE(PAPOUCH_VID, PAPOUCH_GMSR_PID) }, + + { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DGQG_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DOMINTELL_DUSB_PID) }, + { USB_DEVICE(ALTI2_VID, ALTI2_N3_PID) }, + { USB_DEVICE(FTDI_VID, DIEBOLD_BCS_SE923_PID) }, + { USB_DEVICE(ATMEL_VID, STK541_PID) }, + { USB_DEVICE(DE_VID, STB_PID) }, + { USB_DEVICE(DE_VID, WHT_PID) }, + { USB_DEVICE(ADI_VID, ADI_GNICE_PID) }, + { USB_DEVICE(ADI_VID, ADI_GNICEPLUS_PID) }, + { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID, + 0xff, 0xff, 0x00) }, + { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) }, + { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID) }, + { USB_DEVICE(LARSENBRUSGAARD_VID, LB_ALTITRACK_PID) }, + { USB_DEVICE(GN_OTOMETRICS_VID, AURICAL_USB_PID) }, + { USB_DEVICE(FTDI_VID, PI_C865_PID) }, + { USB_DEVICE(FTDI_VID, PI_C857_PID) }, + { USB_DEVICE(PI_VID, PI_C866_PID) }, + { USB_DEVICE(PI_VID, PI_C663_PID) }, + { USB_DEVICE(PI_VID, PI_C725_PID) }, + { USB_DEVICE(PI_VID, PI_E517_PID) }, + { USB_DEVICE(PI_VID, PI_C863_PID) }, + { USB_DEVICE(PI_VID, PI_E861_PID) }, + { USB_DEVICE(PI_VID, PI_C867_PID) }, + { USB_DEVICE(PI_VID, PI_E609_PID) }, + { USB_DEVICE(PI_VID, PI_E709_PID) }, + { USB_DEVICE(PI_VID, PI_100F_PID) }, + { USB_DEVICE(PI_VID, PI_1011_PID) }, + { USB_DEVICE(PI_VID, PI_1012_PID) }, + { USB_DEVICE(PI_VID, PI_1013_PID) }, + { USB_DEVICE(PI_VID, PI_1014_PID) }, + { USB_DEVICE(PI_VID, PI_1015_PID) }, + { USB_DEVICE(PI_VID, PI_1016_PID) }, + { USB_DEVICE(KONDO_VID, KONDO_USB_SERIAL_PID) }, + { USB_DEVICE(BAYER_VID, BAYER_CONTOUR_CABLE_PID) }, + { USB_DEVICE(FTDI_VID, MARVELL_OPENRD_PID) }, + { USB_DEVICE(FTDI_VID, TI_XDS100V2_PID) }, + { USB_DEVICE(FTDI_VID, HAMEG_HO820_PID) }, + { USB_DEVICE(FTDI_VID, HAMEG_HO720_PID) }, + { USB_DEVICE(FTDI_VID, HAMEG_HO730_PID) }, + { USB_DEVICE(FTDI_VID, HAMEG_HO870_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_GENERIC_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_SR_RADIO_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_HD_RADIO_PID) }, + { USB_DEVICE(FTDI_VID, MJSG_XM_RADIO_PID) }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_ST_PID) }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SLITE_PID) }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH2_PID) }, + { USB_DEVICE(FTDI_VID, XVERVE_SIGNALYZER_SH4_PID) }, + { USB_DEVICE(FTDI_VID, SEGWAY_RMP200_PID) }, + { USB_DEVICE(FTDI_VID, ACCESIO_COM4SM_PID) }, + { USB_DEVICE(IONICS_VID, IONICS_PLUGCOMPUTER_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_24_MASTER_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_PC_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_USB_DMX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MIDI_TIMECODE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MINI_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MAXI_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_MEDIA_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CHAMSYS_WING_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LOGBOOKML_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_LS_LOGBOOK_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SCIENCESCOPE_HS_LOGBOOK_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CINTERION_MC55I_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) }, + { USB_DEVICE(ST_VID, ST_STMCLT1030_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_RF_R106) }, + { USB_DEVICE(FTDI_VID, FTDI_DISTORTEC_JTAG_LOCK_PICK_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_LUMEL_PD12_PID) }, + + { .terminating_entry = 1 } /* Terminating Entry */ +}; + +#undef USB_DEVICE +#undef USB_DEVICE_AND_INTERFACE_INFO + +#endif diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c new file mode 100644 index 000000000..5f0ef9cb3 --- /dev/null +++ b/hw/usb/redirect.c @@ -0,0 +1,2618 @@ +/* + * USB redirector usb-guest + * + * Copyright (c) 2011-2012 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/osdep.h" +#include "qemu-common.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "qemu/timer.h" +#include "sysemu/runstate.h" +#include "sysemu/sysemu.h" +#include "qapi/qmp/qerror.h" +#include "qemu/error-report.h" +#include "qemu/iov.h" +#include "qemu/module.h" +#include "chardev/char-fe.h" + +#include <usbredirparser.h> +#include <usbredirfilter.h> + +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/usb.h" +#include "migration/qemu-file-types.h" +#include "migration/vmstate.h" +#include "qom/object.h" + +/* ERROR is defined below. Remove any previous definition. */ +#undef ERROR + +#define MAX_ENDPOINTS 32 +#define NO_INTERFACE_INFO 255 /* Valid interface_count always <= 32 */ +#define EP2I(ep_address) (((ep_address & 0x80) >> 3) | (ep_address & 0x0f)) +#define I2EP(i) (((i & 0x10) << 3) | (i & 0x0f)) +#define USBEP2I(usb_ep) (((usb_ep)->pid == USB_TOKEN_IN) ? \ + ((usb_ep)->nr | 0x10) : ((usb_ep)->nr)) +#define I2USBEP(d, i) (usb_ep_get(&(d)->dev, \ + ((i) & 0x10) ? USB_TOKEN_IN : USB_TOKEN_OUT, \ + (i) & 0x0f)) + +#ifndef USBREDIR_VERSION /* This is not defined in older usbredir versions */ +#define USBREDIR_VERSION 0 +#endif + +typedef struct USBRedirDevice USBRedirDevice; + +/* Struct to hold buffered packets */ +struct buf_packet { + uint8_t *data; + void *free_on_destroy; + uint16_t len; + uint16_t offset; + uint8_t status; + QTAILQ_ENTRY(buf_packet)next; +}; + +struct endp_data { + USBRedirDevice *dev; + uint8_t type; + uint8_t interval; + uint8_t interface; /* bInterfaceNumber this ep belongs to */ + uint16_t max_packet_size; /* In bytes, not wMaxPacketSize format !! */ + uint32_t max_streams; + 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 bulk_receiving_enabled; + uint8_t bulk_receiving_started; + uint8_t bufpq_prefilled; + uint8_t bufpq_dropping_packets; + QTAILQ_HEAD(, buf_packet) bufpq; + int32_t bufpq_size; + int32_t bufpq_target_size; + USBPacket *pending_async_packet; +}; + +struct PacketIdQueueEntry { + uint64_t id; + QTAILQ_ENTRY(PacketIdQueueEntry)next; +}; + +struct PacketIdQueue { + USBRedirDevice *dev; + const char *name; + QTAILQ_HEAD(, PacketIdQueueEntry) head; + int size; +}; + +struct USBRedirDevice { + USBDevice dev; + /* Properties */ + CharBackend cs; + bool enable_streams; + bool suppress_remote_wake; + bool in_write; + uint8_t debug; + int32_t bootindex; + 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; + /* Active chardev-watch-tag */ + guint watch; + /* For async handling of close / reject */ + QEMUBH *chardev_close_bh; + QEMUBH *device_reject_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]; + struct PacketIdQueue cancelled; + struct PacketIdQueue already_in_flight; + void (*buffered_bulk_in_complete)(USBRedirDevice *, USBPacket *, uint8_t); + /* 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; + int compatible_speedmask; + VMChangeStateEntry *vmstate; +}; + +#define TYPE_USB_REDIR "usb-redir" +DECLARE_INSTANCE_CHECKER(USBRedirDevice, USB_REDIRECT, + TYPE_USB_REDIR) + +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, uint64_t id, + struct usb_redir_configuration_status_header *configuration_status); +static void usbredir_alt_setting_status(void *priv, uint64_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status); +static void usbredir_iso_stream_status(void *priv, uint64_t id, + struct usb_redir_iso_stream_status_header *iso_stream_status); +static void usbredir_interrupt_receiving_status(void *priv, uint64_t id, + struct usb_redir_interrupt_receiving_status_header + *interrupt_receiving_status); +static void usbredir_bulk_streams_status(void *priv, uint64_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status); +static void usbredir_bulk_receiving_status(void *priv, uint64_t id, + struct usb_redir_bulk_receiving_status_header *bulk_receiving_status); +static void usbredir_control_packet(void *priv, uint64_t id, + struct usb_redir_control_packet_header *control_packet, + uint8_t *data, int data_len); +static void usbredir_bulk_packet(void *priv, uint64_t id, + struct usb_redir_bulk_packet_header *bulk_packet, + uint8_t *data, int data_len); +static void usbredir_iso_packet(void *priv, uint64_t id, + struct usb_redir_iso_packet_header *iso_packet, + uint8_t *data, int data_len); +static void usbredir_interrupt_packet(void *priv, uint64_t id, + struct usb_redir_interrupt_packet_header *interrupt_header, + uint8_t *data, int data_len); +static void usbredir_buffered_bulk_packet(void *priv, uint64_t id, + struct usb_redir_buffered_bulk_packet_header *buffered_bulk_packet, + uint8_t *data, int data_len); + +static void usbredir_handle_status(USBRedirDevice *dev, USBPacket *p, + int status); + +#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) { \ + warn_report("" __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) +{ + if (dev->debug < usbredirparser_debug_data) { + return; + } + qemu_hexdump(stderr, desc, data, len); +} + +/* + * 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 gboolean usbredir_write_unblocked(void *do_not_use, GIOCondition cond, + void *opaque) +{ + USBRedirDevice *dev = opaque; + + dev->watch = 0; + usbredirparser_do_write(dev->parser); + + return FALSE; +} + +static int usbredir_write(void *priv, uint8_t *data, int count) +{ + USBRedirDevice *dev = priv; + int r; + + if (!qemu_chr_fe_backend_open(&dev->cs)) { + return 0; + } + + /* Don't send new data to the chardev until our state is fully synced */ + if (!runstate_check(RUN_STATE_RUNNING)) { + return 0; + } + + /* Recursion check */ + if (dev->in_write) { + DPRINTF("usbredir_write recursion\n"); + return 0; + } + dev->in_write = true; + + r = qemu_chr_fe_write(&dev->cs, data, count); + if (r < count) { + if (!dev->watch) { + dev->watch = qemu_chr_fe_add_watch(&dev->cs, G_IO_OUT | G_IO_HUP, + usbredir_write_unblocked, dev); + } + if (r < 0) { + r = 0; + } + } + dev->in_write = false; + return r; +} + +/* + * Cancelled and buffered packets helpers + */ + +static void packet_id_queue_init(struct PacketIdQueue *q, + USBRedirDevice *dev, const char *name) +{ + q->dev = dev; + q->name = name; + QTAILQ_INIT(&q->head); + q->size = 0; +} + +static void packet_id_queue_add(struct PacketIdQueue *q, uint64_t id) +{ + USBRedirDevice *dev = q->dev; + struct PacketIdQueueEntry *e; + + DPRINTF("adding packet id %"PRIu64" to %s queue\n", id, q->name); + + e = g_new0(struct PacketIdQueueEntry, 1); + e->id = id; + QTAILQ_INSERT_TAIL(&q->head, e, next); + q->size++; +} + +static int packet_id_queue_remove(struct PacketIdQueue *q, uint64_t id) +{ + USBRedirDevice *dev = q->dev; + struct PacketIdQueueEntry *e; + + QTAILQ_FOREACH(e, &q->head, next) { + if (e->id == id) { + DPRINTF("removing packet id %"PRIu64" from %s queue\n", + id, q->name); + QTAILQ_REMOVE(&q->head, e, next); + q->size--; + g_free(e); + return 1; + } + } + return 0; +} + +static void packet_id_queue_empty(struct PacketIdQueue *q) +{ + USBRedirDevice *dev = q->dev; + struct PacketIdQueueEntry *e, *next_e; + + DPRINTF("removing %d packet-ids from %s queue\n", q->size, q->name); + + QTAILQ_FOREACH_SAFE(e, &q->head, next, next_e) { + QTAILQ_REMOVE(&q->head, e, next); + g_free(e); + } + q->size = 0; +} + +static void usbredir_cancel_packet(USBDevice *udev, USBPacket *p) +{ + USBRedirDevice *dev = USB_REDIRECT(udev); + int i = USBEP2I(p->ep); + + if (p->combined) { + usb_combined_packet_cancel(udev, p); + return; + } + + if (dev->endpoint[i].pending_async_packet) { + assert(dev->endpoint[i].pending_async_packet == p); + dev->endpoint[i].pending_async_packet = NULL; + return; + } + + packet_id_queue_add(&dev->cancelled, p->id); + usbredirparser_send_cancel_data_packet(dev->parser, p->id); + usbredirparser_do_write(dev->parser); +} + +static int usbredir_is_cancelled(USBRedirDevice *dev, uint64_t id) +{ + if (!dev->dev.attached) { + return 1; /* Treat everything as cancelled after a disconnect */ + } + return packet_id_queue_remove(&dev->cancelled, id); +} + +static void usbredir_fill_already_in_flight_from_ep(USBRedirDevice *dev, + struct USBEndpoint *ep) +{ + static USBPacket *p; + + /* async handled packets for bulk receiving eps do not count as inflight */ + if (dev->endpoint[USBEP2I(ep)].bulk_receiving_started) { + return; + } + + QTAILQ_FOREACH(p, &ep->queue, queue) { + /* Skip combined packets, except for the first */ + if (p->combined && p != p->combined->first) { + continue; + } + if (p->state == USB_PACKET_ASYNC) { + packet_id_queue_add(&dev->already_in_flight, p->id); + } + } +} + +static void usbredir_fill_already_in_flight(USBRedirDevice *dev) +{ + int ep; + struct USBDevice *udev = &dev->dev; + + usbredir_fill_already_in_flight_from_ep(dev, &udev->ep_ctl); + + for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) { + usbredir_fill_already_in_flight_from_ep(dev, &udev->ep_in[ep]); + usbredir_fill_already_in_flight_from_ep(dev, &udev->ep_out[ep]); + } +} + +static int usbredir_already_in_flight(USBRedirDevice *dev, uint64_t id) +{ + return packet_id_queue_remove(&dev->already_in_flight, id); +} + +static USBPacket *usbredir_find_packet_by_id(USBRedirDevice *dev, + uint8_t ep, uint64_t id) +{ + USBPacket *p; + + if (usbredir_is_cancelled(dev, id)) { + return NULL; + } + + p = usb_ep_find_packet_by_id(&dev->dev, + (ep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT, + ep & 0x0f, id); + if (p == NULL) { + ERROR("could not find packet with id %"PRIu64"\n", id); + } + return p; +} + +static int bufp_alloc(USBRedirDevice *dev, uint8_t *data, uint16_t len, + uint8_t status, uint8_t ep, void *free_on_destroy) +{ + 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(free_on_destroy); + return -1; + } + dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; + } + + bufp = g_new(struct buf_packet, 1); + bufp->data = data; + bufp->len = len; + bufp->offset = 0; + bufp->status = status; + bufp->free_on_destroy = free_on_destroy; + QTAILQ_INSERT_TAIL(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); + dev->endpoint[EP2I(ep)].bufpq_size++; + return 0; +} + +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->free_on_destroy); + 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 = USB_REDIRECT(udev); + + DPRINTF("reset device\n"); + usbredirparser_send_reset(dev->parser); + usbredirparser_do_write(dev->parser); +} + +static void 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 = DIV_ROUND_UP( + dev->endpoint[EP2I(ep)].bufpq_target_size, + 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; + } + 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; + p->status = status ? USB_RET_IOERROR : USB_RET_SUCCESS; + return; + } + 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; + 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); + len = p->iov.size; + status = usb_redir_babble; + } + usb_packet_copy(p, isop->data, len); + bufp_free(dev, isop, ep); + usbredir_handle_status(dev, p, status); + } 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 + }; + g_autofree uint8_t *buf = g_malloc(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); + usbredir_handle_status(dev, p, status); + } +} + +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); +} + +/* + * The usb-host may poll the endpoint faster then our guest, resulting in lots + * of smaller bulkp-s. The below buffered_bulk_in_complete* functions combine + * data from multiple bulkp-s into a single packet, avoiding bufpq overflows. + */ +static void usbredir_buffered_bulk_add_data_to_packet(USBRedirDevice *dev, + struct buf_packet *bulkp, int count, USBPacket *p, uint8_t ep) +{ + usb_packet_copy(p, bulkp->data + bulkp->offset, count); + bulkp->offset += count; + if (bulkp->offset == bulkp->len) { + /* Store status in the last packet with data from this bulkp */ + usbredir_handle_status(dev, p, bulkp->status); + bufp_free(dev, bulkp, ep); + } +} + +static void usbredir_buffered_bulk_in_complete_raw(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + struct buf_packet *bulkp; + int count; + + while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) && + p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) { + count = bulkp->len - bulkp->offset; + if (count > (p->iov.size - p->actual_length)) { + count = p->iov.size - p->actual_length; + } + usbredir_buffered_bulk_add_data_to_packet(dev, bulkp, count, p, ep); + } +} + +static void usbredir_buffered_bulk_in_complete_ftdi(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + const int maxp = dev->endpoint[EP2I(ep)].max_packet_size; + uint8_t header[2] = { 0, 0 }; + struct buf_packet *bulkp; + int count; + + while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) && + p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) { + if (bulkp->len < 2) { + WARNING("malformed ftdi bulk in packet\n"); + bufp_free(dev, bulkp, ep); + continue; + } + + if ((p->actual_length % maxp) == 0) { + usb_packet_copy(p, bulkp->data, 2); + memcpy(header, bulkp->data, 2); + } else { + if (bulkp->data[0] != header[0] || bulkp->data[1] != header[1]) { + break; /* Different header, add to next packet */ + } + } + + if (bulkp->offset == 0) { + bulkp->offset = 2; /* Skip header */ + } + count = bulkp->len - bulkp->offset; + /* Must repeat the header at maxp interval */ + if (count > (maxp - (p->actual_length % maxp))) { + count = maxp - (p->actual_length % maxp); + } + usbredir_buffered_bulk_add_data_to_packet(dev, bulkp, count, p, ep); + } +} + +static void usbredir_buffered_bulk_in_complete(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + dev->buffered_bulk_in_complete(dev, p, ep); + DPRINTF("bulk-token-in ep %02X status %d len %d id %"PRIu64"\n", + ep, p->status, p->actual_length, p->id); +} + +static void usbredir_handle_buffered_bulk_in_data(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + /* Input bulk endpoint, buffered packet input */ + if (!dev->endpoint[EP2I(ep)].bulk_receiving_started) { + int bpt; + struct usb_redir_start_bulk_receiving_header start = { + .endpoint = ep, + .stream_id = 0, + .no_transfers = 5, + }; + /* Round bytes_per_transfer up to a multiple of max_packet_size */ + bpt = 512 + dev->endpoint[EP2I(ep)].max_packet_size - 1; + bpt /= dev->endpoint[EP2I(ep)].max_packet_size; + bpt *= dev->endpoint[EP2I(ep)].max_packet_size; + start.bytes_per_transfer = bpt; + /* No id, we look at the ep when receiving a status back */ + usbredirparser_send_start_bulk_receiving(dev->parser, 0, &start); + usbredirparser_do_write(dev->parser); + DPRINTF("bulk receiving started bytes/transfer %u count %d ep %02X\n", + start.bytes_per_transfer, start.no_transfers, ep); + dev->endpoint[EP2I(ep)].bulk_receiving_started = 1; + /* We don't really want to drop bulk packets ever, but + having some upper limit to how much we buffer is good. */ + dev->endpoint[EP2I(ep)].bufpq_target_size = 5000; + dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; + } + + if (QTAILQ_EMPTY(&dev->endpoint[EP2I(ep)].bufpq)) { + DPRINTF("bulk-token-in ep %02X, no bulkp\n", ep); + assert(dev->endpoint[EP2I(ep)].pending_async_packet == NULL); + dev->endpoint[EP2I(ep)].pending_async_packet = p; + p->status = USB_RET_ASYNC; + return; + } + usbredir_buffered_bulk_in_complete(dev, p, ep); +} + +static void usbredir_stop_bulk_receiving(USBRedirDevice *dev, uint8_t ep) +{ + struct usb_redir_stop_bulk_receiving_header stop_bulk = { + .endpoint = ep, + .stream_id = 0, + }; + if (dev->endpoint[EP2I(ep)].bulk_receiving_started) { + usbredirparser_send_stop_bulk_receiving(dev->parser, 0, &stop_bulk); + DPRINTF("bulk receiving stopped ep %02X\n", ep); + dev->endpoint[EP2I(ep)].bulk_receiving_started = 0; + } + usbredir_free_bufpq(dev, ep); +} + +static void usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p, + uint8_t ep) +{ + struct usb_redir_bulk_packet_header bulk_packet; + size_t size = usb_packet_size(p); + const int maxp = dev->endpoint[EP2I(ep)].max_packet_size; + + if (usbredir_already_in_flight(dev, p->id)) { + p->status = USB_RET_ASYNC; + return; + } + + if (dev->endpoint[EP2I(ep)].bulk_receiving_enabled) { + if (size != 0 && (size % maxp) == 0) { + usbredir_handle_buffered_bulk_in_data(dev, p, ep); + return; + } + WARNING("bulk recv invalid size %zd ep %02x, disabling\n", size, ep); + assert(dev->endpoint[EP2I(ep)].pending_async_packet == NULL); + usbredir_stop_bulk_receiving(dev, ep); + dev->endpoint[EP2I(ep)].bulk_receiving_enabled = 0; + } + + DPRINTF("bulk-out ep %02X stream %u len %zd id %"PRIu64"\n", + ep, p->stream, size, p->id); + + bulk_packet.endpoint = ep; + bulk_packet.length = size; + bulk_packet.stream_id = p->stream; + bulk_packet.length_high = size >> 16; + assert(bulk_packet.length_high == 0 || + usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_32bits_bulk_length)); + + if (ep & USB_DIR_IN || size == 0) { + usbredirparser_send_bulk_packet(dev->parser, p->id, + &bulk_packet, NULL, 0); + } else { + g_autofree uint8_t *buf = g_malloc(size); + usb_packet_copy(p, buf, size); + usbredir_log_data(dev, "bulk data out:", buf, size); + usbredirparser_send_bulk_packet(dev->parser, p->id, + &bulk_packet, buf, size); + } + usbredirparser_do_write(dev->parser); + p->status = USB_RET_ASYNC; +} + +static void usbredir_handle_interrupt_in_data(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + /* Input interrupt endpoint, buffered packet input */ + struct buf_packet *intp, *intp_to_free; + int status, len, sum; + + 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; + } + + /* check for completed interrupt message (with all fragments) */ + sum = 0; + QTAILQ_FOREACH(intp, &dev->endpoint[EP2I(ep)].bufpq, next) { + sum += intp->len; + if (intp->len < dev->endpoint[EP2I(ep)].max_packet_size || + sum >= p->iov.size) + break; + } + + if (intp == NULL) { + DPRINTF2("interrupt-token-in ep %02X, no intp, buffered %d\n", ep, sum); + /* Check interrupt_error for stream errors */ + status = dev->endpoint[EP2I(ep)].interrupt_error; + dev->endpoint[EP2I(ep)].interrupt_error = 0; + if (status) { + usbredir_handle_status(dev, p, status); + } else { + p->status = USB_RET_NAK; + } + return; + } + + /* copy of completed interrupt message */ + sum = 0; + status = usb_redir_success; + intp_to_free = NULL; + QTAILQ_FOREACH(intp, &dev->endpoint[EP2I(ep)].bufpq, next) { + if (intp_to_free) { + bufp_free(dev, intp_to_free, ep); + } + DPRINTF("interrupt-token-in ep %02X fragment status %d len %d\n", ep, + intp->status, intp->len); + + sum += intp->len; + len = intp->len; + if (status == usb_redir_success) { + status = intp->status; + } + if (sum > p->iov.size) { + ERROR("received int data is larger then packet ep %02X\n", ep); + len -= (sum - p->iov.size); + sum = p->iov.size; + status = usb_redir_babble; + } + + usb_packet_copy(p, intp->data, len); + + intp_to_free = intp; + if (intp->len < dev->endpoint[EP2I(ep)].max_packet_size || + sum >= p->iov.size) + break; + } + if (intp_to_free) { + bufp_free(dev, intp_to_free, ep); + } + DPRINTF("interrupt-token-in ep %02X summary status %d len %d\n", ep, + status, sum); + usbredir_handle_status(dev, p, status); +} + +/* + * Handle interrupt out data, the usbredir protocol expects us to do this + * async, so that it can report back a completion status. But guests will + * expect immediate completion for an interrupt endpoint, and handling this + * async causes migration issues. So we report success directly, counting + * on the fact that output interrupt packets normally always succeed. + */ +static void usbredir_handle_interrupt_out_data(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + struct usb_redir_interrupt_packet_header interrupt_packet; + g_autofree uint8_t *buf = g_malloc(p->iov.size); + + DPRINTF("interrupt-out ep %02X len %zd id %"PRIu64"\n", ep, + p->iov.size, p->id); + + interrupt_packet.endpoint = ep; + interrupt_packet.length = p->iov.size; + + 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, p->id, + &interrupt_packet, buf, p->iov.size); + usbredirparser_do_write(dev->parser); +} + +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 void usbredir_handle_data(USBDevice *udev, USBPacket *p) +{ + USBRedirDevice *dev = USB_REDIRECT(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); + p->status = USB_RET_NAK; + break; + case USB_ENDPOINT_XFER_BULK: + if (p->state == USB_PACKET_SETUP && p->pid == USB_TOKEN_IN && + p->ep->pipeline) { + p->status = USB_RET_ADD_TO_QUEUE; + break; + } + usbredir_handle_bulk_data(dev, p, ep); + break; + case USB_ENDPOINT_XFER_ISOC: + usbredir_handle_iso_data(dev, p, ep); + break; + case USB_ENDPOINT_XFER_INT: + if (ep & USB_DIR_IN) { + usbredir_handle_interrupt_in_data(dev, p, ep); + } else { + usbredir_handle_interrupt_out_data(dev, p, ep); + } + break; + default: + ERROR("handle_data ep %02X has unknown type %d\n", ep, + dev->endpoint[EP2I(ep)].type); + p->status = USB_RET_NAK; + } +} + +static void usbredir_flush_ep_queue(USBDevice *dev, USBEndpoint *ep) +{ + if (ep->pid == USB_TOKEN_IN && ep->pipeline) { + usb_ep_combine_input_packets(ep); + } +} + +static void usbredir_stop_ep(USBRedirDevice *dev, int i) +{ + uint8_t ep = I2EP(i); + + switch (dev->endpoint[i].type) { + case USB_ENDPOINT_XFER_BULK: + if (ep & USB_DIR_IN) { + usbredir_stop_bulk_receiving(dev, ep); + } + break; + case USB_ENDPOINT_XFER_ISOC: + usbredir_stop_iso_stream(dev, ep); + break; + case USB_ENDPOINT_XFER_INT: + if (ep & USB_DIR_IN) { + usbredir_stop_interrupt_receiving(dev, ep); + } + break; + } + usbredir_free_bufpq(dev, ep); +} + +static void usbredir_ep_stopped(USBDevice *udev, USBEndpoint *uep) +{ + USBRedirDevice *dev = USB_REDIRECT(udev); + + usbredir_stop_ep(dev, USBEP2I(uep)); + usbredirparser_do_write(dev->parser); +} + +static void usbredir_set_config(USBRedirDevice *dev, USBPacket *p, + int config) +{ + struct usb_redir_set_configuration_header set_config; + int i; + + DPRINTF("set config %d id %"PRIu64"\n", config, p->id); + + for (i = 0; i < MAX_ENDPOINTS; i++) { + usbredir_stop_ep(dev, i); + } + + set_config.configuration = config; + usbredirparser_send_set_configuration(dev->parser, p->id, &set_config); + usbredirparser_do_write(dev->parser); + p->status = USB_RET_ASYNC; +} + +static void usbredir_get_config(USBRedirDevice *dev, USBPacket *p) +{ + DPRINTF("get config id %"PRIu64"\n", p->id); + + usbredirparser_send_get_configuration(dev->parser, p->id); + usbredirparser_do_write(dev->parser); + p->status = USB_RET_ASYNC; +} + +static void usbredir_set_interface(USBRedirDevice *dev, USBPacket *p, + int interface, int alt) +{ + struct usb_redir_set_alt_setting_header set_alt; + int i; + + DPRINTF("set interface %d alt %d id %"PRIu64"\n", interface, alt, p->id); + + for (i = 0; i < MAX_ENDPOINTS; i++) { + if (dev->endpoint[i].interface == interface) { + usbredir_stop_ep(dev, i); + } + } + + set_alt.interface = interface; + set_alt.alt = alt; + usbredirparser_send_set_alt_setting(dev->parser, p->id, &set_alt); + usbredirparser_do_write(dev->parser); + p->status = USB_RET_ASYNC; +} + +static void usbredir_get_interface(USBRedirDevice *dev, USBPacket *p, + int interface) +{ + struct usb_redir_get_alt_setting_header get_alt; + + DPRINTF("get interface %d id %"PRIu64"\n", interface, p->id); + + get_alt.interface = interface; + usbredirparser_send_get_alt_setting(dev->parser, p->id, &get_alt); + usbredirparser_do_write(dev->parser); + p->status = USB_RET_ASYNC; +} + +static void usbredir_handle_control(USBDevice *udev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBRedirDevice *dev = USB_REDIRECT(udev); + struct usb_redir_control_packet_header control_packet; + + if (usbredir_already_in_flight(dev, p->id)) { + p->status = USB_RET_ASYNC; + return; + } + + /* 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; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + usbredir_set_config(dev, p, value & 0xff); + return; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + usbredir_get_config(dev, p); + return; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + usbredir_set_interface(dev, p, index, value); + return; + case InterfaceRequest | USB_REQ_GET_INTERFACE: + usbredir_get_interface(dev, p, index); + return; + } + + /* Normal ctrl requests, note request is (bRequestType << 8) | bRequest */ + DPRINTF( + "ctrl-out type 0x%x req 0x%x val 0x%x index %d len %d id %"PRIu64"\n", + request >> 8, request & 0xff, value, index, length, p->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; + + if (control_packet.requesttype & USB_DIR_IN) { + usbredirparser_send_control_packet(dev->parser, p->id, + &control_packet, NULL, 0); + } else { + usbredir_log_data(dev, "ctrl data out:", data, length); + usbredirparser_send_control_packet(dev->parser, p->id, + &control_packet, data, length); + } + usbredirparser_do_write(dev->parser); + p->status = USB_RET_ASYNC; +} + +static int usbredir_alloc_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps, int streams) +{ + USBRedirDevice *dev = USB_REDIRECT(udev); +#if USBREDIR_VERSION >= 0x000700 + struct usb_redir_alloc_bulk_streams_header alloc_streams; + int i; + + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_streams)) { + ERROR("peer does not support streams\n"); + goto reject; + } + + if (streams == 0) { + ERROR("request to allocate 0 streams\n"); + return -1; + } + + alloc_streams.no_streams = streams; + alloc_streams.endpoints = 0; + for (i = 0; i < nr_eps; i++) { + alloc_streams.endpoints |= 1 << USBEP2I(eps[i]); + } + usbredirparser_send_alloc_bulk_streams(dev->parser, 0, &alloc_streams); + usbredirparser_do_write(dev->parser); + + return 0; +#else + ERROR("usbredir_alloc_streams not implemented\n"); + goto reject; +#endif +reject: + ERROR("streams are not available, disconnecting\n"); + qemu_bh_schedule(dev->device_reject_bh); + return -1; +} + +static void usbredir_free_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps) +{ +#if USBREDIR_VERSION >= 0x000700 + USBRedirDevice *dev = USB_REDIRECT(udev); + struct usb_redir_free_bulk_streams_header free_streams; + int i; + + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_streams)) { + return; + } + + free_streams.endpoints = 0; + for (i = 0; i < nr_eps; i++) { + free_streams.endpoints |= 1 << USBEP2I(eps[i]); + } + usbredirparser_send_free_bulk_streams(dev->parser, 0, &free_streams); + usbredirparser_do_write(dev->parser); +#endif +} + +/* + * 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. + */ +static void usbredir_chardev_close_bh(void *opaque) +{ + USBRedirDevice *dev = opaque; + + qemu_bh_cancel(dev->device_reject_bh); + usbredir_device_disconnect(dev); + + if (dev->parser) { + DPRINTF("destroying usbredirparser\n"); + usbredirparser_destroy(dev->parser); + dev->parser = NULL; + } + if (dev->watch) { + g_source_remove(dev->watch); + dev->watch = 0; + } +} + +static void usbredir_create_parser(USBRedirDevice *dev) +{ + uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; + int flags = 0; + + DPRINTF("creating usbredirparser\n"); + + 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->bulk_receiving_status_func = usbredir_bulk_receiving_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->parser->buffered_bulk_packet_func = usbredir_buffered_bulk_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_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); + usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); + usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); + usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving); +#if USBREDIR_VERSION >= 0x000700 + if (dev->enable_streams) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams); + } +#endif + + if (runstate_check(RUN_STATE_INMIGRATE)) { + flags |= usbredirparser_fl_no_hello; + } + usbredirparser_init(dev->parser, VERSION, caps, USB_REDIR_CAPS_SIZE, + flags); + usbredirparser_do_write(dev->parser); +} + +static void usbredir_reject_device(USBRedirDevice *dev) +{ + 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); + } +} + +/* + * We may need to reject the device when the hcd calls alloc_streams, doing + * an usb_detach from within a hcd call is not a good idea, hence this bh. + */ +static void usbredir_device_reject_bh(void *opaque) +{ + USBRedirDevice *dev = opaque; + + usbredir_reject_device(dev); +} + +static void usbredir_do_attach(void *opaque) +{ + USBRedirDevice *dev = opaque; + Error *local_err = NULL; + + /* In order to work properly with XHCI controllers we need these caps */ + if ((dev->dev.port->speedmask & USB_SPEED_MASK_SUPER) && !( + usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_ep_info_max_packet_size) && + usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_32bits_bulk_length) && + usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_64bits_ids))) { + ERROR("usb-redir-host lacks capabilities needed for use with XHCI\n"); + usbredir_reject_device(dev); + return; + } + + usb_device_attach(&dev->dev, &local_err); + if (local_err) { + error_report_err(local_err); + WARNING("rejecting device due to speed mismatch\n"); + usbredir_reject_device(dev); + } +} + +/* + * chardev callbacks + */ + +static int usbredir_chardev_can_read(void *opaque) +{ + USBRedirDevice *dev = opaque; + + if (!dev->parser) { + WARNING("chardev_can_read called on non open chardev!\n"); + return 0; + } + + /* Don't read new data from the chardev until our state is fully synced */ + if (!runstate_check(RUN_STATE_RUNNING)) { + return 0; + } + + /* usbredir_parser_do_read will consume *all* data we give it */ + return 1 * MiB; +} + +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, QEMUChrEvent event) +{ + USBRedirDevice *dev = opaque; + + switch (event) { + case CHR_EVENT_OPENED: + DPRINTF("chardev open\n"); + /* Make sure any pending closes are handled (no-op if none pending) */ + usbredir_chardev_close_bh(dev); + qemu_bh_cancel(dev->chardev_close_bh); + usbredir_create_parser(dev); + break; + case CHR_EVENT_CLOSED: + DPRINTF("chardev close\n"); + qemu_bh_schedule(dev->chardev_close_bh); + break; + case CHR_EVENT_BREAK: + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + /* Ignore */ + break; + } +} + +/* + * init + destroy + */ + +static void usbredir_vm_state_change(void *priv, bool running, RunState state) +{ + USBRedirDevice *dev = priv; + + if (state == RUN_STATE_RUNNING && dev->parser != NULL) { + usbredirparser_do_write(dev->parser); /* Flush any pending writes */ + } +} + +static void usbredir_init_endpoints(USBRedirDevice *dev) +{ + int i; + + usb_ep_init(&dev->dev); + memset(dev->endpoint, 0, sizeof(dev->endpoint)); + for (i = 0; i < MAX_ENDPOINTS; i++) { + dev->endpoint[i].dev = dev; + QTAILQ_INIT(&dev->endpoint[i].bufpq); + } +} + +static void usbredir_realize(USBDevice *udev, Error **errp) +{ + USBRedirDevice *dev = USB_REDIRECT(udev); + int i; + + if (!qemu_chr_fe_backend_connected(&dev->cs)) { + error_setg(errp, QERR_MISSING_PARAMETER, "chardev"); + return; + } + + if (dev->filter_str) { + i = usbredirfilter_string_to_rules(dev->filter_str, ":", "|", + &dev->filter_rules, + &dev->filter_rules_count); + if (i) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "filter", + "a usb device filter string"); + return; + } + } + + dev->chardev_close_bh = qemu_bh_new(usbredir_chardev_close_bh, dev); + dev->device_reject_bh = qemu_bh_new(usbredir_device_reject_bh, dev); + dev->attach_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, usbredir_do_attach, dev); + + packet_id_queue_init(&dev->cancelled, dev, "cancelled"); + packet_id_queue_init(&dev->already_in_flight, dev, "already-in-flight"); + usbredir_init_endpoints(dev); + + /* We'll do the attach once we receive the speed from the usb-host */ + udev->auto_attach = 0; + + /* Will be cleared during setup when we find conflicts */ + dev->compatible_speedmask = USB_SPEED_MASK_FULL | USB_SPEED_MASK_HIGH; + + /* Let the backend know we are ready */ + qemu_chr_fe_set_handlers(&dev->cs, usbredir_chardev_can_read, + usbredir_chardev_read, usbredir_chardev_event, + NULL, dev, NULL, true); + + dev->vmstate = + qemu_add_vm_change_state_handler(usbredir_vm_state_change, dev); +} + +static void usbredir_cleanup_device_queues(USBRedirDevice *dev) +{ + int i; + + packet_id_queue_empty(&dev->cancelled); + packet_id_queue_empty(&dev->already_in_flight); + for (i = 0; i < MAX_ENDPOINTS; i++) { + usbredir_free_bufpq(dev, I2EP(i)); + } +} + +static void usbredir_unrealize(USBDevice *udev) +{ + USBRedirDevice *dev = USB_REDIRECT(udev); + + qemu_chr_fe_deinit(&dev->cs, true); + + /* Note must be done after qemu_chr_close, as that causes a close event */ + qemu_bh_delete(dev->chardev_close_bh); + qemu_bh_delete(dev->device_reject_bh); + + timer_free(dev->attach_timer); + + usbredir_cleanup_device_queues(dev); + + if (dev->parser) { + usbredirparser_destroy(dev->parser); + } + if (dev->watch) { + g_source_remove(dev->watch); + } + + free(dev->filter_rules); + qemu_del_vm_change_state_handler(dev->vmstate); +} + +static int usbredir_check_filter(USBRedirDevice *dev) +{ + if (dev->interface_info.interface_count == NO_INTERFACE_INFO) { + 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_reject_device(dev); + return -1; +} + +static void usbredir_check_bulk_receiving(USBRedirDevice *dev) +{ + int i, j, quirks; + + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_receiving)) { + return; + } + + for (i = EP2I(USB_DIR_IN); i < MAX_ENDPOINTS; i++) { + dev->endpoint[i].bulk_receiving_enabled = 0; + } + + if (dev->interface_info.interface_count == NO_INTERFACE_INFO) { + return; + } + + for (i = 0; i < dev->interface_info.interface_count; i++) { + quirks = usb_get_quirks(dev->device_info.vendor_id, + dev->device_info.product_id, + dev->interface_info.interface_class[i], + dev->interface_info.interface_subclass[i], + dev->interface_info.interface_protocol[i]); + if (!(quirks & USB_QUIRK_BUFFER_BULK_IN)) { + continue; + } + if (quirks & USB_QUIRK_IS_FTDI) { + dev->buffered_bulk_in_complete = + usbredir_buffered_bulk_in_complete_ftdi; + } else { + dev->buffered_bulk_in_complete = + usbredir_buffered_bulk_in_complete_raw; + } + + for (j = EP2I(USB_DIR_IN); j < MAX_ENDPOINTS; j++) { + if (dev->endpoint[j].interface == + dev->interface_info.interface[i] && + dev->endpoint[j].type == USB_ENDPOINT_XFER_BULK && + dev->endpoint[j].max_packet_size != 0) { + dev->endpoint[j].bulk_receiving_enabled = 1; + /* + * With buffering pipelining is not necessary. Also packet + * combining and bulk in buffering don't play nice together! + */ + I2USBEP(dev, j)->pipeline = false; + break; /* Only buffer for the first ep of each intf */ + } + } + } +} + +/* + * usbredirparser packet complete callbacks + */ + +static void usbredir_handle_status(USBRedirDevice *dev, USBPacket *p, + int status) +{ + switch (status) { + case usb_redir_success: + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + break; + case usb_redir_stall: + p->status = USB_RET_STALL; + break; + case usb_redir_cancelled: + /* + * When the usbredir-host unredirects a device, it will report a status + * of cancelled for all pending packets, followed by a disconnect msg. + */ + p->status = USB_RET_IOERROR; + break; + case usb_redir_inval: + WARNING("got invalid param error from usb-host?\n"); + p->status = USB_RET_IOERROR; + break; + case usb_redir_babble: + p->status = USB_RET_BABBLE; + break; + case usb_redir_ioerror: + case usb_redir_timeout: + default: + p->status = 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 (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; + dev->compatible_speedmask &= ~USB_SPEED_MASK_FULL; + dev->compatible_speedmask &= ~USB_SPEED_MASK_HIGH; + break; + case usb_redir_speed_full: + speed = "full speed"; + dev->dev.speed = USB_SPEED_FULL; + dev->compatible_speedmask &= ~USB_SPEED_MASK_HIGH; + 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->compatible_speedmask; + 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; + } + + usbredir_check_bulk_receiving(dev); + timer_mod(dev->attach_timer, dev->next_attach_time); +} + +static void usbredir_device_disconnect(void *priv) +{ + USBRedirDevice *dev = priv; + + /* Stop any pending attaches */ + timer_del(dev->attach_timer); + + if (dev->dev.attached) { + DPRINTF("detaching device\n"); + 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_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 200; + } + + /* Reset state so that the next dev connected starts with a clean slate */ + usbredir_cleanup_device_queues(dev); + usbredir_init_endpoints(dev); + dev->interface_info.interface_count = NO_INTERFACE_INFO; + dev->dev.addr = 0; + dev->dev.speed = 0; + dev->compatible_speedmask = USB_SPEED_MASK_FULL | USB_SPEED_MASK_HIGH; +} + +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 interface dependent things. + */ + if (timer_pending(dev->attach_timer) || dev->dev.attached) { + usbredir_check_bulk_receiving(dev); + if (usbredir_check_filter(dev)) { + ERROR("Device no longer matches filter after interface info " + "change, disconnecting!\n"); + } + } +} + +static void usbredir_mark_speed_incompatible(USBRedirDevice *dev, int speed) +{ + dev->compatible_speedmask &= ~(1 << speed); + dev->dev.speedmask = (1 << dev->dev.speed) | dev->compatible_speedmask; +} + +static void usbredir_set_pipeline(USBRedirDevice *dev, struct USBEndpoint *uep) +{ + if (uep->type != USB_ENDPOINT_XFER_BULK) { + return; + } + if (uep->pid == USB_TOKEN_OUT) { + uep->pipeline = true; + } + if (uep->pid == USB_TOKEN_IN && uep->max_packet_size != 0 && + usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_32bits_bulk_length)) { + uep->pipeline = true; + } +} + +static void usbredir_setup_usb_eps(USBRedirDevice *dev) +{ + struct USBEndpoint *usb_ep; + int i; + + for (i = 0; i < MAX_ENDPOINTS; i++) { + usb_ep = I2USBEP(dev, i); + usb_ep->type = dev->endpoint[i].type; + usb_ep->ifnum = dev->endpoint[i].interface; + usb_ep->max_packet_size = dev->endpoint[i].max_packet_size; + usb_ep->max_streams = dev->endpoint[i].max_streams; + usbredir_set_pipeline(dev, usb_ep); + } +} + +static void usbredir_ep_info(void *priv, + struct usb_redir_ep_info_header *ep_info) +{ + USBRedirDevice *dev = priv; + int i; + + assert(dev != NULL); + 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]; + if (usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_ep_info_max_packet_size)) { + dev->endpoint[i].max_packet_size = ep_info->max_packet_size[i]; + } +#if USBREDIR_VERSION >= 0x000700 + if (usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_streams)) { + dev->endpoint[i].max_streams = ep_info->max_streams[i]; + } +#endif + switch (dev->endpoint[i].type) { + case usb_redir_type_invalid: + break; + case usb_redir_type_iso: + usbredir_mark_speed_incompatible(dev, USB_SPEED_FULL); + usbredir_mark_speed_incompatible(dev, USB_SPEED_HIGH); + /* Fall through */ + case usb_redir_type_interrupt: + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_ep_info_max_packet_size) || + ep_info->max_packet_size[i] > 64) { + usbredir_mark_speed_incompatible(dev, USB_SPEED_FULL); + } + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_ep_info_max_packet_size) || + ep_info->max_packet_size[i] > 1024) { + usbredir_mark_speed_incompatible(dev, USB_SPEED_HIGH); + } + if (dev->endpoint[i].interval == 0) { + ERROR("Received 0 interval for isoc or irq endpoint\n"); + usbredir_reject_device(dev); + return; + } + /* 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_reject_device(dev); + return; + } + } + /* The new ep info may have caused a speed incompatibility, recheck */ + if (dev->dev.attached && + !(dev->dev.port->speedmask & dev->dev.speedmask)) { + ERROR("Device no longer matches speed after endpoint info change, " + "disconnecting!\n"); + usbredir_reject_device(dev); + return; + } + usbredir_setup_usb_eps(dev); + usbredir_check_bulk_receiving(dev); +} + +static void usbredir_configuration_status(void *priv, uint64_t id, + struct usb_redir_configuration_status_header *config_status) +{ + USBRedirDevice *dev = priv; + USBPacket *p; + + DPRINTF("set config status %d config %d id %"PRIu64"\n", + config_status->status, config_status->configuration, id); + + p = usbredir_find_packet_by_id(dev, 0, id); + if (p) { + if (dev->dev.setup_buf[0] & USB_DIR_IN) { + dev->dev.data_buf[0] = config_status->configuration; + p->actual_length = 1; + } + usbredir_handle_status(dev, p, config_status->status); + usb_generic_async_ctrl_complete(&dev->dev, p); + } +} + +static void usbredir_alt_setting_status(void *priv, uint64_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status) +{ + USBRedirDevice *dev = priv; + USBPacket *p; + + DPRINTF("alt status %d intf %d alt %d id: %"PRIu64"\n", + alt_setting_status->status, alt_setting_status->interface, + alt_setting_status->alt, id); + + p = usbredir_find_packet_by_id(dev, 0, id); + if (p) { + if (dev->dev.setup_buf[0] & USB_DIR_IN) { + dev->dev.data_buf[0] = alt_setting_status->alt; + p->actual_length = 1; + } + usbredir_handle_status(dev, p, alt_setting_status->status); + usb_generic_async_ctrl_complete(&dev->dev, p); + } +} + +static void usbredir_iso_stream_status(void *priv, uint64_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 %"PRIu64"\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, uint64_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 %"PRIu64"\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, uint64_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status) +{ +#if USBREDIR_VERSION >= 0x000700 + USBRedirDevice *dev = priv; + + if (bulk_streams_status->status == usb_redir_success) { + DPRINTF("bulk streams status %d eps %08x\n", + bulk_streams_status->status, bulk_streams_status->endpoints); + } else { + ERROR("bulk streams %s failed status %d eps %08x\n", + (bulk_streams_status->no_streams == 0) ? "free" : "alloc", + bulk_streams_status->status, bulk_streams_status->endpoints); + ERROR("usb-redir-host does not provide streams, disconnecting\n"); + usbredir_reject_device(dev); + } +#endif +} + +static void usbredir_bulk_receiving_status(void *priv, uint64_t id, + struct usb_redir_bulk_receiving_status_header *bulk_receiving_status) +{ + USBRedirDevice *dev = priv; + uint8_t ep = bulk_receiving_status->endpoint; + + DPRINTF("bulk recv status %d ep %02X id %"PRIu64"\n", + bulk_receiving_status->status, ep, id); + + if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].bulk_receiving_started) { + return; + } + + if (bulk_receiving_status->status == usb_redir_stall) { + DPRINTF("bulk receiving stopped by peer ep %02X\n", ep); + dev->endpoint[EP2I(ep)].bulk_receiving_started = 0; + } +} + +static void usbredir_control_packet(void *priv, uint64_t id, + struct usb_redir_control_packet_header *control_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + USBPacket *p; + int len = control_packet->length; + + DPRINTF("ctrl-in status %d len %d id %"PRIu64"\n", control_packet->status, + len, id); + + /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices + * to work redirected to a not superspeed capable hcd */ + if (dev->dev.speed == USB_SPEED_SUPER && + !((dev->dev.port->speedmask & USB_SPEED_MASK_SUPER)) && + control_packet->requesttype == 0x80 && + control_packet->request == 6 && + control_packet->value == 0x100 && control_packet->index == 0 && + data_len >= 18 && data[7] == 9) { + data[7] = 64; + } + + p = usbredir_find_packet_by_id(dev, 0, id); + if (p) { + usbredir_handle_status(dev, p, control_packet->status); + if (data_len > 0) { + usbredir_log_data(dev, "ctrl data in:", data, data_len); + if (data_len > sizeof(dev->dev.data_buf)) { + ERROR("ctrl buffer too small (%d > %zu)\n", + data_len, sizeof(dev->dev.data_buf)); + p->status = USB_RET_STALL; + data_len = len = sizeof(dev->dev.data_buf); + } + memcpy(dev->dev.data_buf, data, data_len); + } + p->actual_length = len; + /* + * If this is GET_DESCRIPTOR request for configuration descriptor, + * remove 'remote wakeup' flag from it to prevent idle power down + * in Windows guest + */ + if (dev->suppress_remote_wake && + control_packet->requesttype == USB_DIR_IN && + control_packet->request == USB_REQ_GET_DESCRIPTOR && + control_packet->value == (USB_DT_CONFIG << 8) && + control_packet->index == 0 && + /* bmAttributes field of config descriptor */ + len > 7 && (dev->dev.data_buf[7] & USB_CFG_ATT_WAKEUP)) { + DPRINTF("Removed remote wake %04X:%04X\n", + dev->device_info.vendor_id, + dev->device_info.product_id); + dev->dev.data_buf[7] &= ~USB_CFG_ATT_WAKEUP; + } + usb_generic_async_ctrl_complete(&dev->dev, p); + } + free(data); +} + +static void usbredir_bulk_packet(void *priv, uint64_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_high << 16) | bulk_packet->length; + USBPacket *p; + + DPRINTF("bulk-in status %d ep %02X stream %u len %d id %"PRIu64"\n", + bulk_packet->status, ep, bulk_packet->stream_id, len, id); + + p = usbredir_find_packet_by_id(dev, ep, id); + if (p) { + size_t size = usb_packet_size(p); + usbredir_handle_status(dev, p, bulk_packet->status); + if (data_len > 0) { + usbredir_log_data(dev, "bulk data in:", data, data_len); + if (data_len > size) { + ERROR("bulk got more data then requested (%d > %zd)\n", + data_len, p->iov.size); + p->status = USB_RET_BABBLE; + data_len = len = size; + } + usb_packet_copy(p, data, data_len); + } + p->actual_length = len; + if (p->pid == USB_TOKEN_IN && p->ep->pipeline) { + usb_combined_input_packet_complete(&dev->dev, p); + } else { + usb_packet_complete(&dev->dev, p); + } + } + free(data); +} + +static void usbredir_iso_packet(void *priv, uint64_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 %"PRIu64"\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, data); +} + +static void usbredir_interrupt_packet(void *priv, uint64_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 %"PRIu64"\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, data); + + /* insufficient data solved with USB_RET_NAK */ + usb_wakeup(usb_ep_get(&dev->dev, USB_TOKEN_IN, ep & 0x0f), 0); + } else { + /* + * We report output interrupt packets as completed directly upon + * submission, so all we can do here if one failed is warn. + */ + if (interrupt_packet->status) { + WARNING("interrupt output failed status %d ep %02X id %"PRIu64"\n", + interrupt_packet->status, ep, id); + } + } +} + +static void usbredir_buffered_bulk_packet(void *priv, uint64_t id, + struct usb_redir_buffered_bulk_packet_header *buffered_bulk_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t status, ep = buffered_bulk_packet->endpoint; + void *free_on_destroy; + int i, len; + + DPRINTF("buffered-bulk-in status %d ep %02X len %d id %"PRIu64"\n", + buffered_bulk_packet->status, ep, data_len, id); + + if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_BULK) { + ERROR("received buffered-bulk packet for non bulk ep %02X\n", ep); + free(data); + return; + } + + if (dev->endpoint[EP2I(ep)].bulk_receiving_started == 0) { + DPRINTF("received buffered-bulk packet on not started ep %02X\n", ep); + free(data); + return; + } + + /* Data must be in maxp chunks for buffered_bulk_add_*_data_to_packet */ + len = dev->endpoint[EP2I(ep)].max_packet_size; + status = usb_redir_success; + free_on_destroy = NULL; + for (i = 0; i < data_len; i += len) { + int r; + if (len >= (data_len - i)) { + len = data_len - i; + status = buffered_bulk_packet->status; + free_on_destroy = data; + } + /* bufp_alloc also adds the packet to the ep queue */ + r = bufp_alloc(dev, data + i, len, status, ep, free_on_destroy); + if (r) { + break; + } + } + + if (dev->endpoint[EP2I(ep)].pending_async_packet) { + USBPacket *p = dev->endpoint[EP2I(ep)].pending_async_packet; + dev->endpoint[EP2I(ep)].pending_async_packet = NULL; + usbredir_buffered_bulk_in_complete(dev, p, ep); + usb_packet_complete(&dev->dev, p); + } +} + +/* + * Migration code + */ + +static int usbredir_pre_save(void *priv) +{ + USBRedirDevice *dev = priv; + + usbredir_fill_already_in_flight(dev); + + return 0; +} + +static int usbredir_post_load(void *priv, int version_id) +{ + USBRedirDevice *dev = priv; + + if (dev == NULL || dev->parser == NULL) { + return 0; + } + + switch (dev->device_info.speed) { + case usb_redir_speed_low: + dev->dev.speed = USB_SPEED_LOW; + break; + case usb_redir_speed_full: + dev->dev.speed = USB_SPEED_FULL; + break; + case usb_redir_speed_high: + dev->dev.speed = USB_SPEED_HIGH; + break; + case usb_redir_speed_super: + dev->dev.speed = USB_SPEED_SUPER; + break; + default: + dev->dev.speed = USB_SPEED_FULL; + } + dev->dev.speedmask = (1 << dev->dev.speed); + + usbredir_setup_usb_eps(dev); + usbredir_check_bulk_receiving(dev); + + return 0; +} + +/* For usbredirparser migration */ +static int usbredir_put_parser(QEMUFile *f, void *priv, size_t unused, + const VMStateField *field, JSONWriter *vmdesc) +{ + USBRedirDevice *dev = priv; + uint8_t *data; + int len; + + if (dev->parser == NULL) { + qemu_put_be32(f, 0); + return 0; + } + + usbredirparser_serialize(dev->parser, &data, &len); + qemu_oom_check(data); + + qemu_put_be32(f, len); + qemu_put_buffer(f, data, len); + + free(data); + + return 0; +} + +static int usbredir_get_parser(QEMUFile *f, void *priv, size_t unused, + const VMStateField *field) +{ + USBRedirDevice *dev = priv; + uint8_t *data; + int len, ret; + + len = qemu_get_be32(f); + if (len == 0) { + return 0; + } + + /* + * If our chardev is not open already at this point the usbredir connection + * has been broken (non seamless migration, or restore from disk). + * + * In this case create a temporary parser to receive the migration data, + * and schedule the close_bh to report the device as disconnected to the + * guest and to destroy the parser again. + */ + if (dev->parser == NULL) { + WARNING("usb-redir connection broken during migration\n"); + usbredir_create_parser(dev); + qemu_bh_schedule(dev->chardev_close_bh); + } + + data = g_malloc(len); + qemu_get_buffer(f, data, len); + + ret = usbredirparser_unserialize(dev->parser, data, len); + + g_free(data); + + return ret; +} + +static const VMStateInfo usbredir_parser_vmstate_info = { + .name = "usb-redir-parser", + .put = usbredir_put_parser, + .get = usbredir_get_parser, +}; + + +/* For buffered packets (iso/irq) queue migration */ +static int usbredir_put_bufpq(QEMUFile *f, void *priv, size_t unused, + const VMStateField *field, JSONWriter *vmdesc) +{ + struct endp_data *endp = priv; + USBRedirDevice *dev = endp->dev; + struct buf_packet *bufp; + int len, i = 0; + + qemu_put_be32(f, endp->bufpq_size); + QTAILQ_FOREACH(bufp, &endp->bufpq, next) { + len = bufp->len - bufp->offset; + DPRINTF("put_bufpq %d/%d len %d status %d\n", i + 1, endp->bufpq_size, + len, bufp->status); + qemu_put_be32(f, len); + qemu_put_be32(f, bufp->status); + qemu_put_buffer(f, bufp->data + bufp->offset, len); + i++; + } + assert(i == endp->bufpq_size); + + return 0; +} + +static int usbredir_get_bufpq(QEMUFile *f, void *priv, size_t unused, + const VMStateField *field) +{ + struct endp_data *endp = priv; + USBRedirDevice *dev = endp->dev; + struct buf_packet *bufp; + int i; + + endp->bufpq_size = qemu_get_be32(f); + for (i = 0; i < endp->bufpq_size; i++) { + bufp = g_new(struct buf_packet, 1); + bufp->len = qemu_get_be32(f); + bufp->status = qemu_get_be32(f); + bufp->offset = 0; + bufp->data = qemu_oom_check(malloc(bufp->len)); /* regular malloc! */ + bufp->free_on_destroy = bufp->data; + qemu_get_buffer(f, bufp->data, bufp->len); + QTAILQ_INSERT_TAIL(&endp->bufpq, bufp, next); + DPRINTF("get_bufpq %d/%d len %d status %d\n", i + 1, endp->bufpq_size, + bufp->len, bufp->status); + } + return 0; +} + +static const VMStateInfo usbredir_ep_bufpq_vmstate_info = { + .name = "usb-redir-bufpq", + .put = usbredir_put_bufpq, + .get = usbredir_get_bufpq, +}; + + +/* For endp_data migration */ +static bool usbredir_bulk_receiving_needed(void *priv) +{ + struct endp_data *endp = priv; + + return endp->bulk_receiving_started; +} + +static const VMStateDescription usbredir_bulk_receiving_vmstate = { + .name = "usb-redir-ep/bulk-receiving", + .version_id = 1, + .minimum_version_id = 1, + .needed = usbredir_bulk_receiving_needed, + .fields = (VMStateField[]) { + VMSTATE_UINT8(bulk_receiving_started, struct endp_data), + VMSTATE_END_OF_LIST() + } +}; + +static bool usbredir_stream_needed(void *priv) +{ + struct endp_data *endp = priv; + + return endp->max_streams; +} + +static const VMStateDescription usbredir_stream_vmstate = { + .name = "usb-redir-ep/stream-state", + .version_id = 1, + .minimum_version_id = 1, + .needed = usbredir_stream_needed, + .fields = (VMStateField[]) { + VMSTATE_UINT32(max_streams, struct endp_data), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription usbredir_ep_vmstate = { + .name = "usb-redir-ep", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(type, struct endp_data), + VMSTATE_UINT8(interval, struct endp_data), + VMSTATE_UINT8(interface, struct endp_data), + VMSTATE_UINT16(max_packet_size, struct endp_data), + VMSTATE_UINT8(iso_started, struct endp_data), + VMSTATE_UINT8(iso_error, struct endp_data), + VMSTATE_UINT8(interrupt_started, struct endp_data), + VMSTATE_UINT8(interrupt_error, struct endp_data), + VMSTATE_UINT8(bufpq_prefilled, struct endp_data), + VMSTATE_UINT8(bufpq_dropping_packets, struct endp_data), + { + .name = "bufpq", + .version_id = 0, + .field_exists = NULL, + .size = 0, + .info = &usbredir_ep_bufpq_vmstate_info, + .flags = VMS_SINGLE, + .offset = 0, + }, + VMSTATE_INT32(bufpq_target_size, struct endp_data), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription*[]) { + &usbredir_bulk_receiving_vmstate, + &usbredir_stream_vmstate, + NULL + } +}; + + +/* For PacketIdQueue migration */ +static int usbredir_put_packet_id_q(QEMUFile *f, void *priv, size_t unused, + const VMStateField *field, + JSONWriter *vmdesc) +{ + struct PacketIdQueue *q = priv; + USBRedirDevice *dev = q->dev; + struct PacketIdQueueEntry *e; + int remain = q->size; + + DPRINTF("put_packet_id_q %s size %d\n", q->name, q->size); + qemu_put_be32(f, q->size); + QTAILQ_FOREACH(e, &q->head, next) { + qemu_put_be64(f, e->id); + remain--; + } + assert(remain == 0); + + return 0; +} + +static int usbredir_get_packet_id_q(QEMUFile *f, void *priv, size_t unused, + const VMStateField *field) +{ + struct PacketIdQueue *q = priv; + USBRedirDevice *dev = q->dev; + int i, size; + uint64_t id; + + size = qemu_get_be32(f); + DPRINTF("get_packet_id_q %s size %d\n", q->name, size); + for (i = 0; i < size; i++) { + id = qemu_get_be64(f); + packet_id_queue_add(q, id); + } + assert(q->size == size); + return 0; +} + +static const VMStateInfo usbredir_ep_packet_id_q_vmstate_info = { + .name = "usb-redir-packet-id-q", + .put = usbredir_put_packet_id_q, + .get = usbredir_get_packet_id_q, +}; + +static const VMStateDescription usbredir_ep_packet_id_queue_vmstate = { + .name = "usb-redir-packet-id-queue", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + { + .name = "queue", + .version_id = 0, + .field_exists = NULL, + .size = 0, + .info = &usbredir_ep_packet_id_q_vmstate_info, + .flags = VMS_SINGLE, + .offset = 0, + }, + VMSTATE_END_OF_LIST() + } +}; + + +/* For usb_redir_device_connect_header migration */ +static const VMStateDescription usbredir_device_info_vmstate = { + .name = "usb-redir-device-info", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(speed, struct usb_redir_device_connect_header), + VMSTATE_UINT8(device_class, struct usb_redir_device_connect_header), + VMSTATE_UINT8(device_subclass, struct usb_redir_device_connect_header), + VMSTATE_UINT8(device_protocol, struct usb_redir_device_connect_header), + VMSTATE_UINT16(vendor_id, struct usb_redir_device_connect_header), + VMSTATE_UINT16(product_id, struct usb_redir_device_connect_header), + VMSTATE_UINT16(device_version_bcd, + struct usb_redir_device_connect_header), + VMSTATE_END_OF_LIST() + } +}; + + +/* For usb_redir_interface_info_header migration */ +static const VMStateDescription usbredir_interface_info_vmstate = { + .name = "usb-redir-interface-info", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(interface_count, + struct usb_redir_interface_info_header), + VMSTATE_UINT8_ARRAY(interface, + struct usb_redir_interface_info_header, 32), + VMSTATE_UINT8_ARRAY(interface_class, + struct usb_redir_interface_info_header, 32), + VMSTATE_UINT8_ARRAY(interface_subclass, + struct usb_redir_interface_info_header, 32), + VMSTATE_UINT8_ARRAY(interface_protocol, + struct usb_redir_interface_info_header, 32), + VMSTATE_END_OF_LIST() + } +}; + + +/* And finally the USBRedirDevice vmstate itself */ +static const VMStateDescription usbredir_vmstate = { + .name = "usb-redir", + .version_id = 1, + .minimum_version_id = 1, + .pre_save = usbredir_pre_save, + .post_load = usbredir_post_load, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(dev, USBRedirDevice), + VMSTATE_TIMER_PTR(attach_timer, USBRedirDevice), + { + .name = "parser", + .version_id = 0, + .field_exists = NULL, + .size = 0, + .info = &usbredir_parser_vmstate_info, + .flags = VMS_SINGLE, + .offset = 0, + }, + VMSTATE_STRUCT_ARRAY(endpoint, USBRedirDevice, MAX_ENDPOINTS, 1, + usbredir_ep_vmstate, struct endp_data), + VMSTATE_STRUCT(cancelled, USBRedirDevice, 1, + usbredir_ep_packet_id_queue_vmstate, + struct PacketIdQueue), + VMSTATE_STRUCT(already_in_flight, USBRedirDevice, 1, + usbredir_ep_packet_id_queue_vmstate, + struct PacketIdQueue), + VMSTATE_STRUCT(device_info, USBRedirDevice, 1, + usbredir_device_info_vmstate, + struct usb_redir_device_connect_header), + VMSTATE_STRUCT(interface_info, USBRedirDevice, 1, + usbredir_interface_info_vmstate, + struct usb_redir_interface_info_header), + VMSTATE_END_OF_LIST() + } +}; + +static Property usbredir_properties[] = { + DEFINE_PROP_CHR("chardev", USBRedirDevice, cs), + DEFINE_PROP_UINT8("debug", USBRedirDevice, debug, usbredirparser_warning), + DEFINE_PROP_STRING("filter", USBRedirDevice, filter_str), + DEFINE_PROP_BOOL("streams", USBRedirDevice, enable_streams, true), + DEFINE_PROP_BOOL("suppress-remote-wake", USBRedirDevice, + suppress_remote_wake, true), + 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->realize = usbredir_realize; + uc->product_desc = "USB Redirection Device"; + uc->unrealize = usbredir_unrealize; + uc->cancel_packet = usbredir_cancel_packet; + uc->handle_reset = usbredir_handle_reset; + uc->handle_data = usbredir_handle_data; + uc->handle_control = usbredir_handle_control; + uc->flush_ep_queue = usbredir_flush_ep_queue; + uc->ep_stopped = usbredir_ep_stopped; + uc->alloc_streams = usbredir_alloc_streams; + uc->free_streams = usbredir_free_streams; + dc->vmsd = &usbredir_vmstate; + device_class_set_props(dc, usbredir_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static void usbredir_instance_init(Object *obj) +{ + USBDevice *udev = USB_DEVICE(obj); + USBRedirDevice *dev = USB_REDIRECT(udev); + + device_add_bootindex_property(obj, &dev->bootindex, + "bootindex", NULL, + &udev->qdev); +} + +static const TypeInfo usbredir_dev_info = { + .name = TYPE_USB_REDIR, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBRedirDevice), + .class_init = usbredir_class_initfn, + .instance_init = usbredir_instance_init, +}; +module_obj(TYPE_USB_REDIR); + +static void usbredir_register_types(void) +{ + type_register_static(&usbredir_dev_info); +} + +type_init(usbredir_register_types) diff --git a/hw/usb/trace-events b/hw/usb/trace-events new file mode 100644 index 000000000..b8287b63f --- /dev/null +++ b/hw/usb/trace-events @@ -0,0 +1,347 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# core.c +usb_packet_state_change(int bus, const char *port, int ep, void *p, const char *o, const char *n) "bus %d, port %s, ep %d, packet %p, state %s -> %s" +usb_packet_state_fault(int bus, const char *port, int ep, void *p, const char *o, const char *n) "bus %d, port %s, ep %d, packet %p, state %s, expected %s" + +# bus.c +usb_port_claim(int bus, const char *port) "bus %d, port %s" +usb_port_attach(int bus, const char *port, const char *devspeed, const char *portspeed) "bus %d, port %s, devspeed %s, portspeed %s" +usb_port_detach(int bus, const char *port) "bus %d, port %s" +usb_port_release(int bus, const char *port) "bus %d, port %s" + +# hcd-ohci-pci.c +usb_ohci_exit(const char *s) "%s" + +# hcd-ohci.c +usb_ohci_iso_td_read_failed(uint32_t addr) "ISO_TD read error at 0x%x" +usb_ohci_iso_td_head(uint32_t head, uint32_t tail, uint32_t flags, uint32_t bp, uint32_t next, uint32_t be, uint32_t framenum, uint32_t startframe, uint32_t framecount, int rel_frame_num) "ISO_TD ED head 0x%.8x tailp 0x%.8x\n0x%.8x 0x%.8x 0x%.8x 0x%.8x\nframe_number 0x%.8x starting_frame 0x%.8x\nframe_count 0x%.8x relative %d" +usb_ohci_iso_td_head_offset(uint32_t o0, uint32_t o1, uint32_t o2, uint32_t o3, uint32_t o4, uint32_t o5, uint32_t o6, uint32_t o7) "0x%.8x 0x%.8x 0x%.8x 0x%.8x 0x%.8x 0x%.8x 0x%.8x 0x%.8x" +usb_ohci_iso_td_relative_frame_number_neg(int rel) "ISO_TD R=%d < 0" +usb_ohci_iso_td_relative_frame_number_big(int rel, int count) "ISO_TD R=%d > FC=%d" +usb_ohci_iso_td_bad_direction(int dir) "Bad direction %d" +usb_ohci_iso_td_bad_bp_be(uint32_t bp, uint32_t be) "ISO_TD bp 0x%.8x be 0x%.8x" +usb_ohci_iso_td_bad_cc_not_accessed(uint32_t start, uint32_t next) "ISO_TD cc != not accessed 0x%.8x 0x%.8x" +usb_ohci_iso_td_bad_cc_overrun(uint32_t start, uint32_t next) "ISO_TD start_offset=0x%.8x > next_offset=0x%.8x" +usb_ohci_iso_td_so(uint32_t so, uint32_t eo, uint32_t s, uint32_t e, const char *str, ssize_t len, int ret) "0x%.8x eo 0x%.8x\nsa 0x%.8x ea 0x%.8x\ndir %s len %zu ret %d" +usb_ohci_iso_td_data_overrun(int ret, ssize_t len) "DataOverrun %d > %zu" +usb_ohci_iso_td_data_underrun(int ret) "DataUnderrun %d" +usb_ohci_iso_td_nak(int ret) "got NAK/STALL %d" +usb_ohci_iso_td_bad_response(int ret) "Bad device response %d" +usb_ohci_port_attach(int index) "port #%d" +usb_ohci_port_detach(int index) "port #%d" +usb_ohci_port_wakeup(int index) "port #%d" +usb_ohci_port_suspend(int index) "port #%d" +usb_ohci_port_reset(int index) "port #%d" +usb_ohci_remote_wakeup(const char *s) "%s: SUSPEND->RESUME" +usb_ohci_reset(const char *s) "%s" +usb_ohci_start(const char *s) "%s: USB Operational" +usb_ohci_resume(const char *s) "%s: USB Resume" +usb_ohci_stop(const char *s) "%s: USB Suspended" +usb_ohci_set_ctl(const char *s, uint32_t new_state) "%s: new state 0x%x" +usb_ohci_td_underrun(void) "" +usb_ohci_td_dev_error(void) "" +usb_ohci_td_nak(void) "" +usb_ohci_td_stall(void) "" +usb_ohci_td_babble(void) "" +usb_ohci_td_bad_device_response(int rc) "%d" +usb_ohci_td_read_error(uint32_t addr) "TD read error at 0x%x" +usb_ohci_td_bad_direction(int dir) "Bad direction %d" +usb_ohci_td_skip_async(void) "" +usb_ohci_td_pkt_hdr(uint32_t addr, int64_t pktlen, int64_t len, const char *s, int flag_r, uint32_t cbp, uint32_t be) " TD @ 0x%.8x %" PRId64 " of %" PRId64 " bytes %s r=%d cbp=0x%.8x be=0x%.8x" +usb_ohci_td_pkt_short(const char *dir, const char *buf) "%s data: %s" +usb_ohci_td_pkt_full(const char *dir, const char *buf) "%s data: %s" +usb_ohci_td_too_many_pending(void) "" +usb_ohci_td_packet_status(int status) "status=%d" +usb_ohci_ed_read_error(uint32_t addr) "ED read error at 0x%x" +usb_ohci_ed_pkt(uint32_t cur, int h, int c, uint32_t head, uint32_t tail, uint32_t next) "ED @ 0x%.8x h=%u c=%u\n head=0x%.8x tailp=0x%.8x next=0x%.8x" +usb_ohci_ed_pkt_flags(uint32_t fa, uint32_t en, uint32_t d, int s, int k, int f, uint32_t mps) "fa=%u en=%u d=%u s=%u k=%u f=%u mps=%u" +usb_ohci_hcca_read_error(uint32_t addr) "HCCA read error at 0x%x" +usb_ohci_mem_read_unaligned(uint32_t addr) "at 0x%x" +usb_ohci_mem_read_bad_offset(uint32_t addr) "0x%x" +usb_ohci_mem_write_unaligned(uint32_t addr) "at 0x%x" +usb_ohci_mem_write_bad_offset(uint32_t addr) "0x%x" +usb_ohci_process_lists(uint32_t head, uint32_t cur) "head 0x%x, cur 0x%x" +usb_ohci_set_frame_interval(const char *name, uint16_t fi_x, uint16_t fi_u) "%s: FrameInterval = 0x%x (%u)" +usb_ohci_hub_power_up(void) "powered up all ports" +usb_ohci_hub_power_down(void) "powered down all ports" +usb_ohci_init_time(int64_t frametime, int64_t bittime) "usb_bit_time=%" PRId64 " usb_frame_time=%" PRId64 +usb_ohci_die(void) "" +usb_ohci_async_complete(void) "" + +# hcd-ehci.c +usb_ehci_reset(void) "=== RESET ===" +usb_ehci_unrealize(void) "=== UNREALIZE ===" +usb_ehci_opreg_read(uint32_t addr, const char *str, uint32_t val) "rd mmio 0x%04x [%s] = 0x%x" +usb_ehci_opreg_write(uint32_t addr, const char *str, uint32_t val) "wr mmio 0x%04x [%s] = 0x%x" +usb_ehci_opreg_change(uint32_t addr, const char *str, uint32_t new, uint32_t old) "ch mmio 0x%04x [%s] = 0x%x (old: 0x%x)" +usb_ehci_portsc_read(uint32_t addr, uint32_t port, uint32_t val) "rd mmio 0x%04x [port %d] = 0x%x" +usb_ehci_portsc_write(uint32_t addr, uint32_t port, uint32_t val) "wr mmio 0x%04x [port %d] = 0x%x" +usb_ehci_portsc_change(uint32_t addr, uint32_t port, uint32_t new, uint32_t old) "ch mmio 0x%04x [port %d] = 0x%x (old: 0x%x)" +usb_ehci_usbsts(const char *sts, int state) "usbsts %s %d" +usb_ehci_state(const char *schedule, const char *state) "%s schedule %s" +usb_ehci_qh_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ 0x%08x: next 0x%08x qtds 0x%08x,0x%08x,0x%08x" +usb_ehci_qh_fields(uint32_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ 0x%08x - rl %d, mplen %d, eps %d, ep %d, dev %d" +usb_ehci_qh_bits(uint32_t addr, int c, int h, int dtc, int i) "QH @ 0x%08x - c %d, h %d, dtc %d, i %d" +usb_ehci_qtd_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ 0x%08x: next 0x%08x altnext 0x%08x" +usb_ehci_qtd_fields(uint32_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ 0x%08x - tbytes %d, cpage %d, cerr %d, pid %d" +usb_ehci_qtd_bits(uint32_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ 0x%08x - ioc %d, active %d, halt %d, babble %d, xacterr %d" +usb_ehci_itd(uint32_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ 0x%08x: next 0x%08x - mplen %d, mult %d, ep %d, dev %d" +usb_ehci_sitd(uint32_t addr, uint32_t nxt, uint32_t active) "ITD @ 0x%08x: next 0x%08x - active %d" +usb_ehci_port_attach(uint32_t port, const char *owner, const char *device) "attach port #%d, owner %s, device %s" +usb_ehci_port_detach(uint32_t port, const char *owner) "detach port #%d, owner %s" +usb_ehci_port_reset(uint32_t port, int enable) "reset port #%d - %d" +usb_ehci_port_suspend(uint32_t port) "port #%d" +usb_ehci_port_wakeup(uint32_t port) "port #%d" +usb_ehci_port_resume(uint32_t port) "port #%d" +usb_ehci_queue_action(void *q, const char *action) "q %p: %s" +usb_ehci_packet_action(void *q, void *p, const char *action) "q %p p %p: %s" +usb_ehci_irq(uint32_t level, uint32_t frindex, uint32_t sts, uint32_t mask) "level %d, frindex 0x%04x, sts 0x%x, mask 0x%x" +usb_ehci_guest_bug(const char *reason) "%s" +usb_ehci_doorbell_ring(void) "" +usb_ehci_doorbell_ack(void) "" +usb_ehci_dma_error(void) "" + +# hcd-uhci.c +usb_uhci_reset(void) "=== RESET ===" +usb_uhci_exit(void) "=== EXIT ===" +usb_uhci_schedule_start(void) "" +usb_uhci_schedule_stop(void) "" +usb_uhci_frame_start(uint32_t num) "nr %d" +usb_uhci_frame_stop_bandwidth(void) "" +usb_uhci_frame_loop_stop_idle(void) "" +usb_uhci_frame_loop_continue(void) "" +usb_uhci_mmio_readw(uint32_t addr, uint32_t val) "addr 0x%04x, ret 0x%04x" +usb_uhci_mmio_writew(uint32_t addr, uint32_t val) "addr 0x%04x, val 0x%04x" +usb_uhci_queue_add(uint32_t token) "token 0x%x" +usb_uhci_queue_del(uint32_t token, const char *reason) "token 0x%x: %s" +usb_uhci_packet_add(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_link_async(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_unlink_async(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_cancel(uint32_t token, uint32_t addr, int done) "token 0x%x, td 0x%x, done %d" +usb_uhci_packet_complete_success(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_complete_shortxfer(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_complete_stall(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_complete_babble(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_complete_error(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_packet_del(uint32_t token, uint32_t addr) "token 0x%x, td 0x%x" +usb_uhci_qh_load(uint32_t qh) "qh 0x%x" +usb_uhci_td_load(uint32_t qh, uint32_t td, uint32_t ctrl, uint32_t token) "qh 0x%x, td 0x%x, ctrl 0x%x, token 0x%x" +usb_uhci_td_queue(uint32_t td, uint32_t ctrl, uint32_t token) "td 0x%x, ctrl 0x%x, token 0x%x" +usb_uhci_td_nextqh(uint32_t qh, uint32_t td) "qh 0x%x, td 0x%x" +usb_uhci_td_async(uint32_t qh, uint32_t td) "qh 0x%x, td 0x%x" +usb_uhci_td_complete(uint32_t qh, uint32_t td) "qh 0x%x, td 0x%x" + +# hcd-xhci.c +usb_xhci_reset(void) "=== RESET ===" +usb_xhci_exit(void) "=== EXIT ===" +usb_xhci_run(void) "" +usb_xhci_stop(void) "" +usb_xhci_cap_read(uint32_t off, uint32_t val) "off 0x%04x, ret 0x%08x" +usb_xhci_oper_read(uint32_t off, uint32_t val) "off 0x%04x, ret 0x%08x" +usb_xhci_port_read(uint32_t port, uint32_t off, uint32_t val) "port %d, off 0x%04x, ret 0x%08x" +usb_xhci_runtime_read(uint32_t off, uint32_t val) "off 0x%04x, ret 0x%08x" +usb_xhci_doorbell_read(uint32_t off, uint32_t val) "off 0x%04x, ret 0x%08x" +usb_xhci_oper_write(uint32_t off, uint32_t val) "off 0x%04x, val 0x%08x" +usb_xhci_port_write(uint32_t port, uint32_t off, uint32_t val) "port %d, off 0x%04x, val 0x%08x" +usb_xhci_runtime_write(uint32_t off, uint32_t val) "off 0x%04x, val 0x%08x" +usb_xhci_doorbell_write(uint32_t off, uint32_t val) "off 0x%04x, val 0x%08x" +usb_xhci_irq_intx(uint32_t level) "level %d" +usb_xhci_irq_msi(uint32_t nr) "nr %d" +usb_xhci_irq_msix(uint32_t nr) "nr %d" +usb_xhci_irq_msix_use(uint32_t nr) "nr %d" +usb_xhci_irq_msix_unuse(uint32_t nr) "nr %d" +usb_xhci_queue_event(uint32_t vector, uint32_t idx, const char *trb, const char *evt, uint64_t param, uint32_t status, uint32_t control) "v %d, idx %d, %s, %s, p 0x%016" PRIx64 ", s 0x%08x, c 0x%08x" +usb_xhci_fetch_trb(uint64_t addr, const char *name, uint64_t param, uint32_t status, uint32_t control) "addr 0x%016" PRIx64 ", %s, p 0x%016" PRIx64 ", s 0x%08x, c 0x%08x" +usb_xhci_port_reset(uint32_t port, bool warm) "port %d, warm %d" +usb_xhci_port_link(uint32_t port, uint32_t pls) "port %d, pls %d" +usb_xhci_port_notify(uint32_t port, uint32_t pls) "port %d, bits 0x%x" +usb_xhci_slot_enable(uint32_t slotid) "slotid %d" +usb_xhci_slot_disable(uint32_t slotid) "slotid %d" +usb_xhci_slot_address(uint32_t slotid, const char *port) "slotid %d, port %s" +usb_xhci_slot_configure(uint32_t slotid) "slotid %d" +usb_xhci_slot_evaluate(uint32_t slotid) "slotid %d" +usb_xhci_slot_reset(uint32_t slotid) "slotid %d" +usb_xhci_ep_enable(uint32_t slotid, uint32_t epid) "slotid %d, epid %d" +usb_xhci_ep_disable(uint32_t slotid, uint32_t epid) "slotid %d, epid %d" +usb_xhci_ep_set_dequeue(uint32_t slotid, uint32_t epid, uint32_t streamid, uint64_t param) "slotid %d, epid %d, streamid %d, ptr 0x%016" PRIx64 +usb_xhci_ep_kick(uint32_t slotid, uint32_t epid, uint32_t streamid) "slotid %d, epid %d, streamid %d" +usb_xhci_ep_stop(uint32_t slotid, uint32_t epid) "slotid %d, epid %d" +usb_xhci_ep_reset(uint32_t slotid, uint32_t epid) "slotid %d, epid %d" +usb_xhci_ep_state(uint32_t slotid, uint32_t epid, const char *os, const char *ns) "slotid %d, epid %d, %s -> %s" +usb_xhci_xfer_start(void *xfer, uint32_t slotid, uint32_t epid, uint32_t streamid) "%p: slotid %d, epid %d, streamid %d" +usb_xhci_xfer_async(void *xfer) "%p" +usb_xhci_xfer_nak(void *xfer) "%p" +usb_xhci_xfer_retry(void *xfer) "%p" +usb_xhci_xfer_success(void *xfer, uint32_t bytes) "%p: len %d" +usb_xhci_xfer_error(void *xfer, uint32_t ret) "%p: ret %d" +usb_xhci_unimplemented(const char *item, int nr) "%s (0x%x)" +usb_xhci_enforced_limit(const char *item) "%s" + +# hcd-dwc2.c +usb_dwc2_update_irq(uint32_t level) "level=%d" +usb_dwc2_raise_global_irq(uint32_t intr) "0x%08x" +usb_dwc2_lower_global_irq(uint32_t intr) "0x%08x" +usb_dwc2_raise_host_irq(uint32_t intr) "0x%04x" +usb_dwc2_lower_host_irq(uint32_t intr) "0x%04x" +usb_dwc2_sof(int64_t next) "next SOF %" PRId64 +usb_dwc2_bus_start(void) "start SOFs" +usb_dwc2_bus_stop(void) "stop SOFs" +usb_dwc2_find_device(uint8_t addr) "%d" +usb_dwc2_port_disabled(uint32_t pnum) "port %d disabled" +usb_dwc2_device_found(uint32_t pnum) "device found on port %d" +usb_dwc2_device_not_found(void) "device not found" +usb_dwc2_handle_packet(uint32_t chan, void *dev, void *pkt, uint32_t ep, const char *type, const char *dir, uint32_t mps, uint32_t len, uint32_t pcnt) "ch %d dev %p pkt %p ep %d type %s dir %s mps %d len %d pcnt %d" +usb_dwc2_memory_read(uint32_t addr, uint32_t len) "addr %d len %d" +usb_dwc2_packet_status(const char *status, uint32_t len) "status %s len %d" +usb_dwc2_packet_error(const char *status) "ERROR %s" +usb_dwc2_async_packet(void *pkt, uint32_t chan, void *dev, uint32_t ep, const char *dir, uint32_t len) "pkt %p ch %d dev %p ep %d %s len %d" +usb_dwc2_memory_write(uint32_t addr, uint32_t len) "addr %d len %d" +usb_dwc2_packet_done(const char *status, uint32_t actual, uint32_t len, uint32_t pcnt) "status %s actual %d len %d pcnt %d" +usb_dwc2_packet_next(const char *status, uint32_t len, uint32_t pcnt) "status %s len %d pcnt %d" +usb_dwc2_attach(void *port) "port %p" +usb_dwc2_attach_speed(const char *speed) "%s-speed device attached" +usb_dwc2_detach(void *port) "port %p" +usb_dwc2_child_detach(void *port, void *child) "port %p child %p" +usb_dwc2_wakeup(void *port) "port %p" +usb_dwc2_async_packet_complete(void *port, void *pkt, uint32_t chan, void *dev, uint32_t ep, const char *dir, uint32_t len) "port %p packet %p ch %d dev %p ep %d %s len %d" +usb_dwc2_work_bh(void) "" +usb_dwc2_work_bh_service(uint32_t first, uint32_t current, void *dev, uint32_t ep) "first %d servicing %d dev %p ep %d" +usb_dwc2_work_bh_next(uint32_t chan) "next %d" +usb_dwc2_enable_chan(uint32_t chan, void *dev, void *pkt, uint32_t ep) "ch %d dev %p pkt %p ep %d" +usb_dwc2_glbreg_read(uint64_t addr, const char *reg, uint32_t val) " 0x%04" PRIx64 " %s val 0x%08x" +usb_dwc2_glbreg_write(uint64_t addr, const char *reg, uint64_t val, uint32_t old, uint64_t result) "0x%04" PRIx64 " %s val 0x%08" PRIx64 " old 0x%08x result 0x%08" PRIx64 +usb_dwc2_fszreg_read(uint64_t addr, uint32_t val) " 0x%04" PRIx64 " HPTXFSIZ val 0x%08x" +usb_dwc2_fszreg_write(uint64_t addr, uint64_t val, uint32_t old, uint64_t result) "0x%04" PRIx64 " HPTXFSIZ val 0x%08" PRIx64 " old 0x%08x result 0x%08" PRIx64 +usb_dwc2_hreg0_read(uint64_t addr, const char *reg, uint32_t val) " 0x%04" PRIx64 " %s val 0x%08x" +usb_dwc2_hreg0_write(uint64_t addr, const char *reg, uint64_t val, uint32_t old, uint64_t result) " 0x%04" PRIx64 " %s val 0x%08" PRIx64 " old 0x%08x result 0x%08" PRIx64 +usb_dwc2_hreg1_read(uint64_t addr, const char *reg, uint64_t chan, uint32_t val) " 0x%04" PRIx64 " %s%" PRId64 " val 0x%08x" +usb_dwc2_hreg1_write(uint64_t addr, const char *reg, uint64_t chan, uint64_t val, uint32_t old, uint64_t result) " 0x%04" PRIx64 " %s%" PRId64 " val 0x%08" PRIx64 " old 0x%08x result 0x%08" PRIx64 +usb_dwc2_pcgreg_read(uint64_t addr, const char *reg, uint32_t val) " 0x%04" PRIx64 " %s val 0x%08x" +usb_dwc2_pcgreg_write(uint64_t addr, const char *reg, uint64_t val, uint32_t old, uint64_t result) "0x%04" PRIx64 " %s val 0x%08" PRIx64 " old 0x%08x result 0x%08" PRIx64 +usb_dwc2_hreg2_read(uint64_t addr, uint64_t fifo, uint32_t val) " 0x%04" PRIx64 " FIFO%" PRId64 " val 0x%08x" +usb_dwc2_hreg2_write(uint64_t addr, uint64_t fifo, uint64_t val, uint32_t old, uint64_t result) " 0x%04" PRIx64 " FIFO%" PRId64 " val 0x%08" PRIx64 " old 0x%08x result 0x%08" PRIx64 +usb_dwc2_hreg0_action(const char *s) "%s" +usb_dwc2_wakeup_endpoint(void *ep, uint32_t stream) "endp %p stream %d" +usb_dwc2_work_timer(void) "" +usb_dwc2_reset_enter(void) "=== RESET enter ===" +usb_dwc2_reset_hold(void) "=== RESET hold ===" +usb_dwc2_reset_exit(void) "=== RESET exit ===" + +# desc.c +usb_desc_device(int addr, int len, int ret) "dev %d query device, len %d, ret %d" +usb_desc_device_qualifier(int addr, int len, int ret) "dev %d query device qualifier, len %d, ret %d" +usb_desc_config(int addr, int index, int len, int ret) "dev %d query config %d, len %d, ret %d" +usb_desc_other_speed_config(int addr, int index, int len, int ret) "dev %d query config %d, len %d, ret %d" +usb_desc_string(int addr, int index, int len, int ret) "dev %d query string %d, len %d, ret %d" +usb_desc_bos(int addr, int len, int ret) "dev %d bos, len %d, ret %d" +usb_desc_msos(int addr, int index, int len, int ret) "dev %d msos, index 0x%x, len %d, ret %d" +usb_set_addr(int addr) "dev %d" +usb_set_config(int addr, int config, int ret) "dev %d, config %d, ret %d" +usb_set_interface(int addr, int iface, int alt, int ret) "dev %d, interface %d, altsetting %d, ret %d" +usb_clear_device_feature(int addr, int feature, int ret) "dev %d, feature %d, ret %d" +usb_set_device_feature(int addr, int feature, int ret) "dev %d, feature %d, ret %d" + +# dev-hub.c +usb_hub_reset(int addr) "dev %d" +usb_hub_control(int addr, int request, int value, int index, int length) "dev %d, req 0x%x, value %d, index %d, langth %d" +usb_hub_get_port_status(int addr, int nr, int status, int changed) "dev %d, port %d, status 0x%x, changed 0x%x" +usb_hub_set_port_feature(int addr, int nr, const char *f) "dev %d, port %d, feature %s" +usb_hub_clear_port_feature(int addr, int nr, const char *f) "dev %d, port %d, feature %s" +usb_hub_attach(int addr, int nr) "dev %d, port %d" +usb_hub_detach(int addr, int nr) "dev %d, port %d" +usb_hub_status_report(int addr, int status) "dev %d, status 0x%x" + +# dev-storage.c +usb_msd_reset(void) "" +usb_msd_maxlun(unsigned maxlun) "%d" +usb_msd_send_status(unsigned status, unsigned tag, size_t size) "status %d, tag 0x%x, len %zd" +usb_msd_data_in(unsigned packet, unsigned remaining, unsigned total) "%d/%d (scsi %d)" +usb_msd_data_out(unsigned packet, unsigned remaining) "%d/%d" +usb_msd_packet_async(void) "" +usb_msd_packet_complete(void) "" +usb_msd_cmd_submit(unsigned lun, unsigned tag, unsigned flags, unsigned len, unsigned data_len) "lun %u, tag 0x%x, flags 0x%08x, len %d, data-len %d" +usb_msd_cmd_complete(unsigned status, unsigned tag) "status %d, tag 0x%x" +usb_msd_cmd_cancel(unsigned tag) "tag 0x%x" + +# dev-uas.c +usb_uas_reset(int addr) "dev %d" +usb_uas_command(int addr, uint16_t tag, int lun, uint32_t lun64_1, uint32_t lun64_2) "dev %d, tag 0x%x, lun %d, lun64 0x%08x-0x%08x" +usb_uas_response(int addr, uint16_t tag, uint8_t code) "dev %d, tag 0x%x, code 0x%x" +usb_uas_sense(int addr, uint16_t tag, uint8_t status) "dev %d, tag 0x%x, status 0x%x" +usb_uas_read_ready(int addr, uint16_t tag) "dev %d, tag 0x%x" +usb_uas_write_ready(int addr, uint16_t tag) "dev %d, tag 0x%x" +usb_uas_xfer_data(int addr, uint16_t tag, uint32_t copy, uint32_t uoff, uint32_t usize, uint32_t soff, uint32_t ssize) "dev %d, tag 0x%x, copy %d, usb-pkt %d/%d, scsi-buf %d/%d" +usb_uas_scsi_data(int addr, uint16_t tag, uint32_t bytes) "dev %d, tag 0x%x, bytes %d" +usb_uas_scsi_complete(int addr, uint16_t tag, uint32_t status, uint32_t resid) "dev %d, tag 0x%x, status 0x%x, residue %d" +usb_uas_tmf_abort_task(int addr, uint16_t tag, uint16_t task_tag) "dev %d, tag 0x%x, task-tag 0x%x" +usb_uas_tmf_logical_unit_reset(int addr, uint16_t tag, int lun) "dev %d, tag 0x%x, lun %d" +usb_uas_tmf_unsupported(int addr, uint16_t tag, uint32_t function) "dev %d, tag 0x%x, function 0x%x" + +# dev-mtp.c +usb_mtp_reset(int addr) "dev %d" +usb_mtp_command(int dev, uint16_t code, uint32_t trans, uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4) "dev %d, code 0x%x, trans 0x%x, args 0x%x, 0x%x, 0x%x, 0x%x, 0x%x" +usb_mtp_success(int dev, uint32_t trans, uint32_t arg0, uint32_t arg1) "dev %d, trans 0x%x, args 0x%x, 0x%x" +usb_mtp_error(int dev, uint16_t code, uint32_t trans, uint32_t arg0, uint32_t arg1) "dev %d, code 0x%x, trans 0x%x, args 0x%x, 0x%x" +usb_mtp_data_in(int dev, uint32_t trans, uint32_t len) "dev %d, trans 0x%x, len %d" +usb_mtp_xfer(int dev, uint32_t ep, uint32_t dlen, uint32_t plen) "dev %d, ep %d, %d/%d" +usb_mtp_nak(int dev, uint32_t ep) "dev %d, ep %d" +usb_mtp_stall(int dev, const char *reason) "dev %d, reason: %s" +usb_mtp_op_get_device_info(int dev) "dev %d" +usb_mtp_op_open_session(int dev) "dev %d" +usb_mtp_op_close_session(int dev) "dev %d" +usb_mtp_op_get_storage_ids(int dev) "dev %d" +usb_mtp_op_get_storage_info(int dev) "dev %d" +usb_mtp_op_get_num_objects(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s" +usb_mtp_op_get_object_handles(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s" +usb_mtp_op_get_object_info(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s" +usb_mtp_op_get_object(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s" +usb_mtp_op_get_partial_object(int dev, uint32_t handle, const char *path, uint32_t offset, uint32_t length) "dev %d, handle 0x%x, path %s, off %d, len %d" +usb_mtp_op_unknown(int dev, uint32_t code) "dev %d, command code 0x%x" +usb_mtp_object_alloc(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s" +usb_mtp_object_free(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s" +usb_mtp_add_child(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s" +usb_mtp_file_monitor_event(int dev, const char *path, const char *s) "dev %d, path %s event %s" + +# host-libusb.c +usb_host_open_started(int bus, int addr) "dev %d:%d" +usb_host_open_hostfd(int hostfd) "hostfd %d" +usb_host_open_success(int bus, int addr) "dev %d:%d" +usb_host_open_failure(int bus, int addr) "dev %d:%d" +usb_host_close(int bus, int addr) "dev %d:%d" +usb_host_attach_kernel(int bus, int addr, int interface) "dev %d:%d, if %d" +usb_host_detach_kernel(int bus, int addr, int interface) "dev %d:%d, if %d" +usb_host_set_address(int bus, int addr, int config) "dev %d:%d, address %d" +usb_host_set_config(int bus, int addr, int config) "dev %d:%d, config %d" +usb_host_set_interface(int bus, int addr, int interface, int alt) "dev %d:%d, interface %d, alt %d" +usb_host_claim_interface(int bus, int addr, int config, int interface) "dev %d:%d, config %d, if %d" +usb_host_release_interface(int bus, int addr, int interface) "dev %d:%d, if %d" +usb_host_req_control(int bus, int addr, void *p, int req, int value, int index) "dev %d:%d, packet %p, req 0x%x, value %d, index %d" +usb_host_req_data(int bus, int addr, void *p, int in, int ep, int size) "dev %d:%d, packet %p, in %d, ep %d, size %d" +usb_host_req_complete(int bus, int addr, void *p, int status, int length) "dev %d:%d, packet %p, status %d, length %d" +usb_host_req_emulated(int bus, int addr, void *p, int status) "dev %d:%d, packet %p, status %d" +usb_host_req_canceled(int bus, int addr, void *p) "dev %d:%d, packet %p" +usb_host_iso_start(int bus, int addr, int ep) "dev %d:%d, ep %d" +usb_host_iso_stop(int bus, int addr, int ep) "dev %d:%d, ep %d" +usb_host_iso_out_of_bufs(int bus, int addr, int ep) "dev %d:%d, ep %d" +usb_host_reset(int bus, int addr) "dev %d:%d" +usb_host_auto_scan_enabled(void) +usb_host_auto_scan_disabled(void) +usb_host_parse_config(int bus, int addr, int value, int active) "dev %d:%d, value %d, active %d" +usb_host_parse_interface(int bus, int addr, int num, int alt, int active) "dev %d:%d, num %d, alt %d, active %d" +usb_host_parse_endpoint(int bus, int addr, int ep, const char *dir, const char *type, int active) "dev %d:%d, ep %d, %s, %s, active %d" +usb_host_parse_error(int bus, int addr, const char *errmsg) "dev %d:%d, msg %s" +usb_host_remote_wakeup_removed(int bus, int addr) "dev %d:%d" + +# dev-serial.c +usb_serial_reset(int bus, int addr) "dev %d:%u reset" +usb_serial_handle_control(int bus, int addr, int request, int value) "dev %d:%u got control 0x%x, value 0x%x" +usb_serial_unsupported_parity(int bus, int addr, int value) "dev %d:%u unsupported parity %d" +usb_serial_unsupported_stopbits(int bus, int addr, int value) "dev %d:%u unsupported stop bits %d" +usb_serial_unsupported_control(int bus, int addr, int request, int value) "dev %d:%u got unsupported/bogus control 0x%x, value 0x%x" +usb_serial_unsupported_data_bits(int bus, int addr, int value) "dev %d:%u unsupported data bits %d, falling back to 8" +usb_serial_bad_token(int bus, int addr) "dev %d:%u bad token" +usb_serial_set_baud(int bus, int addr, int baud) "dev %d:%u baud rate %d" +usb_serial_set_data(int bus, int addr, int parity, int data, int stop) "dev %d:%u parity %c, data bits %d, stop bits %d" +usb_serial_set_flow_control(int bus, int addr, int index) "dev %d:%u flow control %d" +usb_serial_set_xonxoff(int bus, int addr, uint8_t xon, uint8_t xoff) "dev %d:%u xon 0x%x xoff 0x%x" diff --git a/hw/usb/trace.h b/hw/usb/trace.h new file mode 100644 index 000000000..f3962f2ba --- /dev/null +++ b/hw/usb/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_usb.h" diff --git a/hw/usb/tusb6010.c b/hw/usb/tusb6010.c new file mode 100644 index 000000000..1dd4071e6 --- /dev/null +++ b/hw/usb/tusb6010.c @@ -0,0 +1,850 @@ +/* + * Texas Instruments TUSB6010 emulation. + * Based on reverse-engineering of a linux driver. + * + * 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/>. + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "hw/usb/hcd-musb.h" +#include "hw/arm/omap.h" +#include "hw/hw.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "qom/object.h" + +#define TYPE_TUSB6010 "tusb6010" +OBJECT_DECLARE_SIMPLE_TYPE(TUSBState, TUSB6010) + +struct TUSBState { + SysBusDevice parent_obj; + + MemoryRegion iomem[2]; + qemu_irq irq; + MUSBState *musb; + QEMUTimer *otg_timer; + QEMUTimer *pwr_timer; + + int power; + uint32_t scratch; + uint16_t test_reset; + uint32_t prcm_config; + uint32_t prcm_mngmt; + uint16_t otg_status; + uint32_t dev_config; + int host_mode; + uint32_t intr; + uint32_t intr_ok; + uint32_t mask; + uint32_t usbip_intr; + uint32_t usbip_mask; + uint32_t gpio_intr; + uint32_t gpio_mask; + uint32_t gpio_config; + uint32_t dma_intr; + uint32_t dma_mask; + uint32_t dma_map; + uint32_t dma_config; + uint32_t ep0_config; + uint32_t rx_config[15]; + uint32_t tx_config[15]; + uint32_t wkup_mask; + uint32_t pullup[2]; + uint32_t control_config; + uint32_t otg_timer_val; +}; + +#define TUSB_DEVCLOCK 60000000 /* 60 MHz */ + +#define TUSB_VLYNQ_CTRL 0x004 + +/* Mentor Graphics OTG core registers. */ +#define TUSB_BASE_OFFSET 0x400 + +/* FIFO registers, 32-bit. */ +#define TUSB_FIFO_BASE 0x600 + +/* Device System & Control registers, 32-bit. */ +#define TUSB_SYS_REG_BASE 0x800 + +#define TUSB_DEV_CONF (TUSB_SYS_REG_BASE + 0x000) +#define TUSB_DEV_CONF_USB_HOST_MODE (1 << 16) +#define TUSB_DEV_CONF_PROD_TEST_MODE (1 << 15) +#define TUSB_DEV_CONF_SOFT_ID (1 << 1) +#define TUSB_DEV_CONF_ID_SEL (1 << 0) + +#define TUSB_PHY_OTG_CTRL_ENABLE (TUSB_SYS_REG_BASE + 0x004) +#define TUSB_PHY_OTG_CTRL (TUSB_SYS_REG_BASE + 0x008) +#define TUSB_PHY_OTG_CTRL_WRPROTECT (0xa5 << 24) +#define TUSB_PHY_OTG_CTRL_O_ID_PULLUP (1 << 23) +#define TUSB_PHY_OTG_CTRL_O_VBUS_DET_EN (1 << 19) +#define TUSB_PHY_OTG_CTRL_O_SESS_END_EN (1 << 18) +#define TUSB_PHY_OTG_CTRL_TESTM2 (1 << 17) +#define TUSB_PHY_OTG_CTRL_TESTM1 (1 << 16) +#define TUSB_PHY_OTG_CTRL_TESTM0 (1 << 15) +#define TUSB_PHY_OTG_CTRL_TX_DATA2 (1 << 14) +#define TUSB_PHY_OTG_CTRL_TX_GZ2 (1 << 13) +#define TUSB_PHY_OTG_CTRL_TX_ENABLE2 (1 << 12) +#define TUSB_PHY_OTG_CTRL_DM_PULLDOWN (1 << 11) +#define TUSB_PHY_OTG_CTRL_DP_PULLDOWN (1 << 10) +#define TUSB_PHY_OTG_CTRL_OSC_EN (1 << 9) +#define TUSB_PHY_OTG_CTRL_PHYREF_CLK(v) (((v) & 3) << 7) +#define TUSB_PHY_OTG_CTRL_PD (1 << 6) +#define TUSB_PHY_OTG_CTRL_PLL_ON (1 << 5) +#define TUSB_PHY_OTG_CTRL_EXT_RPU (1 << 4) +#define TUSB_PHY_OTG_CTRL_PWR_GOOD (1 << 3) +#define TUSB_PHY_OTG_CTRL_RESET (1 << 2) +#define TUSB_PHY_OTG_CTRL_SUSPENDM (1 << 1) +#define TUSB_PHY_OTG_CTRL_CLK_MODE (1 << 0) + +/* OTG status register */ +#define TUSB_DEV_OTG_STAT (TUSB_SYS_REG_BASE + 0x00c) +#define TUSB_DEV_OTG_STAT_PWR_CLK_GOOD (1 << 8) +#define TUSB_DEV_OTG_STAT_SESS_END (1 << 7) +#define TUSB_DEV_OTG_STAT_SESS_VALID (1 << 6) +#define TUSB_DEV_OTG_STAT_VBUS_VALID (1 << 5) +#define TUSB_DEV_OTG_STAT_VBUS_SENSE (1 << 4) +#define TUSB_DEV_OTG_STAT_ID_STATUS (1 << 3) +#define TUSB_DEV_OTG_STAT_HOST_DISCON (1 << 2) +#define TUSB_DEV_OTG_STAT_LINE_STATE (3 << 0) +#define TUSB_DEV_OTG_STAT_DP_ENABLE (1 << 1) +#define TUSB_DEV_OTG_STAT_DM_ENABLE (1 << 0) + +#define TUSB_DEV_OTG_TIMER (TUSB_SYS_REG_BASE + 0x010) +#define TUSB_DEV_OTG_TIMER_ENABLE (1 << 31) +#define TUSB_DEV_OTG_TIMER_VAL(v) ((v) & 0x07ffffff) +#define TUSB_PRCM_REV (TUSB_SYS_REG_BASE + 0x014) + +/* PRCM configuration register */ +#define TUSB_PRCM_CONF (TUSB_SYS_REG_BASE + 0x018) +#define TUSB_PRCM_CONF_SFW_CPEN (1 << 24) +#define TUSB_PRCM_CONF_SYS_CLKSEL(v) (((v) & 3) << 16) + +/* PRCM management register */ +#define TUSB_PRCM_MNGMT (TUSB_SYS_REG_BASE + 0x01c) +#define TUSB_PRCM_MNGMT_SRP_FIX_TMR(v) (((v) & 0xf) << 25) +#define TUSB_PRCM_MNGMT_SRP_FIX_EN (1 << 24) +#define TUSB_PRCM_MNGMT_VBUS_VAL_TMR(v) (((v) & 0xf) << 20) +#define TUSB_PRCM_MNGMT_VBUS_VAL_FLT_EN (1 << 19) +#define TUSB_PRCM_MNGMT_DFT_CLK_DIS (1 << 18) +#define TUSB_PRCM_MNGMT_VLYNQ_CLK_DIS (1 << 17) +#define TUSB_PRCM_MNGMT_OTG_SESS_END_EN (1 << 10) +#define TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN (1 << 9) +#define TUSB_PRCM_MNGMT_OTG_ID_PULLUP (1 << 8) +#define TUSB_PRCM_MNGMT_15_SW_EN (1 << 4) +#define TUSB_PRCM_MNGMT_33_SW_EN (1 << 3) +#define TUSB_PRCM_MNGMT_5V_CPEN (1 << 2) +#define TUSB_PRCM_MNGMT_PM_IDLE (1 << 1) +#define TUSB_PRCM_MNGMT_DEV_IDLE (1 << 0) + +/* Wake-up source clear and mask registers */ +#define TUSB_PRCM_WAKEUP_SOURCE (TUSB_SYS_REG_BASE + 0x020) +#define TUSB_PRCM_WAKEUP_CLEAR (TUSB_SYS_REG_BASE + 0x028) +#define TUSB_PRCM_WAKEUP_MASK (TUSB_SYS_REG_BASE + 0x02c) +#define TUSB_PRCM_WAKEUP_RESERVED_BITS (0xffffe << 13) +#define TUSB_PRCM_WGPIO_7 (1 << 12) +#define TUSB_PRCM_WGPIO_6 (1 << 11) +#define TUSB_PRCM_WGPIO_5 (1 << 10) +#define TUSB_PRCM_WGPIO_4 (1 << 9) +#define TUSB_PRCM_WGPIO_3 (1 << 8) +#define TUSB_PRCM_WGPIO_2 (1 << 7) +#define TUSB_PRCM_WGPIO_1 (1 << 6) +#define TUSB_PRCM_WGPIO_0 (1 << 5) +#define TUSB_PRCM_WHOSTDISCON (1 << 4) /* Host disconnect */ +#define TUSB_PRCM_WBUS (1 << 3) /* USB bus resume */ +#define TUSB_PRCM_WNORCS (1 << 2) /* NOR chip select */ +#define TUSB_PRCM_WVBUS (1 << 1) /* OTG PHY VBUS */ +#define TUSB_PRCM_WID (1 << 0) /* OTG PHY ID detect */ + +#define TUSB_PULLUP_1_CTRL (TUSB_SYS_REG_BASE + 0x030) +#define TUSB_PULLUP_2_CTRL (TUSB_SYS_REG_BASE + 0x034) +#define TUSB_INT_CTRL_REV (TUSB_SYS_REG_BASE + 0x038) +#define TUSB_INT_CTRL_CONF (TUSB_SYS_REG_BASE + 0x03c) +#define TUSB_USBIP_INT_SRC (TUSB_SYS_REG_BASE + 0x040) +#define TUSB_USBIP_INT_SET (TUSB_SYS_REG_BASE + 0x044) +#define TUSB_USBIP_INT_CLEAR (TUSB_SYS_REG_BASE + 0x048) +#define TUSB_USBIP_INT_MASK (TUSB_SYS_REG_BASE + 0x04c) +#define TUSB_DMA_INT_SRC (TUSB_SYS_REG_BASE + 0x050) +#define TUSB_DMA_INT_SET (TUSB_SYS_REG_BASE + 0x054) +#define TUSB_DMA_INT_CLEAR (TUSB_SYS_REG_BASE + 0x058) +#define TUSB_DMA_INT_MASK (TUSB_SYS_REG_BASE + 0x05c) +#define TUSB_GPIO_INT_SRC (TUSB_SYS_REG_BASE + 0x060) +#define TUSB_GPIO_INT_SET (TUSB_SYS_REG_BASE + 0x064) +#define TUSB_GPIO_INT_CLEAR (TUSB_SYS_REG_BASE + 0x068) +#define TUSB_GPIO_INT_MASK (TUSB_SYS_REG_BASE + 0x06c) + +/* NOR flash interrupt source registers */ +#define TUSB_INT_SRC (TUSB_SYS_REG_BASE + 0x070) +#define TUSB_INT_SRC_SET (TUSB_SYS_REG_BASE + 0x074) +#define TUSB_INT_SRC_CLEAR (TUSB_SYS_REG_BASE + 0x078) +#define TUSB_INT_MASK (TUSB_SYS_REG_BASE + 0x07c) +#define TUSB_INT_SRC_TXRX_DMA_DONE (1 << 24) +#define TUSB_INT_SRC_USB_IP_CORE (1 << 17) +#define TUSB_INT_SRC_OTG_TIMEOUT (1 << 16) +#define TUSB_INT_SRC_VBUS_SENSE_CHNG (1 << 15) +#define TUSB_INT_SRC_ID_STATUS_CHNG (1 << 14) +#define TUSB_INT_SRC_DEV_WAKEUP (1 << 13) +#define TUSB_INT_SRC_DEV_READY (1 << 12) +#define TUSB_INT_SRC_USB_IP_TX (1 << 9) +#define TUSB_INT_SRC_USB_IP_RX (1 << 8) +#define TUSB_INT_SRC_USB_IP_VBUS_ERR (1 << 7) +#define TUSB_INT_SRC_USB_IP_VBUS_REQ (1 << 6) +#define TUSB_INT_SRC_USB_IP_DISCON (1 << 5) +#define TUSB_INT_SRC_USB_IP_CONN (1 << 4) +#define TUSB_INT_SRC_USB_IP_SOF (1 << 3) +#define TUSB_INT_SRC_USB_IP_RST_BABBLE (1 << 2) +#define TUSB_INT_SRC_USB_IP_RESUME (1 << 1) +#define TUSB_INT_SRC_USB_IP_SUSPEND (1 << 0) + +#define TUSB_GPIO_REV (TUSB_SYS_REG_BASE + 0x080) +#define TUSB_GPIO_CONF (TUSB_SYS_REG_BASE + 0x084) +#define TUSB_DMA_CTRL_REV (TUSB_SYS_REG_BASE + 0x100) +#define TUSB_DMA_REQ_CONF (TUSB_SYS_REG_BASE + 0x104) +#define TUSB_EP0_CONF (TUSB_SYS_REG_BASE + 0x108) +#define TUSB_EP_IN_SIZE (TUSB_SYS_REG_BASE + 0x10c) +#define TUSB_DMA_EP_MAP (TUSB_SYS_REG_BASE + 0x148) +#define TUSB_EP_OUT_SIZE (TUSB_SYS_REG_BASE + 0x14c) +#define TUSB_EP_MAX_PACKET_SIZE_OFFSET (TUSB_SYS_REG_BASE + 0x188) +#define TUSB_SCRATCH_PAD (TUSB_SYS_REG_BASE + 0x1c4) +#define TUSB_WAIT_COUNT (TUSB_SYS_REG_BASE + 0x1c8) +#define TUSB_PROD_TEST_RESET (TUSB_SYS_REG_BASE + 0x1d8) + +#define TUSB_DIDR1_LO (TUSB_SYS_REG_BASE + 0x1f8) +#define TUSB_DIDR1_HI (TUSB_SYS_REG_BASE + 0x1fc) + +/* Device System & Control register bitfields */ +#define TUSB_INT_CTRL_CONF_INT_RLCYC(v) (((v) & 0x7) << 18) +#define TUSB_INT_CTRL_CONF_INT_POLARITY (1 << 17) +#define TUSB_INT_CTRL_CONF_INT_MODE (1 << 16) +#define TUSB_GPIO_CONF_DMAREQ(v) (((v) & 0x3f) << 24) +#define TUSB_DMA_REQ_CONF_BURST_SIZE(v) (((v) & 3) << 26) +#define TUSB_DMA_REQ_CONF_DMA_RQ_EN(v) (((v) & 0x3f) << 20) +#define TUSB_DMA_REQ_CONF_DMA_RQ_ASR(v) (((v) & 0xf) << 16) +#define TUSB_EP0_CONFIG_SW_EN (1 << 8) +#define TUSB_EP0_CONFIG_DIR_TX (1 << 7) +#define TUSB_EP0_CONFIG_XFR_SIZE(v) ((v) & 0x7f) +#define TUSB_EP_CONFIG_SW_EN (1 << 31) +#define TUSB_EP_CONFIG_XFR_SIZE(v) ((v) & 0x7fffffff) +#define TUSB_PROD_TEST_RESET_VAL 0xa596 + +static void tusb_intr_update(TUSBState *s) +{ + if (s->control_config & TUSB_INT_CTRL_CONF_INT_POLARITY) + qemu_set_irq(s->irq, s->intr & ~s->mask & s->intr_ok); + else + qemu_set_irq(s->irq, (!(s->intr & ~s->mask)) & s->intr_ok); +} + +static void tusb_usbip_intr_update(TUSBState *s) +{ + /* TX interrupt in the MUSB */ + if (s->usbip_intr & 0x0000ffff & ~s->usbip_mask) + s->intr |= TUSB_INT_SRC_USB_IP_TX; + else + s->intr &= ~TUSB_INT_SRC_USB_IP_TX; + + /* RX interrupt in the MUSB */ + if (s->usbip_intr & 0xffff0000 & ~s->usbip_mask) + s->intr |= TUSB_INT_SRC_USB_IP_RX; + else + s->intr &= ~TUSB_INT_SRC_USB_IP_RX; + + /* XXX: What about TUSB_INT_SRC_USB_IP_CORE? */ + + tusb_intr_update(s); +} + +static void tusb_dma_intr_update(TUSBState *s) +{ + if (s->dma_intr & ~s->dma_mask) + s->intr |= TUSB_INT_SRC_TXRX_DMA_DONE; + else + s->intr &= ~TUSB_INT_SRC_TXRX_DMA_DONE; + + tusb_intr_update(s); +} + +static void tusb_gpio_intr_update(TUSBState *s) +{ + /* TODO: How is this signalled? */ +} + +static uint32_t tusb_async_readb(void *opaque, hwaddr addr) +{ + TUSBState *s = (TUSBState *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + return musb_read[0](s->musb, addr & 0x1ff); + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + return musb_read[0](s->musb, 0x20 + ((addr >> 3) & 0x3c)); + } + + printf("%s: unknown register at %03x\n", + __func__, (int) (addr & 0xfff)); + return 0; +} + +static uint32_t tusb_async_readh(void *opaque, hwaddr addr) +{ + TUSBState *s = (TUSBState *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + return musb_read[1](s->musb, addr & 0x1ff); + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + return musb_read[1](s->musb, 0x20 + ((addr >> 3) & 0x3c)); + } + + printf("%s: unknown register at %03x\n", + __func__, (int) (addr & 0xfff)); + return 0; +} + +static uint32_t tusb_async_readw(void *opaque, hwaddr addr) +{ + TUSBState *s = (TUSBState *) opaque; + int offset = addr & 0xfff; + int epnum; + uint32_t ret; + + switch (offset) { + case TUSB_DEV_CONF: + return s->dev_config; + + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + return musb_read[2](s->musb, offset & 0x1ff); + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + return musb_read[2](s->musb, 0x20 + ((addr >> 3) & 0x3c)); + + case TUSB_PHY_OTG_CTRL_ENABLE: + case TUSB_PHY_OTG_CTRL: + return 0x00; /* TODO */ + + case TUSB_DEV_OTG_STAT: + ret = s->otg_status; +#if 0 + if (!(s->prcm_mngmt & TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN)) + ret &= ~TUSB_DEV_OTG_STAT_VBUS_VALID; +#endif + return ret; + case TUSB_DEV_OTG_TIMER: + return s->otg_timer_val; + + case TUSB_PRCM_REV: + return 0x20; + case TUSB_PRCM_CONF: + return s->prcm_config; + case TUSB_PRCM_MNGMT: + return s->prcm_mngmt; + case TUSB_PRCM_WAKEUP_SOURCE: + case TUSB_PRCM_WAKEUP_CLEAR: /* TODO: What does this one return? */ + return 0x00000000; + case TUSB_PRCM_WAKEUP_MASK: + return s->wkup_mask; + + case TUSB_PULLUP_1_CTRL: + return s->pullup[0]; + case TUSB_PULLUP_2_CTRL: + return s->pullup[1]; + + case TUSB_INT_CTRL_REV: + return 0x20; + case TUSB_INT_CTRL_CONF: + return s->control_config; + + case TUSB_USBIP_INT_SRC: + case TUSB_USBIP_INT_SET: /* TODO: What do these two return? */ + case TUSB_USBIP_INT_CLEAR: + return s->usbip_intr; + case TUSB_USBIP_INT_MASK: + return s->usbip_mask; + + case TUSB_DMA_INT_SRC: + case TUSB_DMA_INT_SET: /* TODO: What do these two return? */ + case TUSB_DMA_INT_CLEAR: + return s->dma_intr; + case TUSB_DMA_INT_MASK: + return s->dma_mask; + + case TUSB_GPIO_INT_SRC: /* TODO: What do these two return? */ + case TUSB_GPIO_INT_SET: + case TUSB_GPIO_INT_CLEAR: + return s->gpio_intr; + case TUSB_GPIO_INT_MASK: + return s->gpio_mask; + + case TUSB_INT_SRC: + case TUSB_INT_SRC_SET: /* TODO: What do these two return? */ + case TUSB_INT_SRC_CLEAR: + return s->intr; + case TUSB_INT_MASK: + return s->mask; + + case TUSB_GPIO_REV: + return 0x30; + case TUSB_GPIO_CONF: + return s->gpio_config; + + case TUSB_DMA_CTRL_REV: + return 0x30; + case TUSB_DMA_REQ_CONF: + return s->dma_config; + case TUSB_EP0_CONF: + return s->ep0_config; + case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b): + epnum = (offset - TUSB_EP_IN_SIZE) >> 2; + return s->tx_config[epnum]; + case TUSB_DMA_EP_MAP: + return s->dma_map; + case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b): + epnum = (offset - TUSB_EP_OUT_SIZE) >> 2; + return s->rx_config[epnum]; + case TUSB_EP_MAX_PACKET_SIZE_OFFSET ... + (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b): + return 0x00000000; /* TODO */ + case TUSB_WAIT_COUNT: + return 0x00; /* TODO */ + + case TUSB_SCRATCH_PAD: + return s->scratch; + + case TUSB_PROD_TEST_RESET: + return s->test_reset; + + /* DIE IDs */ + case TUSB_DIDR1_LO: + return 0xa9453c59; + case TUSB_DIDR1_HI: + return 0x54059adf; + } + + printf("%s: unknown register at %03x\n", __func__, offset); + return 0; +} + +static void tusb_async_writeb(void *opaque, hwaddr addr, + uint32_t value) +{ + TUSBState *s = (TUSBState *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + musb_write[0](s->musb, addr & 0x1ff, value); + break; + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + musb_write[0](s->musb, 0x20 + ((addr >> 3) & 0x3c), value); + break; + + default: + printf("%s: unknown register at %03x\n", + __func__, (int) (addr & 0xfff)); + return; + } +} + +static void tusb_async_writeh(void *opaque, hwaddr addr, + uint32_t value) +{ + TUSBState *s = (TUSBState *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + musb_write[1](s->musb, addr & 0x1ff, value); + break; + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + musb_write[1](s->musb, 0x20 + ((addr >> 3) & 0x3c), value); + break; + + default: + printf("%s: unknown register at %03x\n", + __func__, (int) (addr & 0xfff)); + return; + } +} + +static void tusb_async_writew(void *opaque, hwaddr addr, + uint32_t value) +{ + TUSBState *s = (TUSBState *) opaque; + int offset = addr & 0xfff; + int epnum; + + switch (offset) { + case TUSB_VLYNQ_CTRL: + break; + + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + musb_write[2](s->musb, offset & 0x1ff, value); + break; + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + musb_write[2](s->musb, 0x20 + ((addr >> 3) & 0x3c), value); + break; + + case TUSB_DEV_CONF: + s->dev_config = value; + s->host_mode = (value & TUSB_DEV_CONF_USB_HOST_MODE); + if (value & TUSB_DEV_CONF_PROD_TEST_MODE) + hw_error("%s: Product Test mode not allowed\n", __func__); + break; + + case TUSB_PHY_OTG_CTRL_ENABLE: + case TUSB_PHY_OTG_CTRL: + return; /* TODO */ + case TUSB_DEV_OTG_TIMER: + s->otg_timer_val = value; + if (value & TUSB_DEV_OTG_TIMER_ENABLE) + timer_mod(s->otg_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + muldiv64(TUSB_DEV_OTG_TIMER_VAL(value), + NANOSECONDS_PER_SECOND, TUSB_DEVCLOCK)); + else + timer_del(s->otg_timer); + break; + + case TUSB_PRCM_CONF: + s->prcm_config = value; + break; + case TUSB_PRCM_MNGMT: + s->prcm_mngmt = value; + break; + case TUSB_PRCM_WAKEUP_CLEAR: + break; + case TUSB_PRCM_WAKEUP_MASK: + s->wkup_mask = value; + break; + + case TUSB_PULLUP_1_CTRL: + s->pullup[0] = value; + break; + case TUSB_PULLUP_2_CTRL: + s->pullup[1] = value; + break; + case TUSB_INT_CTRL_CONF: + s->control_config = value; + tusb_intr_update(s); + break; + + case TUSB_USBIP_INT_SET: + s->usbip_intr |= value; + tusb_usbip_intr_update(s); + break; + case TUSB_USBIP_INT_CLEAR: + s->usbip_intr &= ~value; + tusb_usbip_intr_update(s); + musb_core_intr_clear(s->musb, ~value); + break; + case TUSB_USBIP_INT_MASK: + s->usbip_mask = value; + tusb_usbip_intr_update(s); + break; + + case TUSB_DMA_INT_SET: + s->dma_intr |= value; + tusb_dma_intr_update(s); + break; + case TUSB_DMA_INT_CLEAR: + s->dma_intr &= ~value; + tusb_dma_intr_update(s); + break; + case TUSB_DMA_INT_MASK: + s->dma_mask = value; + tusb_dma_intr_update(s); + break; + + case TUSB_GPIO_INT_SET: + s->gpio_intr |= value; + tusb_gpio_intr_update(s); + break; + case TUSB_GPIO_INT_CLEAR: + s->gpio_intr &= ~value; + tusb_gpio_intr_update(s); + break; + case TUSB_GPIO_INT_MASK: + s->gpio_mask = value; + tusb_gpio_intr_update(s); + break; + + case TUSB_INT_SRC_SET: + s->intr |= value; + tusb_intr_update(s); + break; + case TUSB_INT_SRC_CLEAR: + s->intr &= ~value; + tusb_intr_update(s); + break; + case TUSB_INT_MASK: + s->mask = value; + tusb_intr_update(s); + break; + + case TUSB_GPIO_CONF: + s->gpio_config = value; + break; + case TUSB_DMA_REQ_CONF: + s->dma_config = value; + break; + case TUSB_EP0_CONF: + s->ep0_config = value & 0x1ff; + musb_set_size(s->musb, 0, TUSB_EP0_CONFIG_XFR_SIZE(value), + value & TUSB_EP0_CONFIG_DIR_TX); + break; + case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b): + epnum = (offset - TUSB_EP_IN_SIZE) >> 2; + s->tx_config[epnum] = value; + musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 1); + break; + case TUSB_DMA_EP_MAP: + s->dma_map = value; + break; + case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b): + epnum = (offset - TUSB_EP_OUT_SIZE) >> 2; + s->rx_config[epnum] = value; + musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 0); + break; + case TUSB_EP_MAX_PACKET_SIZE_OFFSET ... + (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b): + return; /* TODO */ + case TUSB_WAIT_COUNT: + return; /* TODO */ + + case TUSB_SCRATCH_PAD: + s->scratch = value; + break; + + case TUSB_PROD_TEST_RESET: + s->test_reset = value; + break; + + default: + printf("%s: unknown register at %03x\n", __func__, offset); + return; + } +} + +static uint64_t tusb_async_readfn(void *opaque, hwaddr addr, unsigned size) +{ + switch (size) { + case 1: + return tusb_async_readb(opaque, addr); + case 2: + return tusb_async_readh(opaque, addr); + case 4: + return tusb_async_readw(opaque, addr); + default: + g_assert_not_reached(); + } +} + +static void tusb_async_writefn(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + switch (size) { + case 1: + tusb_async_writeb(opaque, addr, value); + break; + case 2: + tusb_async_writeh(opaque, addr, value); + break; + case 4: + tusb_async_writew(opaque, addr, value); + break; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps tusb_async_ops = { + .read = tusb_async_readfn, + .write = tusb_async_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void tusb_otg_tick(void *opaque) +{ + TUSBState *s = (TUSBState *) opaque; + + s->otg_timer_val = 0; + s->intr |= TUSB_INT_SRC_OTG_TIMEOUT; + tusb_intr_update(s); +} + +static void tusb_power_tick(void *opaque) +{ + TUSBState *s = (TUSBState *) opaque; + + if (s->power) { + s->intr_ok = ~0; + tusb_intr_update(s); + } +} + +static void tusb_musb_core_intr(void *opaque, int source, int level) +{ + TUSBState *s = (TUSBState *) opaque; + uint16_t otg_status = s->otg_status; + + switch (source) { + case musb_set_vbus: + if (level) + otg_status |= TUSB_DEV_OTG_STAT_VBUS_VALID; + else + otg_status &= ~TUSB_DEV_OTG_STAT_VBUS_VALID; + + /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_VBUS_DET_EN set? */ + /* XXX: only if TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN set? */ + if (s->otg_status != otg_status) { + s->otg_status = otg_status; + s->intr |= TUSB_INT_SRC_VBUS_SENSE_CHNG; + tusb_intr_update(s); + } + break; + + case musb_set_session: + /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_SESS_END_EN set? */ + /* XXX: only if TUSB_PRCM_MNGMT_OTG_SESS_END_EN set? */ + if (level) { + s->otg_status |= TUSB_DEV_OTG_STAT_SESS_VALID; + s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_END; + } else { + s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_VALID; + s->otg_status |= TUSB_DEV_OTG_STAT_SESS_END; + } + + /* XXX: some IRQ or anything? */ + break; + + case musb_irq_tx: + case musb_irq_rx: + s->usbip_intr = musb_core_intr_get(s->musb); + /* Fall through. */ + default: + if (level) + s->intr |= 1 << source; + else + s->intr &= ~(1 << source); + tusb_intr_update(s); + break; + } +} + +static void tusb6010_power(TUSBState *s, int on) +{ + if (!on) { + s->power = 0; + } else if (!s->power && on) { + s->power = 1; + /* Pull the interrupt down after TUSB6010 comes up. */ + s->intr_ok = 0; + tusb_intr_update(s); + timer_mod(s->pwr_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + NANOSECONDS_PER_SECOND / 2); + } +} + +static void tusb6010_irq(void *opaque, int source, int level) +{ + if (source) { + tusb_musb_core_intr(opaque, source - 1, level); + } else { + tusb6010_power(opaque, level); + } +} + +static void tusb6010_reset(DeviceState *dev) +{ + TUSBState *s = TUSB6010(dev); + int i; + + s->test_reset = TUSB_PROD_TEST_RESET_VAL; + s->host_mode = 0; + s->dev_config = 0; + s->otg_status = 0; /* !TUSB_DEV_OTG_STAT_ID_STATUS means host mode */ + s->power = 0; + s->mask = 0xffffffff; + s->intr = 0x00000000; + s->otg_timer_val = 0; + s->scratch = 0; + s->prcm_config = 0; + s->prcm_mngmt = 0; + s->intr_ok = 0; + s->usbip_intr = 0; + s->usbip_mask = 0; + s->gpio_intr = 0; + s->gpio_mask = 0; + s->gpio_config = 0; + s->dma_intr = 0; + s->dma_mask = 0; + s->dma_map = 0; + s->dma_config = 0; + s->ep0_config = 0; + s->wkup_mask = 0; + s->pullup[0] = s->pullup[1] = 0; + s->control_config = 0; + for (i = 0; i < 15; i++) { + s->rx_config[i] = s->tx_config[i] = 0; + } + musb_reset(s->musb); +} + +static void tusb6010_realize(DeviceState *dev, Error **errp) +{ + TUSBState *s = TUSB6010(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + s->otg_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tusb_otg_tick, s); + s->pwr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tusb_power_tick, s); + memory_region_init_io(&s->iomem[1], OBJECT(s), &tusb_async_ops, s, + "tusb-async", UINT32_MAX); + sysbus_init_mmio(sbd, &s->iomem[0]); + sysbus_init_mmio(sbd, &s->iomem[1]); + sysbus_init_irq(sbd, &s->irq); + qdev_init_gpio_in(dev, tusb6010_irq, musb_irq_max + 1); + s->musb = musb_init(dev, 1); +} + +static void tusb6010_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = tusb6010_realize; + dc->reset = tusb6010_reset; +} + +static const TypeInfo tusb6010_info = { + .name = TYPE_TUSB6010, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(TUSBState), + .class_init = tusb6010_class_init, +}; + +static void tusb6010_register_types(void) +{ + type_register_static(&tusb6010_info); +} + +type_init(tusb6010_register_types) diff --git a/hw/usb/u2f-emulated.c b/hw/usb/u2f-emulated.c new file mode 100644 index 000000000..63cceaa5f --- /dev/null +++ b/hw/usb/u2f-emulated.c @@ -0,0 +1,405 @@ +/* + * U2F USB Emulated device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * 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/osdep.h" +#include "qemu/module.h" +#include "qemu/thread.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "hw/usb.h" +#include "hw/qdev-properties.h" + +#include <u2f-emu/u2f-emu.h> + +#include "u2f.h" + +/* Counter which sync with a file */ +struct synced_counter { + /* Emulated device counter */ + struct u2f_emu_vdev_counter vdev_counter; + + /* Private attributes */ + uint32_t value; + FILE *fp; +}; + +static void counter_increment(struct u2f_emu_vdev_counter *vdev_counter) +{ + struct synced_counter *counter = (struct synced_counter *)vdev_counter; + ++counter->value; + + /* Write back */ + if (fseek(counter->fp, 0, SEEK_SET) == -1) { + return; + } + fprintf(counter->fp, "%u\n", counter->value); +} + +static uint32_t counter_read(struct u2f_emu_vdev_counter *vdev_counter) +{ + struct synced_counter *counter = (struct synced_counter *)vdev_counter; + return counter->value; +} + +typedef struct U2FEmulatedState U2FEmulatedState; + +#define PENDING_OUT_NUM 32 + +struct U2FEmulatedState { + U2FKeyState base; + + /* U2F virtual emulated device */ + u2f_emu_vdev *vdev; + QemuMutex vdev_mutex; + + /* Properties */ + char *dir; + char *cert; + char *privkey; + char *entropy; + char *counter; + struct synced_counter synced_counter; + + /* Pending packets received from the guest */ + uint8_t pending_out[PENDING_OUT_NUM][U2FHID_PACKET_SIZE]; + uint8_t pending_out_start; + uint8_t pending_out_end; + uint8_t pending_out_num; + QemuMutex pending_out_mutex; + + /* Emulation thread and sync */ + QemuCond key_cond; + QemuMutex key_mutex; + QemuThread key_thread; + bool stop_thread; + EventNotifier notifier; +}; + +#define TYPE_U2F_EMULATED "u2f-emulated" +#define EMULATED_U2F_KEY(obj) \ + OBJECT_CHECK(U2FEmulatedState, (obj), TYPE_U2F_EMULATED) + +static void u2f_emulated_reset(U2FEmulatedState *key) +{ + key->pending_out_start = 0; + key->pending_out_end = 0; + key->pending_out_num = 0; +} + +static void u2f_pending_out_add(U2FEmulatedState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + int index; + + if (key->pending_out_num >= PENDING_OUT_NUM) { + return; + } + + index = key->pending_out_end; + key->pending_out_end = (index + 1) % PENDING_OUT_NUM; + ++key->pending_out_num; + + memcpy(&key->pending_out[index], packet, U2FHID_PACKET_SIZE); +} + +static uint8_t *u2f_pending_out_get(U2FEmulatedState *key) +{ + int index; + + if (key->pending_out_num == 0) { + return NULL; + } + + index = key->pending_out_start; + key->pending_out_start = (index + 1) % PENDING_OUT_NUM; + --key->pending_out_num; + + return key->pending_out[index]; +} + +static void u2f_emulated_recv_from_guest(U2FKeyState *base, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + U2FEmulatedState *key = EMULATED_U2F_KEY(base); + + qemu_mutex_lock(&key->pending_out_mutex); + u2f_pending_out_add(key, packet); + qemu_mutex_unlock(&key->pending_out_mutex); + + qemu_mutex_lock(&key->key_mutex); + qemu_cond_signal(&key->key_cond); + qemu_mutex_unlock(&key->key_mutex); +} + +static void *u2f_emulated_thread(void* arg) +{ + U2FEmulatedState *key = arg; + uint8_t packet[U2FHID_PACKET_SIZE]; + uint8_t *packet_out = NULL; + + + while (true) { + /* Wait signal */ + qemu_mutex_lock(&key->key_mutex); + qemu_cond_wait(&key->key_cond, &key->key_mutex); + qemu_mutex_unlock(&key->key_mutex); + + /* Exit thread check */ + if (key->stop_thread) { + key->stop_thread = false; + break; + } + + qemu_mutex_lock(&key->pending_out_mutex); + packet_out = u2f_pending_out_get(key); + if (packet_out == NULL) { + qemu_mutex_unlock(&key->pending_out_mutex); + continue; + } + memcpy(packet, packet_out, U2FHID_PACKET_SIZE); + qemu_mutex_unlock(&key->pending_out_mutex); + + qemu_mutex_lock(&key->vdev_mutex); + u2f_emu_vdev_send(key->vdev, U2F_EMU_USB, packet, + U2FHID_PACKET_SIZE); + + /* Notify response */ + if (u2f_emu_vdev_has_response(key->vdev, U2F_EMU_USB)) { + event_notifier_set(&key->notifier); + } + qemu_mutex_unlock(&key->vdev_mutex); + } + return NULL; +} + +static ssize_t u2f_emulated_read(const char *path, char *buffer, + size_t buffer_len) +{ + int fd; + ssize_t ret; + + fd = qemu_open_old(path, O_RDONLY); + if (fd < 0) { + return -1; + } + + ret = read(fd, buffer, buffer_len); + close(fd); + + return ret; +} + +static bool u2f_emulated_setup_counter(const char *path, + struct synced_counter *counter) +{ + int fd, ret; + FILE *fp; + + fd = qemu_open_old(path, O_RDWR); + if (fd < 0) { + return false; + } + fp = fdopen(fd, "r+"); + if (fp == NULL) { + close(fd); + return false; + } + ret = fscanf(fp, "%u", &counter->value); + if (ret == EOF) { + fclose(fp); + return false; + } + counter->fp = fp; + counter->vdev_counter.counter_increment = counter_increment; + counter->vdev_counter.counter_read = counter_read; + + return true; +} + +static u2f_emu_rc u2f_emulated_setup_vdev_manualy(U2FEmulatedState *key) +{ + ssize_t ret; + char cert_pem[4096], privkey_pem[2048]; + struct u2f_emu_vdev_setup setup_info; + + /* Certificate */ + ret = u2f_emulated_read(key->cert, cert_pem, sizeof(cert_pem)); + if (ret < 0) { + return -1; + } + + /* Private key */ + ret = u2f_emulated_read(key->privkey, privkey_pem, sizeof(privkey_pem)); + if (ret < 0) { + return -1; + } + + /* Entropy */ + ret = u2f_emulated_read(key->entropy, (char *)&setup_info.entropy, + sizeof(setup_info.entropy)); + if (ret < 0) { + return -1; + } + + /* Counter */ + if (!u2f_emulated_setup_counter(key->counter, &key->synced_counter)) { + return -1; + } + + /* Setup */ + setup_info.certificate = cert_pem; + setup_info.private_key = privkey_pem; + setup_info.counter = (struct u2f_emu_vdev_counter *)&key->synced_counter; + + return u2f_emu_vdev_new(&key->vdev, &setup_info); +} + +static void u2f_emulated_event_handler(EventNotifier *notifier) +{ + U2FEmulatedState *key = container_of(notifier, U2FEmulatedState, notifier); + size_t packet_size; + uint8_t *packet_in = NULL; + + event_notifier_test_and_clear(&key->notifier); + qemu_mutex_lock(&key->vdev_mutex); + while (u2f_emu_vdev_has_response(key->vdev, U2F_EMU_USB)) { + packet_size = u2f_emu_vdev_get_response(key->vdev, U2F_EMU_USB, + &packet_in); + if (packet_size == U2FHID_PACKET_SIZE) { + u2f_send_to_guest(&key->base, packet_in); + } + u2f_emu_vdev_free_response(packet_in); + } + qemu_mutex_unlock(&key->vdev_mutex); +} + +static void u2f_emulated_realize(U2FKeyState *base, Error **errp) +{ + U2FEmulatedState *key = EMULATED_U2F_KEY(base); + u2f_emu_rc rc; + + if (key->cert != NULL || key->privkey != NULL || key->entropy != NULL + || key->counter != NULL) { + if (key->cert != NULL && key->privkey != NULL + && key->entropy != NULL && key->counter != NULL) { + rc = u2f_emulated_setup_vdev_manualy(key); + } else { + error_setg(errp, "%s: cert, priv, entropy and counter " + "parameters must be provided to manually configure " + "the emulated device", TYPE_U2F_EMULATED); + return; + } + } else if (key->dir != NULL) { + rc = u2f_emu_vdev_new_from_dir(&key->vdev, key->dir); + } else { + rc = u2f_emu_vdev_new_ephemeral(&key->vdev); + } + + if (rc != U2F_EMU_OK) { + error_setg(errp, "%s: Failed to setup the key", TYPE_U2F_EMULATED); + return; + } + + if (event_notifier_init(&key->notifier, false) < 0) { + error_setg(errp, "%s: Failed to initialize notifier", + TYPE_U2F_EMULATED); + return; + } + /* Notifier */ + event_notifier_set_handler(&key->notifier, u2f_emulated_event_handler); + + /* Synchronization */ + qemu_cond_init(&key->key_cond); + qemu_mutex_init(&key->vdev_mutex); + qemu_mutex_init(&key->pending_out_mutex); + qemu_mutex_init(&key->key_mutex); + u2f_emulated_reset(key); + + /* Thread */ + key->stop_thread = false; + qemu_thread_create(&key->key_thread, "u2f-key", u2f_emulated_thread, + key, QEMU_THREAD_JOINABLE); +} + +static void u2f_emulated_unrealize(U2FKeyState *base) +{ + U2FEmulatedState *key = EMULATED_U2F_KEY(base); + + /* Thread */ + key->stop_thread = true; + qemu_cond_signal(&key->key_cond); + qemu_thread_join(&key->key_thread); + + /* Notifier */ + event_notifier_set_handler(&key->notifier, NULL); + event_notifier_cleanup(&key->notifier); + + /* Synchronization */ + qemu_cond_destroy(&key->key_cond); + qemu_mutex_destroy(&key->vdev_mutex); + qemu_mutex_destroy(&key->key_mutex); + qemu_mutex_destroy(&key->pending_out_mutex); + + /* Vdev */ + u2f_emu_vdev_free(key->vdev); + if (key->synced_counter.fp != NULL) { + fclose(key->synced_counter.fp); + } +} + +static Property u2f_emulated_properties[] = { + DEFINE_PROP_STRING("dir", U2FEmulatedState, dir), + DEFINE_PROP_STRING("cert", U2FEmulatedState, cert), + DEFINE_PROP_STRING("privkey", U2FEmulatedState, privkey), + DEFINE_PROP_STRING("entropy", U2FEmulatedState, entropy), + DEFINE_PROP_STRING("counter", U2FEmulatedState, counter), + DEFINE_PROP_END_OF_LIST(), +}; + +static void u2f_emulated_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + U2FKeyClass *kc = U2F_KEY_CLASS(klass); + + kc->realize = u2f_emulated_realize; + kc->unrealize = u2f_emulated_unrealize; + kc->recv_from_guest = u2f_emulated_recv_from_guest; + dc->desc = "QEMU U2F emulated key"; + device_class_set_props(dc, u2f_emulated_properties); +} + +static const TypeInfo u2f_key_emulated_info = { + .name = TYPE_U2F_EMULATED, + .parent = TYPE_U2F_KEY, + .instance_size = sizeof(U2FEmulatedState), + .class_init = u2f_emulated_class_init +}; + +static void u2f_key_emulated_register_types(void) +{ + type_register_static(&u2f_key_emulated_info); +} + +type_init(u2f_key_emulated_register_types) diff --git a/hw/usb/u2f-passthru.c b/hw/usb/u2f-passthru.c new file mode 100644 index 000000000..fc93429c9 --- /dev/null +++ b/hw/usb/u2f-passthru.c @@ -0,0 +1,552 @@ +/* + * U2F USB Passthru device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * 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/osdep.h" +#include "qemu/module.h" +#include "qemu/main-loop.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "migration/vmstate.h" + +#include "u2f.h" + +#ifdef CONFIG_LIBUDEV +#include <libudev.h> +#endif +#include <linux/hidraw.h> +#include <sys/ioctl.h> + +#define NONCE_SIZE 8 +#define BROADCAST_CID 0xFFFFFFFF +#define TRANSACTION_TIMEOUT 120000 + +struct transaction { + uint32_t cid; + uint16_t resp_bcnt; + uint16_t resp_size; + + /* Nonce for broadcast isolation */ + uint8_t nonce[NONCE_SIZE]; +}; + +typedef struct U2FPassthruState U2FPassthruState; + +#define CURRENT_TRANSACTIONS_NUM 4 + +struct U2FPassthruState { + U2FKeyState base; + + /* Host device */ + char *hidraw; + int hidraw_fd; + + /* Current Transactions */ + struct transaction current_transactions[CURRENT_TRANSACTIONS_NUM]; + uint8_t current_transactions_start; + uint8_t current_transactions_end; + uint8_t current_transactions_num; + + /* Transaction time checking */ + int64_t last_transaction_time; + QEMUTimer timer; +}; + +#define TYPE_U2F_PASSTHRU "u2f-passthru" +#define PASSTHRU_U2F_KEY(obj) \ + OBJECT_CHECK(U2FPassthruState, (obj), TYPE_U2F_PASSTHRU) + +/* Init packet sizes */ +#define PACKET_INIT_HEADER_SIZE 7 +#define PACKET_INIT_DATA_SIZE (U2FHID_PACKET_SIZE - PACKET_INIT_HEADER_SIZE) + +/* Cont packet sizes */ +#define PACKET_CONT_HEADER_SIZE 5 +#define PACKET_CONT_DATA_SIZE (U2FHID_PACKET_SIZE - PACKET_CONT_HEADER_SIZE) + +struct packet_init { + uint32_t cid; + uint8_t cmd; + uint8_t bcnth; + uint8_t bcntl; + uint8_t data[PACKET_INIT_DATA_SIZE]; +} QEMU_PACKED; + +static inline uint32_t packet_get_cid(const void *packet) +{ + return *((uint32_t *)packet); +} + +static inline bool packet_is_init(const void *packet) +{ + return ((uint8_t *)packet)[4] & (1 << 7); +} + +static inline uint16_t packet_init_get_bcnt( + const struct packet_init *packet_init) +{ + uint16_t bcnt = 0; + bcnt |= packet_init->bcnth << 8; + bcnt |= packet_init->bcntl; + + return bcnt; +} + +static void u2f_passthru_reset(U2FPassthruState *key) +{ + timer_del(&key->timer); + qemu_set_fd_handler(key->hidraw_fd, NULL, NULL, key); + key->last_transaction_time = 0; + key->current_transactions_start = 0; + key->current_transactions_end = 0; + key->current_transactions_num = 0; +} + +static void u2f_timeout_check(void *opaque) +{ + U2FPassthruState *key = opaque; + int64_t time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + + if (time > key->last_transaction_time + TRANSACTION_TIMEOUT) { + u2f_passthru_reset(key); + } else { + timer_mod(&key->timer, time + TRANSACTION_TIMEOUT / 4); + } +} + +static int u2f_transaction_get_index(U2FPassthruState *key, uint32_t cid) +{ + for (int i = 0; i < key->current_transactions_num; ++i) { + int index = (key->current_transactions_start + i) + % CURRENT_TRANSACTIONS_NUM; + if (cid == key->current_transactions[index].cid) { + return index; + } + } + return -1; +} + +static struct transaction *u2f_transaction_get(U2FPassthruState *key, + uint32_t cid) +{ + int index = u2f_transaction_get_index(key, cid); + if (index < 0) { + return NULL; + } + return &key->current_transactions[index]; +} + +static struct transaction *u2f_transaction_get_from_nonce(U2FPassthruState *key, + const uint8_t nonce[NONCE_SIZE]) +{ + for (int i = 0; i < key->current_transactions_num; ++i) { + int index = (key->current_transactions_start + i) + % CURRENT_TRANSACTIONS_NUM; + if (key->current_transactions[index].cid == BROADCAST_CID + && memcmp(nonce, key->current_transactions[index].nonce, + NONCE_SIZE) == 0) { + return &key->current_transactions[index]; + } + } + return NULL; +} + +static void u2f_transaction_close(U2FPassthruState *key, uint32_t cid) +{ + int index, next_index; + index = u2f_transaction_get_index(key, cid); + if (index < 0) { + return; + } + next_index = (index + 1) % CURRENT_TRANSACTIONS_NUM; + + /* Rearrange to ensure the oldest is at the start position */ + while (next_index != key->current_transactions_end) { + memcpy(&key->current_transactions[index], + &key->current_transactions[next_index], + sizeof(struct transaction)); + + index = next_index; + next_index = (index + 1) % CURRENT_TRANSACTIONS_NUM; + } + + key->current_transactions_end = index; + --key->current_transactions_num; + + if (key->current_transactions_num == 0) { + u2f_passthru_reset(key); + } +} + +static void u2f_transaction_add(U2FPassthruState *key, uint32_t cid, + const uint8_t nonce[NONCE_SIZE]) +{ + uint8_t index; + struct transaction *transaction; + + if (key->current_transactions_num >= CURRENT_TRANSACTIONS_NUM) { + /* Close the oldest transaction */ + index = key->current_transactions_start; + transaction = &key->current_transactions[index]; + u2f_transaction_close(key, transaction->cid); + } + + /* Index */ + index = key->current_transactions_end; + key->current_transactions_end = (index + 1) % CURRENT_TRANSACTIONS_NUM; + ++key->current_transactions_num; + + /* Transaction */ + transaction = &key->current_transactions[index]; + transaction->cid = cid; + transaction->resp_bcnt = 0; + transaction->resp_size = 0; + + /* Nonce */ + if (nonce != NULL) { + memcpy(transaction->nonce, nonce, NONCE_SIZE); + } +} + +static void u2f_passthru_read(void *opaque); + +static void u2f_transaction_start(U2FPassthruState *key, + const struct packet_init *packet_init) +{ + int64_t time; + + /* Transaction */ + if (packet_init->cid == BROADCAST_CID) { + u2f_transaction_add(key, packet_init->cid, packet_init->data); + } else { + u2f_transaction_add(key, packet_init->cid, NULL); + } + + /* Time */ + time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + if (key->last_transaction_time == 0) { + qemu_set_fd_handler(key->hidraw_fd, u2f_passthru_read, NULL, key); + timer_init_ms(&key->timer, QEMU_CLOCK_VIRTUAL, u2f_timeout_check, key); + timer_mod(&key->timer, time + TRANSACTION_TIMEOUT / 4); + } + key->last_transaction_time = time; +} + +static void u2f_passthru_recv_from_host(U2FPassthruState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + struct transaction *transaction; + uint32_t cid; + + /* Retrieve transaction */ + cid = packet_get_cid(packet); + if (cid == BROADCAST_CID) { + struct packet_init *packet_init; + if (!packet_is_init(packet)) { + return; + } + packet_init = (struct packet_init *)packet; + transaction = u2f_transaction_get_from_nonce(key, packet_init->data); + } else { + transaction = u2f_transaction_get(key, cid); + } + + /* Ignore no started transaction */ + if (transaction == NULL) { + return; + } + + if (packet_is_init(packet)) { + struct packet_init *packet_init = (struct packet_init *)packet; + transaction->resp_bcnt = packet_init_get_bcnt(packet_init); + transaction->resp_size = PACKET_INIT_DATA_SIZE; + + if (packet_init->cid == BROADCAST_CID) { + /* Nonce checking for legitimate response */ + if (memcmp(transaction->nonce, packet_init->data, NONCE_SIZE) + != 0) { + return; + } + } + } else { + transaction->resp_size += PACKET_CONT_DATA_SIZE; + } + + /* Transaction end check */ + if (transaction->resp_size >= transaction->resp_bcnt) { + u2f_transaction_close(key, cid); + } + u2f_send_to_guest(&key->base, packet); +} + +static void u2f_passthru_read(void *opaque) +{ + U2FPassthruState *key = opaque; + U2FKeyState *base = &key->base; + uint8_t packet[2 * U2FHID_PACKET_SIZE]; + int ret; + + /* Full size base queue check */ + if (base->pending_in_num >= U2FHID_PENDING_IN_NUM) { + return; + } + + ret = read(key->hidraw_fd, packet, sizeof(packet)); + if (ret < 0) { + /* Detach */ + if (base->dev.attached) { + usb_device_detach(&base->dev); + u2f_passthru_reset(key); + } + return; + } + if (ret != U2FHID_PACKET_SIZE) { + return; + } + u2f_passthru_recv_from_host(key, packet); +} + +static void u2f_passthru_recv_from_guest(U2FKeyState *base, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + U2FPassthruState *key = PASSTHRU_U2F_KEY(base); + uint8_t host_packet[U2FHID_PACKET_SIZE + 1]; + ssize_t written; + + if (packet_is_init(packet)) { + u2f_transaction_start(key, (struct packet_init *)packet); + } + + host_packet[0] = 0; + memcpy(host_packet + 1, packet, U2FHID_PACKET_SIZE); + + written = write(key->hidraw_fd, host_packet, sizeof(host_packet)); + if (written != sizeof(host_packet)) { + error_report("%s: Bad written size (req 0x%zu, val 0x%zd)", + TYPE_U2F_PASSTHRU, sizeof(host_packet), written); + } +} + +static bool u2f_passthru_is_u2f_device(int fd) +{ + int ret, rdesc_size; + struct hidraw_report_descriptor rdesc; + const uint8_t u2f_hid_report_desc_header[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO) */ + 0x09, 0x01, /* Usage (FIDO) */ + }; + + /* Get report descriptor size */ + ret = ioctl(fd, HIDIOCGRDESCSIZE, &rdesc_size); + if (ret < 0 || rdesc_size < sizeof(u2f_hid_report_desc_header)) { + return false; + } + + /* Get report descriptor */ + memset(&rdesc, 0x0, sizeof(rdesc)); + rdesc.size = rdesc_size; + ret = ioctl(fd, HIDIOCGRDESC, &rdesc); + if (ret < 0) { + return false; + } + + /* Header bytes cover specific U2F rdesc values */ + return memcmp(u2f_hid_report_desc_header, rdesc.value, + sizeof(u2f_hid_report_desc_header)) == 0; +} + +#ifdef CONFIG_LIBUDEV +static int u2f_passthru_open_from_device(struct udev_device *device) +{ + const char *devnode = udev_device_get_devnode(device); + + int fd = qemu_open_old(devnode, O_RDWR); + if (fd < 0) { + return -1; + } else if (!u2f_passthru_is_u2f_device(fd)) { + qemu_close(fd); + return -1; + } + return fd; +} + +static int u2f_passthru_open_from_enumerate(struct udev *udev, + struct udev_enumerate *enumerate) +{ + struct udev_list_entry *devices, *entry; + int ret, fd; + + ret = udev_enumerate_scan_devices(enumerate); + if (ret < 0) { + return -1; + } + + devices = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(entry, devices) { + struct udev_device *device; + const char *syspath = udev_list_entry_get_name(entry); + + if (syspath == NULL) { + continue; + } + + device = udev_device_new_from_syspath(udev, syspath); + if (device == NULL) { + continue; + } + + fd = u2f_passthru_open_from_device(device); + udev_device_unref(device); + if (fd >= 0) { + return fd; + } + } + return -1; +} + +static int u2f_passthru_open_from_scan(void) +{ + struct udev *udev; + struct udev_enumerate *enumerate; + int ret, fd = -1; + + udev = udev_new(); + if (udev == NULL) { + return -1; + } + + enumerate = udev_enumerate_new(udev); + if (enumerate == NULL) { + udev_unref(udev); + return -1; + } + + ret = udev_enumerate_add_match_subsystem(enumerate, "hidraw"); + if (ret >= 0) { + fd = u2f_passthru_open_from_enumerate(udev, enumerate); + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return fd; +} +#endif + +static void u2f_passthru_unrealize(U2FKeyState *base) +{ + U2FPassthruState *key = PASSTHRU_U2F_KEY(base); + + u2f_passthru_reset(key); + qemu_close(key->hidraw_fd); +} + +static void u2f_passthru_realize(U2FKeyState *base, Error **errp) +{ + U2FPassthruState *key = PASSTHRU_U2F_KEY(base); + int fd; + + if (key->hidraw == NULL) { +#ifdef CONFIG_LIBUDEV + fd = u2f_passthru_open_from_scan(); + if (fd < 0) { + error_setg(errp, "%s: Failed to find a U2F USB device", + TYPE_U2F_PASSTHRU); + return; + } +#else + error_setg(errp, "%s: Missing hidraw", TYPE_U2F_PASSTHRU); + return; +#endif + } else { + fd = qemu_open_old(key->hidraw, O_RDWR); + if (fd < 0) { + error_setg(errp, "%s: Failed to open %s", TYPE_U2F_PASSTHRU, + key->hidraw); + return; + } + + if (!u2f_passthru_is_u2f_device(fd)) { + qemu_close(fd); + error_setg(errp, "%s: Passed hidraw does not represent " + "a U2F HID device", TYPE_U2F_PASSTHRU); + return; + } + } + key->hidraw_fd = fd; + u2f_passthru_reset(key); +} + +static int u2f_passthru_post_load(void *opaque, int version_id) +{ + U2FPassthruState *key = opaque; + u2f_passthru_reset(key); + return 0; +} + +static const VMStateDescription u2f_passthru_vmstate = { + .name = "u2f-key-passthru", + .version_id = 1, + .minimum_version_id = 1, + .post_load = u2f_passthru_post_load, + .fields = (VMStateField[]) { + VMSTATE_U2F_KEY(base, U2FPassthruState), + VMSTATE_END_OF_LIST() + } +}; + +static Property u2f_passthru_properties[] = { + DEFINE_PROP_STRING("hidraw", U2FPassthruState, hidraw), + DEFINE_PROP_END_OF_LIST(), +}; + +static void u2f_passthru_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + U2FKeyClass *kc = U2F_KEY_CLASS(klass); + + kc->realize = u2f_passthru_realize; + kc->unrealize = u2f_passthru_unrealize; + kc->recv_from_guest = u2f_passthru_recv_from_guest; + dc->desc = "QEMU U2F passthrough key"; + dc->vmsd = &u2f_passthru_vmstate; + device_class_set_props(dc, u2f_passthru_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo u2f_key_passthru_info = { + .name = TYPE_U2F_PASSTHRU, + .parent = TYPE_U2F_KEY, + .instance_size = sizeof(U2FPassthruState), + .class_init = u2f_passthru_class_init +}; + +static void u2f_key_passthru_register_types(void) +{ + type_register_static(&u2f_key_passthru_info); +} + +type_init(u2f_key_passthru_register_types) diff --git a/hw/usb/u2f.c b/hw/usb/u2f.c new file mode 100644 index 000000000..56001249a --- /dev/null +++ b/hw/usb/u2f.c @@ -0,0 +1,351 @@ +/* + * U2F USB device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * 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/osdep.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/usb.h" +#include "hw/usb/hid.h" +#include "migration/vmstate.h" +#include "desc.h" + +#include "u2f.h" + +/* U2F key Vendor / Product */ +#define U2F_KEY_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ +#define U2F_KEY_PRODUCT_NUM 0x0005 + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_CONFIG, + STR_INTERFACE +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [STR_PRODUCT] = "U2F USB key", + [STR_SERIALNUMBER] = "0", + [STR_CONFIG] = "U2F key config", + [STR_INTERFACE] = "U2F key interface" +}; + +static const USBDescIface desc_iface_u2f_key = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0x0, + .bInterfaceProtocol = 0x0, + .ndesc = 1, + .descs = (USBDescOther[]) { + { + /* HID descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_HID, /* u8 bDescriptorType */ + 0x10, 0x01, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + USB_DT_REPORT, /* u8 type: Report */ + 0x22, 0, /* u16 len */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = U2FHID_PACKET_SIZE, + .bInterval = 0x05, + }, { + .bEndpointAddress = USB_DIR_OUT | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = U2FHID_PACKET_SIZE, + .bInterval = 0x05, + }, + }, + +}; + +static const USBDescDevice desc_device_u2f_key = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = U2FHID_PACKET_SIZE, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG, + .bmAttributes = USB_CFG_ATT_ONE, + .bMaxPower = 15, + .nif = 1, + .ifs = &desc_iface_u2f_key, + }, + }, +}; + +static const USBDesc desc_u2f_key = { + .id = { + .idVendor = U2F_KEY_VENDOR_NUM, + .idProduct = U2F_KEY_PRODUCT_NUM, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_u2f_key, + .str = desc_strings, +}; + +static const uint8_t u2f_key_hid_report_desc[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO) */ + 0x09, 0x01, /* Usage (FIDO) */ + 0xa1, 0x01, /* Collection (HID Application) */ + 0x09, 0x20, /* Usage (FIDO data in) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (0xff) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (0x40) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x09, 0x21, /* Usage (FIDO data out) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (0xFF) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (0x40) */ + 0x91, 0x02, /* Output (Data, Variable, Absolute) */ + 0xC0 /* End Collection */ +}; + +static void u2f_key_reset(U2FKeyState *key) +{ + key->pending_in_start = 0; + key->pending_in_end = 0; + key->pending_in_num = 0; +} + +static void u2f_key_handle_reset(USBDevice *dev) +{ + U2FKeyState *key = U2F_KEY(dev); + + u2f_key_reset(key); +} + +static void u2f_key_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + U2FKeyState *key = U2F_KEY(dev); + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch (request) { + case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: + switch (value >> 8) { + case 0x22: + memcpy(data, u2f_key_hid_report_desc, + sizeof(u2f_key_hid_report_desc)); + p->actual_length = sizeof(u2f_key_hid_report_desc); + break; + default: + goto fail; + } + break; + case HID_GET_IDLE: + data[0] = key->idle; + p->actual_length = 1; + break; + case HID_SET_IDLE: + key->idle = (uint8_t)(value >> 8); + break; + default: + fail: + p->status = USB_RET_STALL; + break; + } + +} + +static void u2f_key_recv_from_guest(U2FKeyState *key, USBPacket *p) +{ + U2FKeyClass *kc = U2F_KEY_GET_CLASS(key); + uint8_t packet[U2FHID_PACKET_SIZE]; + + if (kc->recv_from_guest == NULL || p->iov.size != U2FHID_PACKET_SIZE) { + return; + } + + usb_packet_copy(p, packet, p->iov.size); + kc->recv_from_guest(key, packet); +} + +static void u2f_pending_in_add(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + uint8_t index; + + if (key->pending_in_num >= U2FHID_PENDING_IN_NUM) { + return; + } + + index = key->pending_in_end; + key->pending_in_end = (index + 1) % U2FHID_PENDING_IN_NUM; + ++key->pending_in_num; + + memcpy(key->pending_in[index], packet, U2FHID_PACKET_SIZE); +} + +static uint8_t *u2f_pending_in_get(U2FKeyState *key) +{ + uint8_t index; + + if (key->pending_in_num == 0) { + return NULL; + } + + index = key->pending_in_start; + key->pending_in_start = (index + 1) % U2FHID_PENDING_IN_NUM; + --key->pending_in_num; + + return key->pending_in[index]; +} + +static void u2f_key_handle_data(USBDevice *dev, USBPacket *p) +{ + U2FKeyState *key = U2F_KEY(dev); + uint8_t *packet_in; + + /* Endpoint number check */ + if (p->ep->nr != 1) { + p->status = USB_RET_STALL; + return; + } + + switch (p->pid) { + case USB_TOKEN_OUT: + u2f_key_recv_from_guest(key, p); + break; + case USB_TOKEN_IN: + packet_in = u2f_pending_in_get(key); + if (packet_in == NULL) { + p->status = USB_RET_NAK; + return; + } + usb_packet_copy(p, packet_in, U2FHID_PACKET_SIZE); + break; + default: + p->status = USB_RET_STALL; + break; + } +} + +void u2f_send_to_guest(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + u2f_pending_in_add(key, packet); + usb_wakeup(key->ep, 0); +} + +static void u2f_key_unrealize(USBDevice *dev) +{ + U2FKeyState *key = U2F_KEY(dev); + U2FKeyClass *kc = U2F_KEY_GET_CLASS(key); + + if (kc->unrealize != NULL) { + kc->unrealize(key); + } +} + +static void u2f_key_realize(USBDevice *dev, Error **errp) +{ + U2FKeyState *key = U2F_KEY(dev); + U2FKeyClass *kc = U2F_KEY_GET_CLASS(key); + Error *local_err = NULL; + + usb_desc_create_serial(dev); + usb_desc_init(dev); + u2f_key_reset(key); + + if (kc->realize != NULL) { + kc->realize(key, &local_err); + if (local_err != NULL) { + error_propagate(errp, local_err); + return; + } + } + key->ep = usb_ep_get(dev, USB_TOKEN_IN, 1); +} + +const VMStateDescription vmstate_u2f_key = { + .name = "u2f-key", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(dev, U2FKeyState), + VMSTATE_UINT8(idle, U2FKeyState), + VMSTATE_UINT8_2DARRAY(pending_in, U2FKeyState, + U2FHID_PENDING_IN_NUM, U2FHID_PACKET_SIZE), + VMSTATE_UINT8(pending_in_start, U2FKeyState), + VMSTATE_UINT8(pending_in_end, U2FKeyState), + VMSTATE_UINT8(pending_in_num, U2FKeyState), + VMSTATE_END_OF_LIST() + } +}; + +static void u2f_key_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "QEMU U2F USB key"; + uc->usb_desc = &desc_u2f_key; + uc->handle_reset = u2f_key_handle_reset; + uc->handle_control = u2f_key_handle_control; + uc->handle_data = u2f_key_handle_data; + uc->handle_attach = usb_desc_attach; + uc->realize = u2f_key_realize; + uc->unrealize = u2f_key_unrealize; + dc->desc = "QEMU U2F key"; + dc->vmsd = &vmstate_u2f_key; +} + +static const TypeInfo u2f_key_info = { + .name = TYPE_U2F_KEY, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(U2FKeyState), + .abstract = true, + .class_size = sizeof(U2FKeyClass), + .class_init = u2f_key_class_init, +}; + +static void u2f_key_register_types(void) +{ + type_register_static(&u2f_key_info); +} + +type_init(u2f_key_register_types) diff --git a/hw/usb/u2f.h b/hw/usb/u2f.h new file mode 100644 index 000000000..db30f3586 --- /dev/null +++ b/hw/usb/u2f.h @@ -0,0 +1,92 @@ +/* + * U2F USB device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * 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. + */ + +#ifndef U2F_H +#define U2F_H + +#include "hw/qdev-core.h" + +#define U2FHID_PACKET_SIZE 64 +#define U2FHID_PENDING_IN_NUM 32 + +typedef struct U2FKeyState U2FKeyState; +typedef struct U2FKeyInfo U2FKeyInfo; + +#define TYPE_U2F_KEY "u2f-key" +#define U2F_KEY(obj) \ + OBJECT_CHECK(U2FKeyState, (obj), TYPE_U2F_KEY) +#define U2F_KEY_CLASS(klass) \ + OBJECT_CLASS_CHECK(U2FKeyClass, (klass), TYPE_U2F_KEY) +#define U2F_KEY_GET_CLASS(obj) \ + OBJECT_GET_CLASS(U2FKeyClass, (obj), TYPE_U2F_KEY) + +/* + * Callbacks to be used by the U2F key base device (i.e. hw/u2f.c) + * to interact with its variants (i.e. hw/u2f-*.c) + */ +typedef struct U2FKeyClass { + /*< private >*/ + USBDeviceClass parent_class; + + /*< public >*/ + void (*recv_from_guest)(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]); + void (*realize)(U2FKeyState *key, Error **errp); + void (*unrealize)(U2FKeyState *key); +} U2FKeyClass; + +/* + * State of the U2F key base device (i.e. hw/u2f.c) + */ +typedef struct U2FKeyState { + USBDevice dev; + USBEndpoint *ep; + uint8_t idle; + + /* Pending packets to be send to the guest */ + uint8_t pending_in[U2FHID_PENDING_IN_NUM][U2FHID_PACKET_SIZE]; + uint8_t pending_in_start; + uint8_t pending_in_end; + uint8_t pending_in_num; +} U2FKeyState; + +/* + * API to be used by the U2F key device variants (i.e. hw/u2f-*.c) + * to interact with the the U2F key base device (i.e. hw/u2f.c) + */ +void u2f_send_to_guest(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]); + +extern const VMStateDescription vmstate_u2f_key; + +#define VMSTATE_U2F_KEY(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(U2FKeyState), \ + .vmsd = &vmstate_u2f_key, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, U2FKeyState), \ +} + +#endif /* U2F_H */ diff --git a/hw/usb/vt82c686-uhci-pci.c b/hw/usb/vt82c686-uhci-pci.c new file mode 100644 index 000000000..0bf2b72ff --- /dev/null +++ b/hw/usb/vt82c686-uhci-pci.c @@ -0,0 +1,58 @@ +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/isa/vt82c686.h" +#include "hcd-uhci.h" + +static void uhci_isa_set_irq(void *opaque, int irq_num, int level) +{ + UHCIState *s = opaque; + uint8_t irq = pci_get_byte(s->dev.config + PCI_INTERRUPT_LINE); + if (irq > 0 && irq < 15) { + via_isa_set_irq(pci_get_function_0(&s->dev), irq, level); + } +} + +static void usb_uhci_vt82c686b_realize(PCIDevice *dev, Error **errp) +{ + UHCIState *s = UHCI(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); + + usb_uhci_common_realize(dev, errp); + object_unref(s->irq); + s->irq = qemu_allocate_irq(uhci_isa_set_irq, s, 0); +} + +static UHCIInfo uhci_info[] = { + { + .name = "vt82c686b-usb-uhci", + .vendor_id = PCI_VENDOR_ID_VIA, + .device_id = PCI_DEVICE_ID_VIA_UHCI, + .revision = 0x01, + .irq_pin = 3, + .realize = usb_uhci_vt82c686b_realize, + .unplug = true, + /* Reason: only works as USB function of VT82xx superio chips */ + .notuser = true, + } +}; + +static const TypeInfo vt82c686b_usb_uhci_type_info = { + .parent = TYPE_UHCI, + .name = "vt82c686b-usb-uhci", + .class_init = uhci_data_class_init, + .class_data = uhci_info, +}; + +static void vt82c686b_usb_uhci_register_types(void) +{ + type_register_static(&vt82c686b_usb_uhci_type_info); +} + +type_init(vt82c686b_usb_uhci_register_types) diff --git a/hw/usb/xen-usb.c b/hw/usb/xen-usb.c new file mode 100644 index 000000000..0f7369e7e --- /dev/null +++ b/hw/usb/xen-usb.c @@ -0,0 +1,1097 @@ +/* + * xen paravirt usb device backend + * + * (c) Juergen Gross <jgross@suse.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; under version 2 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/>. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include <libusb.h> +#include <sys/user.h> + +#include "qemu/config-file.h" +#include "qemu/main-loop.h" +#include "qemu/option.h" +#include "hw/usb.h" +#include "hw/xen/xen-legacy-backend.h" +#include "monitor/qdev.h" +#include "qapi/error.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qstring.h" + +#include "hw/xen/interface/io/usbif.h" + +/* + * Check for required support of usbif.h: USBIF_SHORT_NOT_OK was the last + * macro added we rely on. + */ +#ifdef USBIF_SHORT_NOT_OK + +#define TR(xendev, lvl, fmt, args...) \ + { \ + struct timeval tv; \ + \ + gettimeofday(&tv, NULL); \ + xen_pv_printf(xendev, lvl, "%8ld.%06ld xen-usb(%s):" fmt, \ + tv.tv_sec, tv.tv_usec, __func__, ##args); \ + } +#define TR_BUS(xendev, fmt, args...) TR(xendev, 2, fmt, ##args) +#define TR_REQ(xendev, fmt, args...) TR(xendev, 3, fmt, ##args) + +#define USBBACK_MAXPORTS USBIF_PIPE_PORT_MASK +#define USB_DEV_ADDR_SIZE (USBIF_PIPE_DEV_MASK + 1) + +/* USB wire protocol: structure describing control request parameter. */ +struct usbif_ctrlrequest { + uint8_t bRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +}; + +struct usbback_info; +struct usbback_req; + +struct usbback_stub { + USBDevice *dev; + USBPort port; + unsigned int speed; + bool attached; + QTAILQ_HEAD(, usbback_req) submit_q; +}; + +struct usbback_req { + struct usbback_info *usbif; + struct usbback_stub *stub; + struct usbif_urb_request req; + USBPacket packet; + + unsigned int nr_buffer_segs; /* # of transfer_buffer segments */ + unsigned int nr_extra_segs; /* # of iso_frame_desc segments */ + + QTAILQ_ENTRY(usbback_req) q; + + void *buffer; + void *isoc_buffer; + struct libusb_transfer *xfer; + + bool cancelled; +}; + +struct usbback_hotplug { + QSIMPLEQ_ENTRY(usbback_hotplug) q; + unsigned port; +}; + +struct usbback_info { + struct XenLegacyDevice xendev; /* must be first */ + USBBus bus; + void *urb_sring; + void *conn_sring; + struct usbif_urb_back_ring urb_ring; + struct usbif_conn_back_ring conn_ring; + int num_ports; + int usb_ver; + bool ring_error; + QTAILQ_HEAD(, usbback_req) req_free_q; + QSIMPLEQ_HEAD(, usbback_hotplug) hotplug_q; + struct usbback_stub ports[USBBACK_MAXPORTS]; + struct usbback_stub *addr_table[USB_DEV_ADDR_SIZE]; + QEMUBH *bh; +}; + +static struct usbback_req *usbback_get_req(struct usbback_info *usbif) +{ + struct usbback_req *usbback_req; + + if (QTAILQ_EMPTY(&usbif->req_free_q)) { + usbback_req = g_new0(struct usbback_req, 1); + } else { + usbback_req = QTAILQ_FIRST(&usbif->req_free_q); + QTAILQ_REMOVE(&usbif->req_free_q, usbback_req, q); + } + return usbback_req; +} + +static void usbback_put_req(struct usbback_req *usbback_req) +{ + struct usbback_info *usbif; + + usbif = usbback_req->usbif; + memset(usbback_req, 0, sizeof(*usbback_req)); + QTAILQ_INSERT_HEAD(&usbif->req_free_q, usbback_req, q); +} + +static int usbback_gnttab_map(struct usbback_req *usbback_req) +{ + unsigned int nr_segs, i, prot; + uint32_t ref[USBIF_MAX_SEGMENTS_PER_REQUEST]; + struct usbback_info *usbif = usbback_req->usbif; + struct XenLegacyDevice *xendev = &usbif->xendev; + struct usbif_request_segment *seg; + void *addr; + + nr_segs = usbback_req->nr_buffer_segs + usbback_req->nr_extra_segs; + if (!nr_segs) { + return 0; + } + + if (nr_segs > USBIF_MAX_SEGMENTS_PER_REQUEST) { + xen_pv_printf(xendev, 0, "bad number of segments in request (%d)\n", + nr_segs); + return -EINVAL; + } + + for (i = 0; i < nr_segs; i++) { + if ((unsigned)usbback_req->req.seg[i].offset + + (unsigned)usbback_req->req.seg[i].length > XC_PAGE_SIZE) { + xen_pv_printf(xendev, 0, "segment crosses page boundary\n"); + return -EINVAL; + } + } + + if (usbback_req->nr_buffer_segs) { + prot = PROT_READ; + if (usbif_pipein(usbback_req->req.pipe)) { + prot |= PROT_WRITE; + } + for (i = 0; i < usbback_req->nr_buffer_segs; i++) { + ref[i] = usbback_req->req.seg[i].gref; + } + usbback_req->buffer = + xen_be_map_grant_refs(xendev, ref, usbback_req->nr_buffer_segs, + prot); + + if (!usbback_req->buffer) { + return -ENOMEM; + } + + for (i = 0; i < usbback_req->nr_buffer_segs; i++) { + seg = usbback_req->req.seg + i; + addr = usbback_req->buffer + i * XC_PAGE_SIZE + seg->offset; + qemu_iovec_add(&usbback_req->packet.iov, addr, seg->length); + } + } + + if (!usbif_pipeisoc(usbback_req->req.pipe)) { + return 0; + } + + /* + * Right now isoc requests are not supported. + * Prepare supporting those by doing the work needed on the guest + * interface side. + */ + + if (!usbback_req->nr_extra_segs) { + xen_pv_printf(xendev, 0, "iso request without descriptor segments\n"); + return -EINVAL; + } + + prot = PROT_READ | PROT_WRITE; + for (i = 0; i < usbback_req->nr_extra_segs; i++) { + ref[i] = usbback_req->req.seg[i + usbback_req->req.nr_buffer_segs].gref; + } + usbback_req->isoc_buffer = + xen_be_map_grant_refs(xendev, ref, usbback_req->nr_extra_segs, + prot); + + if (!usbback_req->isoc_buffer) { + return -ENOMEM; + } + + return 0; +} + +static int usbback_init_packet(struct usbback_req *usbback_req) +{ + struct XenLegacyDevice *xendev = &usbback_req->usbif->xendev; + USBPacket *packet = &usbback_req->packet; + USBDevice *dev = usbback_req->stub->dev; + USBEndpoint *ep; + unsigned int pid, ep_nr; + bool sok; + int ret = 0; + + qemu_iovec_init(&packet->iov, USBIF_MAX_SEGMENTS_PER_REQUEST); + pid = usbif_pipein(usbback_req->req.pipe) ? USB_TOKEN_IN : USB_TOKEN_OUT; + ep_nr = usbif_pipeendpoint(usbback_req->req.pipe); + sok = !!(usbback_req->req.transfer_flags & USBIF_SHORT_NOT_OK); + if (usbif_pipectrl(usbback_req->req.pipe)) { + ep_nr = 0; + sok = false; + } + ep = usb_ep_get(dev, pid, ep_nr); + usb_packet_setup(packet, pid, ep, 0, 1, sok, true); + + switch (usbif_pipetype(usbback_req->req.pipe)) { + case USBIF_PIPE_TYPE_ISOC: + TR_REQ(xendev, "iso transfer %s: buflen: %x, %d frames\n", + (pid == USB_TOKEN_IN) ? "in" : "out", + usbback_req->req.buffer_length, + usbback_req->req.u.isoc.nr_frame_desc_segs); + ret = -EINVAL; /* isoc not implemented yet */ + break; + + case USBIF_PIPE_TYPE_INT: + TR_REQ(xendev, "int transfer %s: buflen: %x\n", + (pid == USB_TOKEN_IN) ? "in" : "out", + usbback_req->req.buffer_length); + break; + + case USBIF_PIPE_TYPE_CTRL: + packet->parameter = *(uint64_t *)usbback_req->req.u.ctrl; + TR_REQ(xendev, "ctrl parameter: %"PRIx64", buflen: %x\n", + packet->parameter, + usbback_req->req.buffer_length); + break; + + case USBIF_PIPE_TYPE_BULK: + TR_REQ(xendev, "bulk transfer %s: buflen: %x\n", + (pid == USB_TOKEN_IN) ? "in" : "out", + usbback_req->req.buffer_length); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void usbback_do_response(struct usbback_req *usbback_req, int32_t status, + int32_t actual_length, int32_t error_count) +{ + struct usbback_info *usbif; + struct usbif_urb_response *res; + struct XenLegacyDevice *xendev; + unsigned int notify; + + usbif = usbback_req->usbif; + xendev = &usbif->xendev; + + TR_REQ(xendev, "id %d, status %d, length %d, errcnt %d\n", + usbback_req->req.id, status, actual_length, error_count); + + if (usbback_req->packet.iov.iov) { + qemu_iovec_destroy(&usbback_req->packet.iov); + } + + if (usbback_req->buffer) { + xen_be_unmap_grant_refs(xendev, usbback_req->buffer, + usbback_req->nr_buffer_segs); + usbback_req->buffer = NULL; + } + + if (usbback_req->isoc_buffer) { + xen_be_unmap_grant_refs(xendev, usbback_req->isoc_buffer, + usbback_req->nr_extra_segs); + usbback_req->isoc_buffer = NULL; + } + + if (usbif->urb_sring) { + res = RING_GET_RESPONSE(&usbif->urb_ring, usbif->urb_ring.rsp_prod_pvt); + res->id = usbback_req->req.id; + res->status = status; + res->actual_length = actual_length; + res->error_count = error_count; + res->start_frame = 0; + usbif->urb_ring.rsp_prod_pvt++; + RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&usbif->urb_ring, notify); + + if (notify) { + xen_pv_send_notify(xendev); + } + } + + if (!usbback_req->cancelled) + usbback_put_req(usbback_req); +} + +static void usbback_do_response_ret(struct usbback_req *usbback_req, + int32_t status) +{ + usbback_do_response(usbback_req, status, 0, 0); +} + +static int32_t usbback_xlat_status(int status) +{ + switch (status) { + case USB_RET_SUCCESS: + return 0; + case USB_RET_NODEV: + return -ENODEV; + case USB_RET_STALL: + return -EPIPE; + case USB_RET_BABBLE: + return -EOVERFLOW; + case USB_RET_IOERROR: + return -EPROTO; + } + + return -ESHUTDOWN; +} + +static void usbback_packet_complete(struct usbback_req *usbback_req) +{ + USBPacket *packet = &usbback_req->packet; + int32_t status; + + QTAILQ_REMOVE(&usbback_req->stub->submit_q, usbback_req, q); + + status = usbback_xlat_status(packet->status); + usbback_do_response(usbback_req, status, packet->actual_length, 0); +} + +static void usbback_set_address(struct usbback_info *usbif, + struct usbback_stub *stub, + unsigned int cur_addr, unsigned int new_addr) +{ + if (cur_addr) { + usbif->addr_table[cur_addr] = NULL; + } + if (new_addr) { + usbif->addr_table[new_addr] = stub; + } +} + +static void usbback_cancel_req(struct usbback_req *usbback_req) +{ + if (usb_packet_is_inflight(&usbback_req->packet)) { + usb_cancel_packet(&usbback_req->packet); + QTAILQ_REMOVE(&usbback_req->stub->submit_q, usbback_req, q); + usbback_req->cancelled = true; + usbback_do_response_ret(usbback_req, -EPROTO); + } +} + +static void usbback_process_unlink_req(struct usbback_req *usbback_req) +{ + struct usbback_info *usbif; + struct usbback_req *unlink_req; + unsigned int id, devnum; + int ret; + + usbif = usbback_req->usbif; + ret = 0; + id = usbback_req->req.u.unlink.unlink_id; + TR_REQ(&usbif->xendev, "unlink id %d\n", id); + devnum = usbif_pipedevice(usbback_req->req.pipe); + if (unlikely(devnum == 0)) { + usbback_req->stub = usbif->ports + + usbif_pipeportnum(usbback_req->req.pipe) - 1; + if (unlikely(!usbback_req->stub)) { + ret = -ENODEV; + goto fail_response; + } + } else { + if (unlikely(!usbif->addr_table[devnum])) { + ret = -ENODEV; + goto fail_response; + } + usbback_req->stub = usbif->addr_table[devnum]; + } + + QTAILQ_FOREACH(unlink_req, &usbback_req->stub->submit_q, q) { + if (unlink_req->req.id == id) { + usbback_cancel_req(unlink_req); + break; + } + } + +fail_response: + usbback_do_response_ret(usbback_req, ret); +} + +/* + * Checks whether a request can be handled at once or should be forwarded + * to the usb framework. + * Return value is: + * 0 in case of usb framework is needed + * 1 in case of local handling (no error) + * The request response has been queued already if return value not 0. + */ +static int usbback_check_and_submit(struct usbback_req *usbback_req) +{ + struct usbback_info *usbif; + unsigned int devnum; + struct usbback_stub *stub; + struct usbif_ctrlrequest *ctrl; + int ret; + uint16_t wValue; + + usbif = usbback_req->usbif; + stub = NULL; + devnum = usbif_pipedevice(usbback_req->req.pipe); + ctrl = (struct usbif_ctrlrequest *)usbback_req->req.u.ctrl; + wValue = le16_to_cpu(ctrl->wValue); + + /* + * When the device is first connected or resetted, USB device has no + * address. In this initial state, following requests are sent to device + * address (#0), + * + * 1. GET_DESCRIPTOR (with Descriptor Type is "DEVICE") is sent, + * and OS knows what device is connected to. + * + * 2. SET_ADDRESS is sent, and then device has its address. + * + * In the next step, SET_CONFIGURATION is sent to addressed device, and + * then the device is finally ready to use. + */ + if (unlikely(devnum == 0)) { + stub = usbif->ports + usbif_pipeportnum(usbback_req->req.pipe) - 1; + if (!stub->dev || !stub->attached) { + ret = -ENODEV; + goto do_response; + } + + switch (ctrl->bRequest) { + case USB_REQ_GET_DESCRIPTOR: + /* + * GET_DESCRIPTOR request to device #0. + * through normal transfer. + */ + TR_REQ(&usbif->xendev, "devnum 0 GET_DESCRIPTOR\n"); + usbback_req->stub = stub; + return 0; + case USB_REQ_SET_ADDRESS: + /* + * SET_ADDRESS request to device #0. + * add attached device to addr_table. + */ + TR_REQ(&usbif->xendev, "devnum 0 SET_ADDRESS\n"); + usbback_set_address(usbif, stub, 0, wValue); + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + goto do_response; + } + + if (unlikely(!usbif->addr_table[devnum])) { + ret = -ENODEV; + goto do_response; + } + usbback_req->stub = usbif->addr_table[devnum]; + + /* + * Check special request + */ + if (ctrl->bRequest != USB_REQ_SET_ADDRESS) { + return 0; + } + + /* + * SET_ADDRESS request to addressed device. + * change addr or remove from addr_table. + */ + usbback_set_address(usbif, usbback_req->stub, devnum, wValue); + ret = 0; + +do_response: + usbback_do_response_ret(usbback_req, ret); + return 1; +} + +static void usbback_dispatch(struct usbback_req *usbback_req) +{ + int ret; + unsigned int devnum; + struct usbback_info *usbif; + + usbif = usbback_req->usbif; + + TR_REQ(&usbif->xendev, "start req_id %d pipe %08x\n", usbback_req->req.id, + usbback_req->req.pipe); + + /* unlink request */ + if (unlikely(usbif_pipeunlink(usbback_req->req.pipe))) { + usbback_process_unlink_req(usbback_req); + return; + } + + if (usbif_pipectrl(usbback_req->req.pipe)) { + if (usbback_check_and_submit(usbback_req)) { + return; + } + } else { + devnum = usbif_pipedevice(usbback_req->req.pipe); + usbback_req->stub = usbif->addr_table[devnum]; + + if (!usbback_req->stub || !usbback_req->stub->attached) { + ret = -ENODEV; + goto fail_response; + } + } + + QTAILQ_INSERT_TAIL(&usbback_req->stub->submit_q, usbback_req, q); + + usbback_req->nr_buffer_segs = usbback_req->req.nr_buffer_segs; + usbback_req->nr_extra_segs = usbif_pipeisoc(usbback_req->req.pipe) ? + usbback_req->req.u.isoc.nr_frame_desc_segs : 0; + + ret = usbback_init_packet(usbback_req); + if (ret) { + xen_pv_printf(&usbif->xendev, 0, "invalid request\n"); + ret = -ESHUTDOWN; + goto fail_free_urb; + } + + ret = usbback_gnttab_map(usbback_req); + if (ret) { + xen_pv_printf(&usbif->xendev, 0, "invalid buffer, ret=%d\n", ret); + ret = -ESHUTDOWN; + goto fail_free_urb; + } + + usb_handle_packet(usbback_req->stub->dev, &usbback_req->packet); + if (usbback_req->packet.status != USB_RET_ASYNC) { + usbback_packet_complete(usbback_req); + } + return; + +fail_free_urb: + QTAILQ_REMOVE(&usbback_req->stub->submit_q, usbback_req, q); + +fail_response: + usbback_do_response_ret(usbback_req, ret); +} + +static void usbback_hotplug_notify(struct usbback_info *usbif) +{ + struct usbif_conn_back_ring *ring = &usbif->conn_ring; + struct usbif_conn_request req; + struct usbif_conn_response *res; + struct usbback_hotplug *usb_hp; + unsigned int notify; + + if (!usbif->conn_sring) { + return; + } + + /* Check for full ring. */ + if ((RING_SIZE(ring) - ring->rsp_prod_pvt - ring->req_cons) == 0) { + xen_pv_send_notify(&usbif->xendev); + return; + } + + usb_hp = QSIMPLEQ_FIRST(&usbif->hotplug_q); + QSIMPLEQ_REMOVE_HEAD(&usbif->hotplug_q, q); + + RING_COPY_REQUEST(ring, ring->req_cons, &req); + ring->req_cons++; + ring->sring->req_event = ring->req_cons + 1; + + res = RING_GET_RESPONSE(ring, ring->rsp_prod_pvt); + res->id = req.id; + res->portnum = usb_hp->port; + res->speed = usbif->ports[usb_hp->port - 1].speed; + ring->rsp_prod_pvt++; + RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(ring, notify); + + if (notify) { + xen_pv_send_notify(&usbif->xendev); + } + + TR_BUS(&usbif->xendev, "hotplug port %d speed %d\n", usb_hp->port, + res->speed); + + g_free(usb_hp); + + if (!QSIMPLEQ_EMPTY(&usbif->hotplug_q)) { + qemu_bh_schedule(usbif->bh); + } +} + +static void usbback_bh(void *opaque) +{ + struct usbback_info *usbif; + struct usbif_urb_back_ring *urb_ring; + struct usbback_req *usbback_req; + RING_IDX rc, rp; + unsigned int more_to_do; + + usbif = opaque; + if (usbif->ring_error) { + return; + } + + if (!QSIMPLEQ_EMPTY(&usbif->hotplug_q)) { + usbback_hotplug_notify(usbif); + } + + urb_ring = &usbif->urb_ring; + rc = urb_ring->req_cons; + rp = urb_ring->sring->req_prod; + xen_rmb(); /* Ensure we see queued requests up to 'rp'. */ + + if (RING_REQUEST_PROD_OVERFLOW(urb_ring, rp)) { + rc = urb_ring->rsp_prod_pvt; + xen_pv_printf(&usbif->xendev, 0, "domU provided bogus ring requests " + "(%#x - %#x = %u). Halting ring processing.\n", + rp, rc, rp - rc); + usbif->ring_error = true; + return; + } + + while (rc != rp) { + if (RING_REQUEST_CONS_OVERFLOW(urb_ring, rc)) { + break; + } + usbback_req = usbback_get_req(usbif); + + RING_COPY_REQUEST(urb_ring, rc, &usbback_req->req); + usbback_req->usbif = usbif; + + usbback_dispatch(usbback_req); + + urb_ring->req_cons = ++rc; + } + + RING_FINAL_CHECK_FOR_REQUESTS(urb_ring, more_to_do); + if (more_to_do) { + qemu_bh_schedule(usbif->bh); + } +} + +static void usbback_hotplug_enq(struct usbback_info *usbif, unsigned port) +{ + struct usbback_hotplug *usb_hp; + + usb_hp = g_new0(struct usbback_hotplug, 1); + usb_hp->port = port; + QSIMPLEQ_INSERT_TAIL(&usbif->hotplug_q, usb_hp, q); + usbback_hotplug_notify(usbif); +} + +static void usbback_portid_drain(struct usbback_info *usbif, unsigned port) +{ + struct usbback_req *req, *tmp; + bool sched = false; + + QTAILQ_FOREACH_SAFE(req, &usbif->ports[port - 1].submit_q, q, tmp) { + usbback_cancel_req(req); + sched = true; + } + + if (sched) { + qemu_bh_schedule(usbif->bh); + } +} + +static void usbback_portid_detach(struct usbback_info *usbif, unsigned port) +{ + if (!usbif->ports[port - 1].attached) { + return; + } + + usbif->ports[port - 1].speed = USBIF_SPEED_NONE; + usbif->ports[port - 1].attached = false; + usbback_portid_drain(usbif, port); + usbback_hotplug_enq(usbif, port); +} + +static void usbback_portid_remove(struct usbback_info *usbif, unsigned port) +{ + if (!usbif->ports[port - 1].dev) { + return; + } + + object_unparent(OBJECT(usbif->ports[port - 1].dev)); + usbif->ports[port - 1].dev = NULL; + usbback_portid_detach(usbif, port); + + TR_BUS(&usbif->xendev, "port %d removed\n", port); +} + +static void usbback_portid_add(struct usbback_info *usbif, unsigned port, + char *busid) +{ + unsigned speed; + char *portname; + Error *local_err = NULL; + QDict *qdict; + QemuOpts *opts; + char *tmp; + + if (usbif->ports[port - 1].dev) { + return; + } + + portname = strchr(busid, '-'); + if (!portname) { + xen_pv_printf(&usbif->xendev, 0, "device %s illegal specification\n", + busid); + return; + } + portname++; + + qdict = qdict_new(); + qdict_put_str(qdict, "driver", "usb-host"); + tmp = g_strdup_printf("%s.0", usbif->xendev.qdev.id); + qdict_put_str(qdict, "bus", tmp); + g_free(tmp); + tmp = g_strdup_printf("%s-%u", usbif->xendev.qdev.id, port); + qdict_put_str(qdict, "id", tmp); + g_free(tmp); + qdict_put_int(qdict, "port", port); + qdict_put_int(qdict, "hostbus", atoi(busid)); + qdict_put_str(qdict, "hostport", portname); + opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict, + &error_abort); + usbif->ports[port - 1].dev = USB_DEVICE(qdev_device_add(opts, &local_err)); + if (!usbif->ports[port - 1].dev) { + qobject_unref(qdict); + xen_pv_printf(&usbif->xendev, 0, + "device %s could not be opened: %s\n", + busid, error_get_pretty(local_err)); + error_free(local_err); + return; + } + qobject_unref(qdict); + speed = usbif->ports[port - 1].dev->speed; + switch (speed) { + case USB_SPEED_LOW: + speed = USBIF_SPEED_LOW; + break; + case USB_SPEED_FULL: + speed = USBIF_SPEED_FULL; + break; + case USB_SPEED_HIGH: + speed = (usbif->usb_ver < USB_VER_USB20) ? + USBIF_SPEED_NONE : USBIF_SPEED_HIGH; + break; + default: + speed = USBIF_SPEED_NONE; + break; + } + if (speed == USBIF_SPEED_NONE) { + xen_pv_printf(&usbif->xendev, 0, "device %s wrong speed\n", busid); + object_unparent(OBJECT(usbif->ports[port - 1].dev)); + usbif->ports[port - 1].dev = NULL; + return; + } + usb_device_reset(usbif->ports[port - 1].dev); + usbif->ports[port - 1].speed = speed; + usbif->ports[port - 1].attached = true; + QTAILQ_INIT(&usbif->ports[port - 1].submit_q); + usbback_hotplug_enq(usbif, port); + + TR_BUS(&usbif->xendev, "port %d attached\n", port); +} + +static void usbback_process_port(struct usbback_info *usbif, unsigned port) +{ + char node[8]; + char *busid; + + snprintf(node, sizeof(node), "port/%d", port); + busid = xenstore_read_be_str(&usbif->xendev, node); + if (busid == NULL) { + xen_pv_printf(&usbif->xendev, 0, "xenstore_read %s failed\n", node); + return; + } + + /* Remove portid, if the port is not connected. */ + if (strlen(busid) == 0) { + usbback_portid_remove(usbif, port); + } else { + usbback_portid_add(usbif, port, busid); + } + + g_free(busid); +} + +static void usbback_disconnect(struct XenLegacyDevice *xendev) +{ + struct usbback_info *usbif; + unsigned int i; + + TR_BUS(xendev, "start\n"); + + usbif = container_of(xendev, struct usbback_info, xendev); + + xen_pv_unbind_evtchn(xendev); + + if (usbif->urb_sring) { + xen_be_unmap_grant_ref(xendev, usbif->urb_sring); + usbif->urb_sring = NULL; + } + if (usbif->conn_sring) { + xen_be_unmap_grant_ref(xendev, usbif->conn_sring); + usbif->conn_sring = NULL; + } + + for (i = 0; i < usbif->num_ports; i++) { + if (usbif->ports[i].dev) { + usbback_portid_drain(usbif, i + 1); + } + } + + TR_BUS(xendev, "finished\n"); +} + +static int usbback_connect(struct XenLegacyDevice *xendev) +{ + struct usbback_info *usbif; + struct usbif_urb_sring *urb_sring; + struct usbif_conn_sring *conn_sring; + int urb_ring_ref; + int conn_ring_ref; + unsigned int i, max_grants; + + TR_BUS(xendev, "start\n"); + + /* max_grants: for each request and for the rings (request and connect). */ + max_grants = USBIF_MAX_SEGMENTS_PER_REQUEST * USB_URB_RING_SIZE + 2; + xen_be_set_max_grant_refs(xendev, max_grants); + + usbif = container_of(xendev, struct usbback_info, xendev); + + if (xenstore_read_fe_int(xendev, "urb-ring-ref", &urb_ring_ref)) { + xen_pv_printf(xendev, 0, "error reading urb-ring-ref\n"); + return -1; + } + if (xenstore_read_fe_int(xendev, "conn-ring-ref", &conn_ring_ref)) { + xen_pv_printf(xendev, 0, "error reading conn-ring-ref\n"); + return -1; + } + if (xenstore_read_fe_int(xendev, "event-channel", &xendev->remote_port)) { + xen_pv_printf(xendev, 0, "error reading event-channel\n"); + return -1; + } + + usbif->urb_sring = xen_be_map_grant_ref(xendev, urb_ring_ref, + PROT_READ | PROT_WRITE); + usbif->conn_sring = xen_be_map_grant_ref(xendev, conn_ring_ref, + PROT_READ | PROT_WRITE); + if (!usbif->urb_sring || !usbif->conn_sring) { + xen_pv_printf(xendev, 0, "error mapping rings\n"); + usbback_disconnect(xendev); + return -1; + } + + urb_sring = usbif->urb_sring; + conn_sring = usbif->conn_sring; + BACK_RING_INIT(&usbif->urb_ring, urb_sring, XC_PAGE_SIZE); + BACK_RING_INIT(&usbif->conn_ring, conn_sring, XC_PAGE_SIZE); + + xen_be_bind_evtchn(xendev); + + xen_pv_printf(xendev, 1, "urb-ring-ref %d, conn-ring-ref %d, " + "remote port %d, local port %d\n", urb_ring_ref, + conn_ring_ref, xendev->remote_port, xendev->local_port); + + for (i = 1; i <= usbif->num_ports; i++) { + if (usbif->ports[i - 1].dev) { + usbback_hotplug_enq(usbif, i); + } + } + + return 0; +} + +static void usbback_backend_changed(struct XenLegacyDevice *xendev, + const char *node) +{ + struct usbback_info *usbif; + unsigned int i; + + TR_BUS(xendev, "path %s\n", node); + + usbif = container_of(xendev, struct usbback_info, xendev); + for (i = 1; i <= usbif->num_ports; i++) { + usbback_process_port(usbif, i); + } +} + +static int usbback_init(struct XenLegacyDevice *xendev) +{ + struct usbback_info *usbif; + + TR_BUS(xendev, "start\n"); + + usbif = container_of(xendev, struct usbback_info, xendev); + + if (xenstore_read_be_int(xendev, "num-ports", &usbif->num_ports) || + usbif->num_ports < 1 || usbif->num_ports > USBBACK_MAXPORTS) { + xen_pv_printf(xendev, 0, "num-ports not readable or out of bounds\n"); + return -1; + } + if (xenstore_read_be_int(xendev, "usb-ver", &usbif->usb_ver) || + (usbif->usb_ver != USB_VER_USB11 && usbif->usb_ver != USB_VER_USB20)) { + xen_pv_printf(xendev, 0, "usb-ver not readable or out of bounds\n"); + return -1; + } + + usbback_backend_changed(xendev, "port"); + + TR_BUS(xendev, "finished\n"); + + return 0; +} + +static void xen_bus_attach(USBPort *port) +{ + struct usbback_info *usbif; + + usbif = port->opaque; + TR_BUS(&usbif->xendev, "\n"); + usbif->ports[port->index].attached = true; + usbback_hotplug_enq(usbif, port->index + 1); +} + +static void xen_bus_detach(USBPort *port) +{ + struct usbback_info *usbif; + + usbif = port->opaque; + TR_BUS(&usbif->xendev, "\n"); + usbback_portid_detach(usbif, port->index + 1); +} + +static void xen_bus_child_detach(USBPort *port, USBDevice *child) +{ + struct usbback_info *usbif; + + usbif = port->opaque; + TR_BUS(&usbif->xendev, "\n"); +} + +static void xen_bus_complete(USBPort *port, USBPacket *packet) +{ + struct usbback_req *usbback_req; + struct usbback_info *usbif; + + usbback_req = container_of(packet, struct usbback_req, packet); + if (usbback_req->cancelled) { + g_free(usbback_req); + return; + } + + usbif = usbback_req->usbif; + TR_REQ(&usbif->xendev, "\n"); + usbback_packet_complete(usbback_req); +} + +static USBPortOps xen_usb_port_ops = { + .attach = xen_bus_attach, + .detach = xen_bus_detach, + .child_detach = xen_bus_child_detach, + .complete = xen_bus_complete, +}; + +static USBBusOps xen_usb_bus_ops = { +}; + +static void usbback_alloc(struct XenLegacyDevice *xendev) +{ + struct usbback_info *usbif; + USBPort *p; + unsigned int i; + + usbif = container_of(xendev, struct usbback_info, xendev); + + usb_bus_new(&usbif->bus, sizeof(usbif->bus), &xen_usb_bus_ops, + DEVICE(&xendev->qdev)); + for (i = 0; i < USBBACK_MAXPORTS; i++) { + p = &(usbif->ports[i].port); + usb_register_port(&usbif->bus, p, usbif, i, &xen_usb_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL | + USB_SPEED_MASK_HIGH); + } + + QTAILQ_INIT(&usbif->req_free_q); + QSIMPLEQ_INIT(&usbif->hotplug_q); + usbif->bh = qemu_bh_new(usbback_bh, usbif); +} + +static int usbback_free(struct XenLegacyDevice *xendev) +{ + struct usbback_info *usbif; + struct usbback_req *usbback_req; + struct usbback_hotplug *usb_hp; + unsigned int i; + + TR_BUS(xendev, "start\n"); + + usbback_disconnect(xendev); + usbif = container_of(xendev, struct usbback_info, xendev); + for (i = 1; i <= usbif->num_ports; i++) { + usbback_portid_remove(usbif, i); + } + + while (!QTAILQ_EMPTY(&usbif->req_free_q)) { + usbback_req = QTAILQ_FIRST(&usbif->req_free_q); + QTAILQ_REMOVE(&usbif->req_free_q, usbback_req, q); + g_free(usbback_req); + } + while (!QSIMPLEQ_EMPTY(&usbif->hotplug_q)) { + usb_hp = QSIMPLEQ_FIRST(&usbif->hotplug_q); + QSIMPLEQ_REMOVE_HEAD(&usbif->hotplug_q, q); + g_free(usb_hp); + } + + qemu_bh_delete(usbif->bh); + + for (i = 0; i < USBBACK_MAXPORTS; i++) { + usb_unregister_port(&usbif->bus, &(usbif->ports[i].port)); + } + + usb_bus_release(&usbif->bus); + + TR_BUS(xendev, "finished\n"); + + return 0; +} + +static void usbback_event(struct XenLegacyDevice *xendev) +{ + struct usbback_info *usbif; + + usbif = container_of(xendev, struct usbback_info, xendev); + qemu_bh_schedule(usbif->bh); +} + +struct XenDevOps xen_usb_ops = { + .size = sizeof(struct usbback_info), + .flags = DEVOPS_FLAG_NEED_GNTDEV, + .init = usbback_init, + .alloc = usbback_alloc, + .free = usbback_free, + .backend_changed = usbback_backend_changed, + .initialise = usbback_connect, + .disconnect = usbback_disconnect, + .event = usbback_event, +}; + +#else /* USBIF_SHORT_NOT_OK */ + +static int usbback_not_supported(void) +{ + return -EINVAL; +} + +struct XenDevOps xen_usb_ops = { + .backend_register = usbback_not_supported, +}; + +#endif diff --git a/hw/usb/xlnx-usb-subsystem.c b/hw/usb/xlnx-usb-subsystem.c new file mode 100644 index 000000000..d8deeb6ce --- /dev/null +++ b/hw/usb/xlnx-usb-subsystem.c @@ -0,0 +1,92 @@ +/* + * QEMU model of the Xilinx usb subsystem + * + * Copyright (c) 2020 Xilinx Inc. Sai Pavan Boddu <sai.pava.boddu@xilinx.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/osdep.h" +#include "hw/sysbus.h" +#include "hw/register.h" +#include "qemu/bitops.h" +#include "qom/object.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/usb/xlnx-usb-subsystem.h" + +static void versal_usb2_realize(DeviceState *dev, Error **errp) +{ + VersalUsb2 *s = VERSAL_USB2(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + Error *err = NULL; + + sysbus_realize(SYS_BUS_DEVICE(&s->dwc3), &err); + if (err) { + error_propagate(errp, err); + return; + } + sysbus_realize(SYS_BUS_DEVICE(&s->usb2Ctrl), &err); + if (err) { + error_propagate(errp, err); + return; + } + sysbus_init_mmio(sbd, &s->dwc3_mr); + sysbus_init_mmio(sbd, &s->usb2Ctrl_mr); + qdev_pass_gpios(DEVICE(&s->dwc3.sysbus_xhci), dev, SYSBUS_DEVICE_GPIO_IRQ); +} + +static void versal_usb2_init(Object *obj) +{ + VersalUsb2 *s = VERSAL_USB2(obj); + + object_initialize_child(obj, "versal.dwc3", &s->dwc3, + TYPE_USB_DWC3); + object_initialize_child(obj, "versal.usb2-ctrl", &s->usb2Ctrl, + TYPE_XILINX_VERSAL_USB2_CTRL_REGS); + memory_region_init_alias(&s->dwc3_mr, obj, "versal.dwc3_alias", + &s->dwc3.iomem, 0, DWC3_SIZE); + memory_region_init_alias(&s->usb2Ctrl_mr, obj, "versal.usb2Ctrl_alias", + &s->usb2Ctrl.iomem, 0, USB2_REGS_R_MAX * 4); + qdev_alias_all_properties(DEVICE(&s->dwc3), obj); + qdev_alias_all_properties(DEVICE(&s->dwc3.sysbus_xhci), obj); + object_property_add_alias(obj, "dma", OBJECT(&s->dwc3.sysbus_xhci), "dma"); +} + +static void versal_usb2_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = versal_usb2_realize; +} + +static const TypeInfo versal_usb2_info = { + .name = TYPE_XILINX_VERSAL_USB2, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(VersalUsb2), + .class_init = versal_usb2_class_init, + .instance_init = versal_usb2_init, +}; + +static void versal_usb_types(void) +{ + type_register_static(&versal_usb2_info); +} + +type_init(versal_usb_types) diff --git a/hw/usb/xlnx-versal-usb2-ctrl-regs.c b/hw/usb/xlnx-versal-usb2-ctrl-regs.c new file mode 100644 index 000000000..1c094aa1a --- /dev/null +++ b/hw/usb/xlnx-versal-usb2-ctrl-regs.c @@ -0,0 +1,228 @@ +/* + * QEMU model of the VersalUsb2CtrlRegs Register control/Status block for + * USB2.0 controller + * + * This module should control phy_reset, permanent device plugs, frame length + * time adjust & setting of coherency paths. None of which are emulated in + * present model. + * + * Copyright (c) 2020 Xilinx Inc. Vikram Garhwal <fnu.vikram@xilinx.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/osdep.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "hw/register.h" +#include "qemu/bitops.h" +#include "qom/object.h" +#include "migration/vmstate.h" +#include "hw/usb/xlnx-versal-usb2-ctrl-regs.h" + +#ifndef XILINX_VERSAL_USB2_CTRL_REGS_ERR_DEBUG +#define XILINX_VERSAL_USB2_CTRL_REGS_ERR_DEBUG 0 +#endif + +REG32(BUS_FILTER, 0x30) + FIELD(BUS_FILTER, BYPASS, 0, 4) +REG32(PORT, 0x34) + FIELD(PORT, HOST_SMI_BAR_WR, 4, 1) + FIELD(PORT, HOST_SMI_PCI_CMD_REG_WR, 3, 1) + FIELD(PORT, HOST_MSI_ENABLE, 2, 1) + FIELD(PORT, PWR_CTRL_PRSNT, 1, 1) + FIELD(PORT, HUB_PERM_ATTACH, 0, 1) +REG32(JITTER_ADJUST, 0x38) + FIELD(JITTER_ADJUST, FLADJ, 0, 6) +REG32(BIGENDIAN, 0x40) + FIELD(BIGENDIAN, ENDIAN_GS, 0, 1) +REG32(COHERENCY, 0x44) + FIELD(COHERENCY, USB_COHERENCY, 0, 1) +REG32(XHC_BME, 0x48) + FIELD(XHC_BME, XHC_BME, 0, 1) +REG32(REG_CTRL, 0x60) + FIELD(REG_CTRL, SLVERR_ENABLE, 0, 1) +REG32(IR_STATUS, 0x64) + FIELD(IR_STATUS, HOST_SYS_ERR, 1, 1) + FIELD(IR_STATUS, ADDR_DEC_ERR, 0, 1) +REG32(IR_MASK, 0x68) + FIELD(IR_MASK, HOST_SYS_ERR, 1, 1) + FIELD(IR_MASK, ADDR_DEC_ERR, 0, 1) +REG32(IR_ENABLE, 0x6c) + FIELD(IR_ENABLE, HOST_SYS_ERR, 1, 1) + FIELD(IR_ENABLE, ADDR_DEC_ERR, 0, 1) +REG32(IR_DISABLE, 0x70) + FIELD(IR_DISABLE, HOST_SYS_ERR, 1, 1) + FIELD(IR_DISABLE, ADDR_DEC_ERR, 0, 1) +REG32(USB3, 0x78) + +static void ir_update_irq(VersalUsb2CtrlRegs *s) +{ + bool pending = s->regs[R_IR_STATUS] & ~s->regs[R_IR_MASK]; + qemu_set_irq(s->irq_ir, pending); +} + +static void ir_status_postw(RegisterInfo *reg, uint64_t val64) +{ + VersalUsb2CtrlRegs *s = XILINX_VERSAL_USB2_CTRL_REGS(reg->opaque); + /* + * TODO: This should also clear USBSTS.HSE field in USB XHCI register. + * May be combine both the modules. + */ + ir_update_irq(s); +} + +static uint64_t ir_enable_prew(RegisterInfo *reg, uint64_t val64) +{ + VersalUsb2CtrlRegs *s = XILINX_VERSAL_USB2_CTRL_REGS(reg->opaque); + uint32_t val = val64; + + s->regs[R_IR_MASK] &= ~val; + ir_update_irq(s); + return 0; +} + +static uint64_t ir_disable_prew(RegisterInfo *reg, uint64_t val64) +{ + VersalUsb2CtrlRegs *s = XILINX_VERSAL_USB2_CTRL_REGS(reg->opaque); + uint32_t val = val64; + + s->regs[R_IR_MASK] |= val; + ir_update_irq(s); + return 0; +} + +static const RegisterAccessInfo usb2_ctrl_regs_regs_info[] = { + { .name = "BUS_FILTER", .addr = A_BUS_FILTER, + .rsvd = 0xfffffff0, + },{ .name = "PORT", .addr = A_PORT, + .rsvd = 0xffffffe0, + },{ .name = "JITTER_ADJUST", .addr = A_JITTER_ADJUST, + .reset = 0x20, + .rsvd = 0xffffffc0, + },{ .name = "BIGENDIAN", .addr = A_BIGENDIAN, + .rsvd = 0xfffffffe, + },{ .name = "COHERENCY", .addr = A_COHERENCY, + .rsvd = 0xfffffffe, + },{ .name = "XHC_BME", .addr = A_XHC_BME, + .reset = 0x1, + .rsvd = 0xfffffffe, + },{ .name = "REG_CTRL", .addr = A_REG_CTRL, + .rsvd = 0xfffffffe, + },{ .name = "IR_STATUS", .addr = A_IR_STATUS, + .rsvd = 0xfffffffc, + .w1c = 0x3, + .post_write = ir_status_postw, + },{ .name = "IR_MASK", .addr = A_IR_MASK, + .reset = 0x3, + .rsvd = 0xfffffffc, + .ro = 0x3, + },{ .name = "IR_ENABLE", .addr = A_IR_ENABLE, + .rsvd = 0xfffffffc, + .pre_write = ir_enable_prew, + },{ .name = "IR_DISABLE", .addr = A_IR_DISABLE, + .rsvd = 0xfffffffc, + .pre_write = ir_disable_prew, + },{ .name = "USB3", .addr = A_USB3, + } +}; + +static void usb2_ctrl_regs_reset_init(Object *obj, ResetType type) +{ + VersalUsb2CtrlRegs *s = XILINX_VERSAL_USB2_CTRL_REGS(obj); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) { + register_reset(&s->regs_info[i]); + } +} + +static void usb2_ctrl_regs_reset_hold(Object *obj) +{ + VersalUsb2CtrlRegs *s = XILINX_VERSAL_USB2_CTRL_REGS(obj); + + ir_update_irq(s); +} + +static const MemoryRegionOps usb2_ctrl_regs_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void usb2_ctrl_regs_init(Object *obj) +{ + VersalUsb2CtrlRegs *s = XILINX_VERSAL_USB2_CTRL_REGS(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + RegisterInfoArray *reg_array; + + memory_region_init(&s->iomem, obj, TYPE_XILINX_VERSAL_USB2_CTRL_REGS, + USB2_REGS_R_MAX * 4); + reg_array = + register_init_block32(DEVICE(obj), usb2_ctrl_regs_regs_info, + ARRAY_SIZE(usb2_ctrl_regs_regs_info), + s->regs_info, s->regs, + &usb2_ctrl_regs_ops, + XILINX_VERSAL_USB2_CTRL_REGS_ERR_DEBUG, + USB2_REGS_R_MAX * 4); + memory_region_add_subregion(&s->iomem, + 0x0, + ®_array->mem); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq_ir); +} + +static const VMStateDescription vmstate_usb2_ctrl_regs = { + .name = TYPE_XILINX_VERSAL_USB2_CTRL_REGS, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, VersalUsb2CtrlRegs, USB2_REGS_R_MAX), + VMSTATE_END_OF_LIST(), + } +}; + +static void usb2_ctrl_regs_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.enter = usb2_ctrl_regs_reset_init; + rc->phases.hold = usb2_ctrl_regs_reset_hold; + dc->vmsd = &vmstate_usb2_ctrl_regs; +} + +static const TypeInfo usb2_ctrl_regs_info = { + .name = TYPE_XILINX_VERSAL_USB2_CTRL_REGS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(VersalUsb2CtrlRegs), + .class_init = usb2_ctrl_regs_class_init, + .instance_init = usb2_ctrl_regs_init, +}; + +static void usb2_ctrl_regs_register_types(void) +{ + type_register_static(&usb2_ctrl_regs_info); +} + +type_init(usb2_ctrl_regs_register_types) |