aboutsummaryrefslogtreecommitdiffstats
path: root/hw/usb
diff options
context:
space:
mode:
authorTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
committerTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
commite02cda008591317b1625707ff8e115a4841aa889 (patch)
treeaee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/usb
parentcc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (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')
-rw-r--r--hw/usb/Kconfig135
-rw-r--r--hw/usb/bus.c776
-rw-r--r--hw/usb/ccid-card-emulated.c622
-rw-r--r--hw/usb/ccid-card-passthru.c424
-rw-r--r--hw/usb/ccid.h62
-rw-r--r--hw/usb/chipidea.c176
-rw-r--r--hw/usb/combined-packet.c190
-rw-r--r--hw/usb/core.c821
-rw-r--r--hw/usb/desc-msos.c239
-rw-r--r--hw/usb/desc.c812
-rw-r--r--hw/usb/desc.h244
-rw-r--r--hw/usb/dev-audio.c1029
-rw-r--r--hw/usb/dev-hid.c879
-rw-r--r--hw/usb/dev-hub.c704
-rw-r--r--hw/usb/dev-mtp.c2121
-rw-r--r--hw/usb/dev-network.c1429
-rw-r--r--hw/usb/dev-serial.c709
-rw-r--r--hw/usb/dev-smartcard-reader.c1496
-rw-r--r--hw/usb/dev-storage-bot.c63
-rw-r--r--hw/usb/dev-storage-classic.c157
-rw-r--r--hw/usb/dev-storage.c589
-rw-r--r--hw/usb/dev-uas.c991
-rw-r--r--hw/usb/dev-wacom.c383
-rw-r--r--hw/usb/hcd-dwc2.c1478
-rw-r--r--hw/usb/hcd-dwc2.h186
-rw-r--r--hw/usb/hcd-dwc3.c688
-rw-r--r--hw/usb/hcd-ehci-pci.c235
-rw-r--r--hw/usb/hcd-ehci-sysbus.c305
-rw-r--r--hw/usb/hcd-ehci.c2598
-rw-r--r--hw/usb/hcd-ehci.h383
-rw-r--r--hw/usb/hcd-musb.c1553
-rw-r--r--hw/usb/hcd-ohci-pci.c164
-rw-r--r--hw/usb/hcd-ohci.c2028
-rw-r--r--hw/usb/hcd-ohci.h121
-rw-r--r--hw/usb/hcd-uhci.c1370
-rw-r--r--hw/usb/hcd-uhci.h94
-rw-r--r--hw/usb/hcd-xhci-nec.c85
-rw-r--r--hw/usb/hcd-xhci-pci.c262
-rw-r--r--hw/usb/hcd-xhci-pci.h44
-rw-r--r--hw/usb/hcd-xhci-sysbus.c123
-rw-r--r--hw/usb/hcd-xhci-sysbus.h31
-rw-r--r--hw/usb/hcd-xhci.c3613
-rw-r--r--hw/usb/hcd-xhci.h228
-rw-r--r--hw/usb/host-libusb.c1978
-rw-r--r--hw/usb/host.h44
-rw-r--r--hw/usb/imx-usb-phy.c224
-rw-r--r--hw/usb/libhw.c69
-rw-r--r--hw/usb/meson.build84
-rw-r--r--hw/usb/pcap.c253
-rw-r--r--hw/usb/quirks-ftdi-ids.h1249
-rw-r--r--hw/usb/quirks-pl2303-ids.h150
-rw-r--r--hw/usb/quirks.c54
-rw-r--r--hw/usb/quirks.h918
-rw-r--r--hw/usb/redirect.c2618
-rw-r--r--hw/usb/trace-events347
-rw-r--r--hw/usb/trace.h1
-rw-r--r--hw/usb/tusb6010.c850
-rw-r--r--hw/usb/u2f-emulated.c405
-rw-r--r--hw/usb/u2f-passthru.c552
-rw-r--r--hw/usb/u2f.c351
-rw-r--r--hw/usb/u2f.h92
-rw-r--r--hw/usb/vt82c686-uhci-pci.c58
-rw-r--r--hw/usb/xen-usb.c1097
-rw-r--r--hw/usb/xlnx-usb-subsystem.c92
-rw-r--r--hw/usb/xlnx-versal-usb2-ctrl-regs.c228
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, &params, 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, &params, 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,
+ &reg_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,
+ &reg_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)