diff options
Diffstat (limited to 'hw/usb')
39 files changed, 33192 insertions, 0 deletions
diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs new file mode 100644 index 00000000..7443e386 --- /dev/null +++ b/hw/usb/Makefile.objs @@ -0,0 +1,39 @@ +# usb subsystem core +common-obj-y += core.o combined-packet.o bus.o libhw.o +common-obj-$(CONFIG_USB) += desc.o desc-msos.o + +# usb host adapters +common-obj-$(CONFIG_USB_UHCI) += hcd-uhci.o +common-obj-$(CONFIG_USB_OHCI) += hcd-ohci.o +common-obj-$(CONFIG_USB_EHCI) += hcd-ehci.o hcd-ehci-pci.o +common-obj-$(CONFIG_USB_EHCI_SYSBUS) += hcd-ehci-sysbus.o +common-obj-$(CONFIG_USB_XHCI) += hcd-xhci.o +common-obj-$(CONFIG_USB_MUSB) += hcd-musb.o + +# emulated usb devices +common-obj-$(CONFIG_USB) += dev-hub.o +common-obj-$(CONFIG_USB) += dev-hid.o +common-obj-$(CONFIG_USB_TABLET_WACOM) += dev-wacom.o +common-obj-$(CONFIG_USB_STORAGE_BOT)  += dev-storage.o +common-obj-$(CONFIG_USB_STORAGE_UAS)  += dev-uas.o +common-obj-$(CONFIG_USB_AUDIO)        += dev-audio.o +common-obj-$(CONFIG_USB_SERIAL)       += dev-serial.o +common-obj-$(CONFIG_USB_NETWORK)      += dev-network.o +common-obj-$(CONFIG_USB_BLUETOOTH)    += dev-bluetooth.o + +ifeq ($(CONFIG_USB_SMARTCARD),y) +common-obj-y                          += dev-smartcard-reader.o +common-obj-y                          += ccid-card-passthru.o +common-obj-$(CONFIG_SMARTCARD_NSS)    += ccid-card-emulated.o +ccid-card-emulated.o-cflags := -I$(SRC_PATH)/libcacard +endif + +ifeq ($(CONFIG_POSIX),y) +common-obj-$(CONFIG_USB_STORAGE_MTP)  += dev-mtp.o +endif + +# usb redirection +common-obj-$(CONFIG_USB_REDIR) += redirect.o quirks.o + +# usb pass-through +common-obj-y += $(patsubst %,host-%.o,$(HOST_USB)) diff --git a/hw/usb/bus.c b/hw/usb/bus.c new file mode 100644 index 00000000..5f39e1e3 --- /dev/null +++ b/hw/usb/bus.c @@ -0,0 +1,758 @@ +#include "hw/hw.h" +#include "hw/usb.h" +#include "hw/qdev.h" +#include "qemu/error-report.h" +#include "sysemu/sysemu.h" +#include "monitor/monitor.h" +#include "trace.h" + +static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent); + +static char *usb_get_dev_path(DeviceState *dev); +static char *usb_get_fw_dev_path(DeviceState *qdev); +static void usb_qdev_unrealize(DeviceState *qdev, Error **errp); + +static Property usb_props[] = { +    DEFINE_PROP_STRING("port", USBDevice, port_path), +    DEFINE_PROP_STRING("serial", USBDevice, serial), +    DEFINE_PROP_BIT("full-path", USBDevice, flags, +                    USB_DEV_FLAG_FULL_PATH, true), +    DEFINE_PROP_BIT("msos-desc", USBDevice, flags, +                    USB_DEV_FLAG_MSOS_DESC_ENABLE, true), +    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 = 0; +    } else { +        dev->attached = 1; +    } +    if (dev->setup_index < 0 || +        dev->setup_len < 0 || +        dev->setup_index > dev->setup_len || +        dev->setup_len > sizeof(dev->data_buf)) { +        return -EINVAL; +    } +    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_create_inplace(bus, bus_size, TYPE_USB_BUS, host, NULL); +    qbus_set_bus_hotplug_handler(BUS(bus), &error_abort); +    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_handle_destroy(USBDevice *dev) +{ +    USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); +    if (klass->handle_destroy) { +        klass->handle_destroy(dev); +    } +} + +void usb_device_cancel_packet(USBDevice *dev, USBPacket *p) +{ +    USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); +    if (klass->cancel_packet) { +        klass->cancel_packet(dev, p); +    } +} + +void usb_device_handle_attach(USBDevice *dev) +{ +    USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); +    if (klass->handle_attach) { +        klass->handle_attach(dev); +    } +} + +void usb_device_handle_reset(USBDevice *dev) +{ +    USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); +    if (klass->handle_reset) { +        klass->handle_reset(dev); +    } +} + +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, NULL); +            error_propagate(errp, local_err); +            return; +        } +    } +} + +static void usb_qdev_unrealize(DeviceState *qdev, Error **errp) +{ +    USBDevice *dev = USB_DEVICE(qdev); + +    if (dev->attached) { +        usb_device_detach(dev); +    } +    usb_device_handle_destroy(dev); +    if (dev->port) { +        usb_release_port(dev); +    } +} + +typedef struct LegacyUSBFactory +{ +    const char *name; +    const char *usbdevice_name; +    USBDevice *(*usbdevice_init)(USBBus *bus, const char *params); +} LegacyUSBFactory; + +static GSList *legacy_usb_factory; + +void usb_legacy_register(const char *typename, const char *usbdevice_name, +                         USBDevice *(*usbdevice_init)(USBBus *bus, +                                                      const char *params)) +{ +    if (usbdevice_name) { +        LegacyUSBFactory *f = g_malloc0(sizeof(*f)); +        f->name = typename; +        f->usbdevice_name = usbdevice_name; +        f->usbdevice_init = usbdevice_init; +        legacy_usb_factory = g_slist_append(legacy_usb_factory, f); +    } +} + +USBDevice *usb_create(USBBus *bus, const char *name) +{ +    DeviceState *dev; + +    dev = qdev_create(&bus->qbus, name); +    return USB_DEVICE(dev); +} + +static USBDevice *usb_try_create_simple(USBBus *bus, const char *name, +                                        Error **errp) +{ +    Error *err = NULL; +    USBDevice *dev; + +    dev = USB_DEVICE(qdev_try_create(&bus->qbus, name)); +    if (!dev) { +        error_setg(errp, "Failed to create USB device '%s'", name); +        return NULL; +    } +    object_property_set_bool(OBJECT(dev), true, "realized", &err); +    if (err) { +        error_setg(errp, "Failed to initialize USB device '%s': %s", +                   name, error_get_pretty(err)); +        error_free(err); +        object_unparent(OBJECT(dev)); +        return NULL; +    } +    return dev; +} + +USBDevice *usb_create_simple(USBBus *bus, const char *name) +{ +    return usb_try_create_simple(bus, name, &error_abort); +} + +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) { +        snprintf(downstream->path, sizeof(downstream->path), "%s.%d", +                 upstream->path, portnr); +        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; + +    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 */ +            usb_try_create_simple(bus, "usb-hub", 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); +        } +    } +} + +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++; +    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--; +    return 0; +} + +int usb_device_delete_addr(int busnr, int addr) +{ +    USBBus *bus; +    USBPort *port; +    USBDevice *dev; + +    bus = usb_bus_find(busnr); +    if (!bus) +        return -1; + +    QTAILQ_FOREACH(port, &bus->used, next) { +        if (port->dev->addr == addr) +            break; +    } +    if (!port) +        return -1; +    dev = port->dev; + +    object_unparent(OBJECT(dev)); +    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 = NULL; + +    if (dev->flags & (1 << USB_DEV_FLAG_FULL_PATH)) { +        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 */ +            pos += snprintf(fw_path + pos, fw_len - pos, "%s@%lx", +                            qdev_fw_name(qdev), nr); +            break; +        } +    } +    return fw_path; +} + +void hmp_info_usb(Monitor *mon, const QDict *qdict) +{ +    USBBus *bus; +    USBDevice *dev; +    USBPort *port; + +    if (QTAILQ_EMPTY(&busses)) { +        monitor_printf(mon, "USB support not enabled\n"); +        return; +    } + +    QTAILQ_FOREACH(bus, &busses, next) { +        QTAILQ_FOREACH(port, &bus->used, next) { +            dev = port->dev; +            if (!dev) +                continue; +            monitor_printf(mon, "  Device %d.%d, Port %s, Speed %s Mb/s, Product %s\n", +                           bus->busnr, dev->addr, port->path, usb_speed(dev->speed), +                           dev->product_desc); +        } +    } +} + +/* handle legacy -usbdevice cmd line option */ +USBDevice *usbdevice_create(const char *cmdline) +{ +    USBBus *bus = usb_bus_find(-1 /* any */); +    LegacyUSBFactory *f = NULL; +    Error *err = NULL; +    GSList *i; +    char driver[32]; +    const char *params; +    int len; +    USBDevice *dev; + +    params = strchr(cmdline,':'); +    if (params) { +        params++; +        len = params - cmdline; +        if (len > sizeof(driver)) +            len = sizeof(driver); +        pstrcpy(driver, len, cmdline); +    } else { +        params = ""; +        pstrcpy(driver, sizeof(driver), cmdline); +    } + +    for (i = legacy_usb_factory; i; i = i->next) { +        f = i->data; +        if (strcmp(f->usbdevice_name, driver) == 0) { +            break; +        } +    } +    if (i == NULL) { +#if 0 +        /* no error because some drivers are not converted (yet) */ +        error_report("usbdevice %s not found", driver); +#endif +        return NULL; +    } + +    if (!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; +    } + +    if (f->usbdevice_init) { +        dev = f->usbdevice_init(bus, params); +    } else { +        if (*params) { +            error_report("usbdevice %s accepts no params", driver); +            return NULL; +        } +        dev = usb_create(bus, f->name); +    } +    if (!dev) { +        error_report("Failed to create USB device '%s'", f->name); +        return NULL; +    } +    object_property_set_bool(OBJECT(dev), true, "realized", &err); +    if (err) { +        error_report("Failed to initialize USB device '%s': %s", +                     f->name, error_get_pretty(err)); +        error_free(err); +        object_unparent(OBJECT(dev)); +        return NULL; +    } +    return dev; +} + +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; +    k->props    = usb_props; +} + +static const TypeInfo usb_device_type_info = { +    .name = TYPE_USB_DEVICE, +    .parent = TYPE_DEVICE, +    .instance_size = sizeof(USBDevice), +    .abstract = true, +    .class_size = sizeof(USBDeviceClass), +    .class_init = usb_device_class_init, +}; + +static void usb_register_types(void) +{ +    type_register_static(&usb_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 00000000..72329ed7 --- /dev/null +++ b/hw/usb/ccid-card-emulated.c @@ -0,0 +1,602 @@ +/* + * 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 <eventt.h> +#include <vevent.h> +#include <vreader.h> +#include <vcard_emul.h> + +#include "qemu/thread.h" +#include "sysemu/char.h" +#include "ccid.h" + +#define DPRINTF(card, lvl, fmt, ...) \ +do {\ +    if (lvl <= card->debug) {\ +        printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__);\ +    } \ +} while (0) + +#define EMULATED_DEV_NAME "ccid-card-emulated" + +#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 + +typedef struct EmulatedState EmulatedState; + +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(event_list, EmulEvent) event_list; +    QemuMutex event_list_mutex; +    QemuThread event_thread_id; +    VReader *reader; +    QSIMPLEQ_HEAD(guest_apdu_list, 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 = DO_UPCAST(EmulatedState, base, 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 = DO_UPCAST(EmulatedState, base, 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 = (EmulEvent *)g_malloc(sizeof(EmulEvent)); + +    assert(event); +    event->p.gen.type = type; +    emulated_push_event(card, event); +} + +static void emulated_push_error(EmulatedState *card, uint64_t code) +{ +    EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent)); + +    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; +        } +        qemu_mutex_lock(&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); +        } +        qemu_mutex_unlock(&card->vreader_mutex); +    } +    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: quiting 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; +            break; +        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_mutex_lock(&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); +    qemu_mutex_unlock(&card->event_list_mutex); +} + +static int init_event_notifier(EmulatedState *card) +{ +    if (event_notifier_init(&card->notifier, false) < 0) { +        DPRINTF(card, 2, "event notifier creation failed\n"); +        return -1; +    } +    event_notifier_set_handler(&card->notifier, card_event_handler); +    return 0; +} + +#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 int emulated_initfn(CCIDCardState *base) +{ +    EmulatedState *card = DO_UPCAST(EmulatedState, base, 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) < 0) { +        return -1; +    } + +    card->backend = 0; +    if (card->backend_str) { +        card->backend = parse_enumeration(card->backend_str, +                                          backend_enum_table, 0); +    } + +    if (card->backend == 0) { +        printf("backend must be one of:\n"); +        for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) { +            printf("%s\n", ptable->name); +        } +        return -1; +    } + +    /* 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 { +            printf("%s: you must provide all three certs for" +                   " certificates backend\n", EMULATED_DEV_NAME); +            return -1; +        } +    } else { +        if (card->backend != BACKEND_NSS_EMULATED) { +            printf("%s: bad backend specified. The options are:\n%s (default)," +                " %s.\n", EMULATED_DEV_NAME, BACKEND_NSS_EMULATED_NAME, +                BACKEND_CERTIFICATES_NAME); +            return -1; +        } +        if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) { +            printf("%s: unexpected cert parameters to nss emulated backend\n", +                   EMULATED_DEV_NAME); +            return -1; +        } +        /* default to mirroring the local hardware readers */ +        ret = wrap_vcard_emul_init(NULL); +    } +    if (ret != VCARD_EMUL_OK) { +        printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); +        return -1; +    } +    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 0; +} + +static int emulated_exitfn(CCIDCardState *base) +{ +    EmulatedState *card = DO_UPCAST(EmulatedState, base, 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); + +    /* 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); +    return 0; +} + +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->initfn = emulated_initfn; +    cc->exitfn = emulated_exitfn; +    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"; +    dc->props = emulated_card_properties; +} + +static const TypeInfo emulated_card_info = { +    .name          = EMULATED_DEV_NAME, +    .parent        = TYPE_CCID_CARD, +    .instance_size = sizeof(EmulatedState), +    .class_init    = emulated_class_initfn, +}; + +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 00000000..85a4fc3e --- /dev/null +++ b/hw/usb/ccid-card-passthru.c @@ -0,0 +1,413 @@ +/* + * 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 "sysemu/char.h" +#include "qemu/error-report.h" +#include "qemu/sockets.h" +#include "ccid.h" +#include "libcacard/vscard_common.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 PASSTHRU_DEV_NAME "ccid-card-passthru" +#define VSCARD_IN_SIZE 65536 + +/* maximum size of ATR - from 7816-3 */ +#define MAX_ATR_SIZE        40 + +typedef struct PassthruState PassthruState; + +struct PassthruState { +    CCIDCardState base; +    CharDriverState *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; +}; + +/* + * 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); +    qemu_chr_fe_write(s->cs, (uint8_t *)&scr_msg_header, sizeof(VSCMsgHeader)); +    qemu_chr_fe_write(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_delete(card->cs); +    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 %d +  size %d > %d. 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, int event) +{ +    PassthruState *card = opaque; + +    switch (event) { +    case CHR_EVENT_BREAK: +        card->vscard_in_pos = card->vscard_in_hdr = 0; +        break; +    case CHR_EVENT_FOCUS: +        break; +    case CHR_EVENT_OPENED: +        DPRINTF(card, D_INFO, "%s: CHR_EVENT_OPENED\n", __func__); +        break; +    } +} + +/* End VSCard handling */ + +static void passthru_apdu_from_guest( +    CCIDCardState *base, const uint8_t *apdu, uint32_t len) +{ +    PassthruState *card = DO_UPCAST(PassthruState, base, base); + +    if (!card->cs) { +        printf("ccid-passthru: no chardev, discarding apdu length %d\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 = DO_UPCAST(PassthruState, base, base); + +    *len = card->atr_length; +    return card->atr; +} + +static int passthru_initfn(CCIDCardState *base) +{ +    PassthruState *card = DO_UPCAST(PassthruState, base, base); + +    card->vscard_in_pos = 0; +    card->vscard_in_hdr = 0; +    if (card->cs) { +        DPRINTF(card, D_INFO, "initing chardev\n"); +        qemu_chr_add_handlers(card->cs, +            ccid_card_vscard_can_read, +            ccid_card_vscard_read, +            ccid_card_vscard_event, card); +        ccid_card_vscard_send_init(card); +    } else { +        error_report("missing chardev"); +        return -1; +    } +    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); +    return 0; +} + +static int passthru_exitfn(CCIDCardState *base) +{ +    return 0; +} + +static 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->initfn = passthru_initfn; +    cc->exitfn = passthru_exitfn; +    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; +    dc->props = passthru_card_properties; +} + +static const TypeInfo passthru_card_info = { +    .name          = PASSTHRU_DEV_NAME, +    .parent        = TYPE_CCID_CARD, +    .instance_size = sizeof(PassthruState), +    .class_init    = passthru_class_initfn, +}; + +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 00000000..9334da8a --- /dev/null +++ b/hw/usb/ccid.h @@ -0,0 +1,65 @@ +/* + * 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.h" + +typedef struct CCIDCardState CCIDCardState; +typedef struct CCIDCardInfo CCIDCardInfo; + +#define TYPE_CCID_CARD "ccid-card" +#define CCID_CARD(obj) \ +     OBJECT_CHECK(CCIDCardState, (obj), TYPE_CCID_CARD) +#define CCID_CARD_CLASS(klass) \ +     OBJECT_CLASS_CHECK(CCIDCardClass, (klass), TYPE_CCID_CARD) +#define CCID_CARD_GET_CLASS(obj) \ +     OBJECT_GET_CLASS(CCIDCardClass, (obj), TYPE_CCID_CARD) + +/* + * callbacks to be used by the CCID device (hw/usb-ccid.c) to call + * into the smartcard device (hw/ccid-card-*.c) + */ +typedef struct CCIDCardClass { +    DeviceClass parent_class; +    const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); +    void (*apdu_from_guest)(CCIDCardState *card, +                            const uint8_t *apdu, +                            uint32_t len); +    int (*exitfn)(CCIDCardState *card); +    int (*initfn)(CCIDCardState *card); +} CCIDCardClass; + +/* + * 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/combined-packet.c b/hw/usb/combined-packet.c new file mode 100644 index 00000000..ad77705f --- /dev/null +++ b/hw/usb/combined-packet.c @@ -0,0 +1,187 @@ +/* + * 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 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "qemu-common.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, packets_head)->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 == 16348 && p->int_req)) { +            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 00000000..d0025db6 --- /dev/null +++ b/hw/usb/core.c @@ -0,0 +1,794 @@ +/* + * QEMU USB emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * 2008 Generic packet handler rewrite by Max Krasnyansky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "hw/usb.h" +#include "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; +    } +    dev->remote_wakeup = 0; +    dev->addr = 0; +    dev->state = USB_STATE_DEFAULT; +    usb_device_handle_reset(dev); +} + +void usb_wakeup(USBEndpoint *ep, unsigned int stream) +{ +    USBDevice *dev = ep->dev; +    USBBus *bus = usb_bus_from_device(dev); + +    if (dev->remote_wakeup && dev->port && dev->port->ops->wakeup) { +        dev->port->ops->wakeup(dev->port); +    } +    if (bus->ops->wakeup_endpoint) { +        bus->ops->wakeup_endpoint(bus, ep, 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; + +    if (p->iov.size != 8) { +        p->status = USB_RET_STALL; +        return; +    } + +    usb_packet_copy(p, s->setup_buf, p->iov.size); +    p->actual_length = 0; +    s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6]; +    s->setup_index = 0; + +    request = (s->setup_buf[0] << 8) | s->setup_buf[1]; +    value   = (s->setup_buf[3] << 8) | s->setup_buf[2]; +    index   = (s->setup_buf[5] << 8) | s->setup_buf[4]; + +    if (s->setup_buf[0] & USB_DIR_IN) { +        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 > sizeof(s->data_buf)) { +            fprintf(stderr, +                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", +                s->setup_len, sizeof(s->data_buf)); +            p->status = USB_RET_STALL; +            return; +        } +        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_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; +        } +        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; +        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; +            /* 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; +        break; + +    default: +        p->status = USB_RET_STALL; +    } +} + +static void do_parameter(USBDevice *s, USBPacket *p) +{ +    int i, request, value, index; + +    for (i = 0; i < 8; i++) { +        s->setup_buf[i] = p->parameter >> (i*8); +    } + +    s->setup_state = SETUP_STATE_PARAM; +    s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6]; +    s->setup_index = 0; + +    request = (s->setup_buf[0] << 8) | s->setup_buf[1]; +    value   = (s->setup_buf[3] << 8) | s->setup_buf[2]; +    index   = (s->setup_buf[5] << 8) | s->setup_buf[4]; + +    if (s->setup_len > sizeof(s->data_buf)) { +        fprintf(stderr, +                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", +                s->setup_len, sizeof(s->data_buf)); +        p->status = USB_RET_STALL; +        return; +    } + +    if (p->pid == USB_TOKEN_OUT) { +        usb_packet_copy(p, s->data_buf, s->setup_len); +    } + +    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); +    } +} + +/* 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; +    } + +    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; +        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; + +    /* +     * 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(). +     */ +    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 */ +        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_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_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; + +    if (dev == NULL) { +        return NULL; +    } +    eps = (pid == USB_TOKEN_IN) ? dev->ep_in : dev->ep_out; +    if (ep == 0) { +        return &dev->ep_ctl; +    } +    assert(pid == USB_TOKEN_IN || pid == USB_TOKEN_OUT); +    assert(ep > 0 && ep <= USB_MAX_ENDPOINTS); +    return eps + ep - 1; +} + +uint8_t usb_ep_get_type(USBDevice *dev, int pid, int ep) +{ +    struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); +    return uep->type; +} + +void usb_ep_set_type(USBDevice *dev, int pid, int ep, uint8_t type) +{ +    struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); +    uep->type = type; +} + +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 00000000..32c3600d --- /dev/null +++ b/hw/usb/desc-msos.c @@ -0,0 +1,238 @@ +#include "hw/usb.h" +#include "hw/usb/desc.h" + +/* + * Microsoft OS Descriptors + * + * Windows tries to fetch some special descriptors with informations + * 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 enougth. + *   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 enouth + * 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.  Havn'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 enouth to make windows actually use it. +         * This is the "Yes, we really mean it" registy 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 00000000..b82c397e --- /dev/null +++ b/hw/usb/desc.c @@ -0,0 +1,804 @@ +#include <ctype.h> + +#include "hw/usb.h" +#include "hw/usb/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 serial[64]; +    char *path; +    int dst; + +    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); +    dst = snprintf(serial, sizeof(serial), "%s", desc->str[index]); +    path = qdev_get_dev_path(hcd); +    if (path) { +        dst += snprintf(serial+dst, sizeof(serial)-dst, "-%s", path); +    } +    dst += snprintf(serial+dst, sizeof(serial)-dst, "-%s", dev->port->path); +    usb_desc_set_string(dev, index, 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", __FUNCTION__, +                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 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 00000000..8e8db03a --- /dev/null +++ b/hw/usb/desc.h @@ -0,0 +1,245 @@ +#ifndef QEMU_HW_USB_DESC_H +#define QEMU_HW_USB_DESC_H + +#include <inttypes.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 releated interfaces */ +struct USBDescIfaceAssoc { +    uint8_t                   bFirstInterface; +    uint8_t                   bInterfaceCount; +    uint8_t                   bFunctionClass; +    uint8_t                   bFunctionSubClass; +    uint8_t                   bFunctionProtocol; +    uint8_t                   iFunction; + +    uint8_t                   nif; +    const USBDescIface        *ifs; +}; + +struct USBDescIface { +    uint8_t                   bInterfaceNumber; +    uint8_t                   bAlternateSetting; +    uint8_t                   bNumEndpoints; +    uint8_t                   bInterfaceClass; +    uint8_t                   bInterfaceSubClass; +    uint8_t                   bInterfaceProtocol; +    uint8_t                   iInterface; + +    uint8_t                   ndesc; +    USBDescOther              *descs; +    USBDescEndpoint           *eps; +}; + +struct USBDescEndpoint { +    uint8_t                   bEndpointAddress; +    uint8_t                   bmAttributes; +    uint16_t                  wMaxPacketSize; +    uint8_t                   bInterval; +    uint8_t                   bRefresh; +    uint8_t                   bSynchAddress; + +    uint8_t                   is_audio; /* has bRefresh + bSynchAddress */ +    uint8_t                   *extra; + +    /* 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 00000000..f092bb84 --- /dev/null +++ b/hw/usb/dev-audio.c @@ -0,0 +1,702 @@ +/* + * QEMU USB audio device + * + * written by: + *  H. Peter Anvin <hpa@linux.intel.com> + *  Gerd Hoffmann <kraxel@redhat.com> + * + * lousely based on usb net device code which is: + * + * Copyright (c) 2006 Thomas Sailer + * Copyright (c) 2008 Andrzej Zaborowski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/hw.h" +#include "audio/audio.h" + +#define USBAUDIO_VENDOR_NUM     0x46f4 /* CRC16() of "QEMU" */ +#define USBAUDIO_PRODUCT_NUM    0x0002 + +#define DEV_CONFIG_VALUE        1 /* The one and only */ + +/* Descriptor subtypes for AC interfaces */ +#define DST_AC_HEADER           1 +#define DST_AC_INPUT_TERMINAL   2 +#define DST_AC_OUTPUT_TERMINAL  3 +#define DST_AC_FEATURE_UNIT     6 +/* Descriptor subtypes for AS interfaces */ +#define DST_AS_GENERAL          1 +#define DST_AS_FORMAT_TYPE      2 +/* Descriptor subtypes for endpoints */ +#define DST_EP_GENERAL          1 + +enum usb_audio_strings { +    STRING_NULL, +    STRING_MANUFACTURER, +    STRING_PRODUCT, +    STRING_SERIALNUMBER, +    STRING_CONFIG, +    STRING_USBAUDIO_CONTROL, +    STRING_INPUT_TERMINAL, +    STRING_FEATURE_UNIT, +    STRING_OUTPUT_TERMINAL, +    STRING_NULL_STREAM, +    STRING_REAL_STREAM, +}; + +static const USBDescStrings usb_audio_stringtable = { +    [STRING_MANUFACTURER]       = "QEMU", +    [STRING_PRODUCT]            = "QEMU USB Audio", +    [STRING_SERIALNUMBER]       = "1", +    [STRING_CONFIG]             = "Audio Configuration", +    [STRING_USBAUDIO_CONTROL]   = "Audio Device", +    [STRING_INPUT_TERMINAL]     = "Audio Output Pipe", +    [STRING_FEATURE_UNIT]       = "Audio Output Volume Control", +    [STRING_OUTPUT_TERMINAL]    = "Audio Output Terminal", +    [STRING_NULL_STREAM]        = "Audio Output - Disabled", +    [STRING_REAL_STREAM]        = "Audio Output - 48 kHz Stereo", +}; + +#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff) +#define U24(x) U16(x), (((x) >> 16) & 0xff) +#define U32(x) U24(x), (((x) >> 24) & 0xff) + +/* + * A Basic Audio Device uses these specific values + */ +#define USBAUDIO_PACKET_SIZE     192 +#define USBAUDIO_SAMPLE_RATE     48000 +#define USBAUDIO_PACKET_INTERVAL 1 + +static const USBDescIface desc_iface[] = { +    { +        .bInterfaceNumber              = 0, +        .bNumEndpoints                 = 0, +        .bInterfaceClass               = USB_CLASS_AUDIO, +        .bInterfaceSubClass            = USB_SUBCLASS_AUDIO_CONTROL, +        .bInterfaceProtocol            = 0x04, +        .iInterface                    = STRING_USBAUDIO_CONTROL, +        .ndesc                         = 4, +        .descs = (USBDescOther[]) { +            { +                /* Headphone Class-Specific AC Interface Header Descriptor */ +                .data = (uint8_t[]) { +                    0x09,                       /*  u8  bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8  bDescriptorType */ +                    DST_AC_HEADER,              /*  u8  bDescriptorSubtype */ +                    U16(0x0100),                /* u16  bcdADC */ +                    U16(0x2b),                  /* u16  wTotalLength */ +                    0x01,                       /*  u8  bInCollection */ +                    0x01,                       /*  u8  baInterfaceNr */ +                } +            },{ +                /* Generic Stereo Input Terminal ID1 Descriptor */ +                .data = (uint8_t[]) { +                    0x0c,                       /*  u8  bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8  bDescriptorType */ +                    DST_AC_INPUT_TERMINAL,      /*  u8  bDescriptorSubtype */ +                    0x01,                       /*  u8  bTerminalID */ +                    U16(0x0101),                /* u16  wTerminalType */ +                    0x00,                       /*  u8  bAssocTerminal */ +                    0x02,                       /* u16  bNrChannels */ +                    U16(0x0003),                /* u16  wChannelConfig */ +                    0x00,                       /*  u8  iChannelNames */ +                    STRING_INPUT_TERMINAL,      /*  u8  iTerminal */ +                } +            },{ +                /* Generic Stereo Feature Unit ID2 Descriptor */ +                .data = (uint8_t[]) { +                    0x0d,                       /*  u8  bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8  bDescriptorType */ +                    DST_AC_FEATURE_UNIT,        /*  u8  bDescriptorSubtype */ +                    0x02,                       /*  u8  bUnitID */ +                    0x01,                       /*  u8  bSourceID */ +                    0x02,                       /*  u8  bControlSize */ +                    U16(0x0001),                /* u16  bmaControls(0) */ +                    U16(0x0002),                /* u16  bmaControls(1) */ +                    U16(0x0002),                /* u16  bmaControls(2) */ +                    STRING_FEATURE_UNIT,        /*  u8  iFeature */ +                } +            },{ +                /* Headphone Ouptut Terminal ID3 Descriptor */ +                .data = (uint8_t[]) { +                    0x09,                       /*  u8  bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8  bDescriptorType */ +                    DST_AC_OUTPUT_TERMINAL,     /*  u8  bDescriptorSubtype */ +                    0x03,                       /*  u8  bUnitID */ +                    U16(0x0301),                /* u16  wTerminalType (SPK) */ +                    0x00,                       /*  u8  bAssocTerminal */ +                    0x02,                       /*  u8  bSourceID */ +                    STRING_OUTPUT_TERMINAL,     /*  u8  iTerminal */ +                } +            } +        }, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 0, +        .bNumEndpoints                 = 0, +        .bInterfaceClass               = USB_CLASS_AUDIO, +        .bInterfaceSubClass            = USB_SUBCLASS_AUDIO_STREAMING, +        .iInterface                    = STRING_NULL_STREAM, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 1, +        .bNumEndpoints                 = 1, +        .bInterfaceClass               = USB_CLASS_AUDIO, +        .bInterfaceSubClass            = USB_SUBCLASS_AUDIO_STREAMING, +        .iInterface                    = STRING_REAL_STREAM, +        .ndesc                         = 2, +        .descs = (USBDescOther[]) { +            { +                /* Headphone Class-specific AS General Interface Descriptor */ +                .data = (uint8_t[]) { +                    0x07,                       /*  u8  bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8  bDescriptorType */ +                    DST_AS_GENERAL,             /*  u8  bDescriptorSubtype */ +                    0x01,                       /*  u8  bTerminalLink */ +                    0x00,                       /*  u8  bDelay */ +                    0x01, 0x00,                 /* u16  wFormatTag */ +                } +            },{ +                /* Headphone Type I Format Type Descriptor */ +                .data = (uint8_t[]) { +                    0x0b,                       /*  u8  bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8  bDescriptorType */ +                    DST_AS_FORMAT_TYPE,         /*  u8  bDescriptorSubtype */ +                    0x01,                       /*  u8  bFormatType */ +                    0x02,                       /*  u8  bNrChannels */ +                    0x02,                       /*  u8  bSubFrameSize */ +                    0x10,                       /*  u8  bBitResolution */ +                    0x01,                       /*  u8  bSamFreqType */ +                    U24(USBAUDIO_SAMPLE_RATE),  /* u24  tSamFreq */ +                } +            } +        }, +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_OUT | 0x01, +                .bmAttributes          = 0x0d, +                .wMaxPacketSize        = USBAUDIO_PACKET_SIZE, +                .bInterval             = 1, +                .is_audio              = 1, +                /* Stereo Headphone Class-specific +                   AS Audio Data Endpoint Descriptor */ +                .extra = (uint8_t[]) { +                    0x07,                       /*  u8  bLength */ +                    USB_DT_CS_ENDPOINT,         /*  u8  bDescriptorType */ +                    DST_EP_GENERAL,             /*  u8  bDescriptorSubtype */ +                    0x00,                       /*  u8  bmAttributes */ +                    0x00,                       /*  u8  bLockDelayUnits */ +                    U16(0x0000),                /* u16  wLockDelay */ +                }, +            }, +        } +    } +}; + +static const USBDescDevice desc_device = { +    .bcdUSB                        = 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, +}; + +/* + * A USB audio device supports an arbitrary number of alternate + * interface settings for each interface.  Each corresponds to a block + * diagram of parameterized blocks.  This can thus refer to things like + * number of channels, data rates, or in fact completely different + * block diagrams.  Alternative setting 0 is always the null block diagram, + * which is used by a disabled device. + */ +enum usb_audio_altset { +    ALTSET_OFF  = 0x00,         /* No endpoint */ +    ALTSET_ON   = 0x01,         /* Single endpoint */ +}; + +/* + * Class-specific control requests + */ +#define CR_SET_CUR      0x01 +#define CR_GET_CUR      0x81 +#define CR_SET_MIN      0x02 +#define CR_GET_MIN      0x82 +#define CR_SET_MAX      0x03 +#define CR_GET_MAX      0x83 +#define CR_SET_RES      0x04 +#define CR_GET_RES      0x84 +#define CR_SET_MEM      0x05 +#define CR_GET_MEM      0x85 +#define CR_GET_STAT     0xff + +/* + * Feature Unit Control Selectors + */ +#define MUTE_CONTROL                    0x01 +#define VOLUME_CONTROL                  0x02 +#define BASS_CONTROL                    0x03 +#define MID_CONTROL                     0x04 +#define TREBLE_CONTROL                  0x05 +#define GRAPHIC_EQUALIZER_CONTROL       0x06 +#define AUTOMATIC_GAIN_CONTROL          0x07 +#define DELAY_CONTROL                   0x08 +#define BASS_BOOST_CONTROL              0x09 +#define LOUDNESS_CONTROL                0x0a + +/* + * buffering + */ + +struct streambuf { +    uint8_t *data; +    uint32_t size; +    uint32_t prod; +    uint32_t cons; +}; + +static void streambuf_init(struct streambuf *buf, uint32_t size) +{ +    g_free(buf->data); +    buf->size = size - (size % USBAUDIO_PACKET_SIZE); +    buf->data = g_malloc(buf->size); +    buf->prod = 0; +    buf->cons = 0; +} + +static void streambuf_fini(struct streambuf *buf) +{ +    g_free(buf->data); +    buf->data = NULL; +} + +static int streambuf_put(struct streambuf *buf, USBPacket *p) +{ +    uint32_t free = buf->size - (buf->prod - buf->cons); + +    if (!free) { +        return 0; +    } +    assert(free >= USBAUDIO_PACKET_SIZE); +    usb_packet_copy(p, buf->data + (buf->prod % buf->size), +                    USBAUDIO_PACKET_SIZE); +    buf->prod += USBAUDIO_PACKET_SIZE; +    return USBAUDIO_PACKET_SIZE; +} + +static uint8_t *streambuf_get(struct streambuf *buf) +{ +    uint32_t used = buf->prod - buf->cons; +    uint8_t *data; + +    if (!used) { +        return NULL; +    } +    assert(used >= USBAUDIO_PACKET_SIZE); +    data = buf->data + (buf->cons % buf->size); +    buf->cons += USBAUDIO_PACKET_SIZE; +    return data; +} + +typedef struct USBAudioState { +    /* qemu interfaces */ +    USBDevice dev; +    QEMUSoundCard card; + +    /* state */ +    struct { +        enum usb_audio_altset altset; +        struct audsettings as; +        SWVoiceOut *voice; +        bool mute; +        uint8_t vol[2]; +        struct streambuf buf; +    } out; + +    /* properties */ +    uint32_t debug; +    uint32_t buffer; +} USBAudioState; + +#define TYPE_USB_AUDIO "usb-audio" +#define USB_AUDIO(obj) OBJECT_CHECK(USBAudioState, (obj), TYPE_USB_AUDIO) + +static void output_callback(void *opaque, int avail) +{ +    USBAudioState *s = opaque; +    uint8_t *data; + +    for (;;) { +        if (avail < USBAUDIO_PACKET_SIZE) { +            return; +        } +        data = streambuf_get(&s->out.buf); +        if (!data) { +            return; +        } +        AUD_write(s->out.voice, data, USBAUDIO_PACKET_SIZE); +        avail -= USBAUDIO_PACKET_SIZE; +    } +} + +static int usb_audio_set_output_altset(USBAudioState *s, int altset) +{ +    switch (altset) { +    case ALTSET_OFF: +        streambuf_init(&s->out.buf, s->buffer); +        AUD_set_active_out(s->out.voice, false); +        break; +    case ALTSET_ON: +        AUD_set_active_out(s->out.voice, true); +        break; +    default: +        return -1; +    } + +    if (s->debug) { +        fprintf(stderr, "usb-audio: set interface %d\n", altset); +    } +    s->out.altset = altset; +    return 0; +} + +/* + * Note: we arbitrarily map the volume control range onto -inf..+8 dB + */ +#define ATTRIB_ID(cs, attrib, idif)     \ +    (((cs) << 24) | ((attrib) << 16) | (idif)) + +static int usb_audio_get_control(USBAudioState *s, uint8_t attrib, +                                 uint16_t cscn, uint16_t idif, +                                 int length, uint8_t *data) +{ +    uint8_t cs = cscn >> 8; +    uint8_t cn = cscn - 1;      /* -1 for the non-present master control */ +    uint32_t aid = ATTRIB_ID(cs, attrib, idif); +    int ret = USB_RET_STALL; + +    switch (aid) { +    case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200): +        data[0] = s->out.mute; +        ret = 1; +        break; +    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200): +        if (cn < 2) { +            uint16_t vol = (s->out.vol[cn] * 0x8800 + 127) / 255 + 0x8000; +            data[0] = vol; +            data[1] = vol >> 8; +            ret = 2; +        } +        break; +    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200): +        if (cn < 2) { +            data[0] = 0x01; +            data[1] = 0x80; +            ret = 2; +        } +        break; +    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): +        if (cn < 2) { +            data[0] = 0x00; +            data[1] = 0x08; +            ret = 2; +        } +        break; +    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): +        if (cn < 2) { +            data[0] = 0x88; +            data[1] = 0x00; +            ret = 2; +        } +        break; +    } + +    return ret; +} +static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, +                                 uint16_t cscn, uint16_t idif, +                                 int length, uint8_t *data) +{ +    uint8_t cs = cscn >> 8; +    uint8_t cn = cscn - 1;      /* -1 for the non-present master control */ +    uint32_t aid = ATTRIB_ID(cs, attrib, idif); +    int ret = USB_RET_STALL; +    bool set_vol = false; + +    switch (aid) { +    case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200): +        s->out.mute = data[0] & 1; +        set_vol = true; +        ret = 0; +        break; +    case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200): +        if (cn < 2) { +            uint16_t vol = data[0] + (data[1] << 8); + +            if (s->debug) { +                fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol); +            } + +            vol -= 0x8000; +            vol = (vol * 255 + 0x4400) / 0x8800; +            if (vol > 255) { +                vol = 255; +            } + +            s->out.vol[cn] = vol; +            set_vol = true; +            ret = 0; +        } +        break; +    } + +    if (set_vol) { +        if (s->debug) { +            fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n", +                    s->out.mute, s->out.vol[0], s->out.vol[1]); +        } +        AUD_set_volume_out(s->out.voice, s->out.mute, +                           s->out.vol[0], s->out.vol[1]); +    } + +    return ret; +} + +static 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); +    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_handle_destroy(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); + +    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.mute          = false; +    s->out.vol[0]        = 240; /* 0 dB */ +    s->out.vol[1]        = 240; /* 0 dB */ +    s->out.as.freq       = USBAUDIO_SAMPLE_RATE; +    s->out.as.nchannels  = 2; +    s->out.as.fmt        = AUD_FMT_S16; +    s->out.as.endianness = 0; +    streambuf_init(&s->out.buf, s->buffer); + +    s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_AUDIO, +                                s, output_callback, &s->out.as); +    AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.vol[1]); +    AUD_set_active_out(s->out.voice, 0); +} + +static const VMStateDescription vmstate_usb_audio = { +    .name = TYPE_USB_AUDIO, +    .unmigratable = 1, +}; + +static Property usb_audio_properties[] = { +    DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0), +    DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, +                       8 * USBAUDIO_PACKET_SIZE), +    DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_audio_class_init(ObjectClass *klass, void *data) +{ +    DeviceClass *dc = DEVICE_CLASS(klass); +    USBDeviceClass *k = USB_DEVICE_CLASS(klass); + +    dc->vmsd          = &vmstate_usb_audio; +    dc->props         = usb_audio_properties; +    set_bit(DEVICE_CATEGORY_SOUND, dc->categories); +    k->product_desc   = "QEMU USB Audio Interface"; +    k->usb_desc       = &desc_audio; +    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->handle_destroy = usb_audio_handle_destroy; +    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); +    usb_legacy_register(TYPE_USB_AUDIO, "audio", NULL); +} + +type_init(usb_audio_register_types) diff --git a/hw/usb/dev-bluetooth.c b/hw/usb/dev-bluetooth.c new file mode 100644 index 00000000..b19ec76b --- /dev/null +++ b/hw/usb/dev-bluetooth.c @@ -0,0 +1,579 @@ +/* + * QEMU Bluetooth HCI USB Transport Layer v1.0 + * + * Copyright (C) 2007 OpenMoko, Inc. + * Copyright (C) 2008 Andrzej Zaborowski  <balrog@zabor.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu-common.h" +#include "qemu/error-report.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "sysemu/bt.h" +#include "hw/bt.h" + +struct USBBtState { +    USBDevice dev; +    struct HCIInfo *hci; +    USBEndpoint *intr; + +    int config; + +#define CFIFO_LEN_MASK	255 +#define DFIFO_LEN_MASK	4095 +    struct usb_hci_in_fifo_s { +        uint8_t data[(DFIFO_LEN_MASK + 1) * 2]; +        struct { +            uint8_t *data; +            int len; +        } fifo[CFIFO_LEN_MASK + 1]; +        int dstart, dlen, dsize, start, len; +    } evt, acl, sco; + +    struct usb_hci_out_fifo_s { +        uint8_t data[4096]; +	int len; +    } outcmd, outacl, outsco; +}; + +#define TYPE_USB_BT "usb-bt-dongle" +#define USB_BT(obj) OBJECT_CHECK(struct USBBtState, (obj), TYPE_USB_BT) + +#define USB_EVT_EP	1 +#define USB_ACL_EP	2 +#define USB_SCO_EP	3 + +enum { +    STR_MANUFACTURER = 1, +    STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { +    [STR_MANUFACTURER]     = "QEMU", +    [STR_SERIALNUMBER]     = "1", +}; + +static const USBDescIface desc_iface_bluetooth[] = { +    { +        .bInterfaceNumber              = 0, +        .bNumEndpoints                 = 3, +        .bInterfaceClass               = 0xe0, /* Wireless */ +        .bInterfaceSubClass            = 0x01, /* Radio Frequency */ +        .bInterfaceProtocol            = 0x01, /* Bluetooth */ +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_IN | USB_EVT_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_INT, +                .wMaxPacketSize        = 0x10, +                .bInterval             = 0x02, +            }, +            { +                .bEndpointAddress      = USB_DIR_OUT | USB_ACL_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_BULK, +                .wMaxPacketSize        = 0x40, +                .bInterval             = 0x0a, +            }, +            { +                .bEndpointAddress      = USB_DIR_IN | USB_ACL_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_BULK, +                .wMaxPacketSize        = 0x40, +                .bInterval             = 0x0a, +            }, +        }, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 0, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = 0xe0, /* Wireless */ +        .bInterfaceSubClass            = 0x01, /* Radio Frequency */ +        .bInterfaceProtocol            = 0x01, /* Bluetooth */ +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_OUT | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0, +                .bInterval             = 0x01, +            }, +            { +                .bEndpointAddress      = USB_DIR_IN | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0, +                .bInterval             = 0x01, +            }, +        }, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 1, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = 0xe0, /* Wireless */ +        .bInterfaceSubClass            = 0x01, /* Radio Frequency */ +        .bInterfaceProtocol            = 0x01, /* Bluetooth */ +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_OUT | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x09, +                .bInterval             = 0x01, +            }, +            { +                .bEndpointAddress      = USB_DIR_IN | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x09, +                .bInterval             = 0x01, +            }, +        }, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 2, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = 0xe0, /* Wireless */ +        .bInterfaceSubClass            = 0x01, /* Radio Frequency */ +        .bInterfaceProtocol            = 0x01, /* Bluetooth */ +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_OUT | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x11, +                .bInterval             = 0x01, +            }, +            { +                .bEndpointAddress      = USB_DIR_IN | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x11, +                .bInterval             = 0x01, +            }, +        }, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 3, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = 0xe0, /* Wireless */ +        .bInterfaceSubClass            = 0x01, /* Radio Frequency */ +        .bInterfaceProtocol            = 0x01, /* Bluetooth */ +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_OUT | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x19, +                .bInterval             = 0x01, +            }, +            { +                .bEndpointAddress      = USB_DIR_IN | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x19, +                .bInterval             = 0x01, +            }, +        }, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 4, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = 0xe0, /* Wireless */ +        .bInterfaceSubClass            = 0x01, /* Radio Frequency */ +        .bInterfaceProtocol            = 0x01, /* Bluetooth */ +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_OUT | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x21, +                .bInterval             = 0x01, +            }, +            { +                .bEndpointAddress      = USB_DIR_IN | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x21, +                .bInterval             = 0x01, +            }, +        }, +    },{ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 5, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = 0xe0, /* Wireless */ +        .bInterfaceSubClass            = 0x01, /* Radio Frequency */ +        .bInterfaceProtocol            = 0x01, /* Bluetooth */ +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_OUT | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x31, +                .bInterval             = 0x01, +            }, +            { +                .bEndpointAddress      = USB_DIR_IN | USB_SCO_EP, +                .bmAttributes          = USB_ENDPOINT_XFER_ISOC, +                .wMaxPacketSize        = 0x31, +                .bInterval             = 0x01, +            }, +        }, +    } +}; + +static const USBDescDevice desc_device_bluetooth = { +    .bcdUSB                        = 0x0110, +    .bDeviceClass                  = 0xe0, /* Wireless */ +    .bDeviceSubClass               = 0x01, /* Radio Frequency */ +    .bDeviceProtocol               = 0x01, /* Bluetooth */ +    .bMaxPacketSize0               = 64, +    .bNumConfigurations            = 1, +    .confs = (USBDescConfig[]) { +        { +            .bNumInterfaces        = 2, +            .bConfigurationValue   = 1, +            .bmAttributes          = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, +            .bMaxPower             = 0, +            .nif = ARRAY_SIZE(desc_iface_bluetooth), +            .ifs = desc_iface_bluetooth, +        }, +    }, +}; + +static const USBDesc desc_bluetooth = { +    .id = { +        .idVendor          = 0x0a12, +        .idProduct         = 0x0001, +        .bcdDevice         = 0x1958, +        .iManufacturer     = STR_MANUFACTURER, +        .iProduct          = 0, +        .iSerialNumber     = STR_SERIALNUMBER, +    }, +    .full = &desc_device_bluetooth, +    .str  = desc_strings, +}; + +static void usb_bt_fifo_reset(struct usb_hci_in_fifo_s *fifo) +{ +    fifo->dstart = 0; +    fifo->dlen = 0; +    fifo->dsize = DFIFO_LEN_MASK + 1; +    fifo->start = 0; +    fifo->len = 0; +} + +static void usb_bt_fifo_enqueue(struct usb_hci_in_fifo_s *fifo, +                const uint8_t *data, int len) +{ +    int off = fifo->dstart + fifo->dlen; +    uint8_t *buf; + +    fifo->dlen += len; +    if (off <= DFIFO_LEN_MASK) { +        if (off + len > DFIFO_LEN_MASK + 1 && +                        (fifo->dsize = off + len) > (DFIFO_LEN_MASK + 1) * 2) { +            fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len); +            exit(-1); +        } +        buf = fifo->data + off; +    } else { +        if (fifo->dlen > fifo->dsize) { +            fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len); +            exit(-1); +        } +        buf = fifo->data + off - fifo->dsize; +    } + +    off = (fifo->start + fifo->len ++) & CFIFO_LEN_MASK; +    fifo->fifo[off].data = memcpy(buf, data, len); +    fifo->fifo[off].len = len; +} + +static inline void usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo, +                USBPacket *p) +{ +    int len; + +    assert(fifo->len != 0); + +    len = MIN(p->iov.size, fifo->fifo[fifo->start].len); +    usb_packet_copy(p, fifo->fifo[fifo->start].data, len); +    if (len == p->iov.size) { +        fifo->fifo[fifo->start].len -= len; +        fifo->fifo[fifo->start].data += len; +    } else { +        fifo->start ++; +        fifo->start &= CFIFO_LEN_MASK; +        fifo->len --; +    } + +    fifo->dstart += len; +    fifo->dlen -= len; +    if (fifo->dstart >= fifo->dsize) { +        fifo->dstart = 0; +        fifo->dsize = DFIFO_LEN_MASK + 1; +    } +} + +static inline void usb_bt_fifo_out_enqueue(struct USBBtState *s, +                struct usb_hci_out_fifo_s *fifo, +                void (*send)(struct HCIInfo *, const uint8_t *, int), +                int (*complete)(const uint8_t *, int), +                USBPacket *p) +{ +    usb_packet_copy(p, fifo->data + fifo->len, p->iov.size); +    fifo->len += p->iov.size; +    if (complete(fifo->data, fifo->len)) { +        send(s->hci, fifo->data, fifo->len); +        fifo->len = 0; +    } + +    /* TODO: do we need to loop? */ +} + +static int usb_bt_hci_cmd_complete(const uint8_t *data, int len) +{ +    len -= HCI_COMMAND_HDR_SIZE; +    return len >= 0 && +            len >= ((struct hci_command_hdr *) data)->plen; +} + +static int usb_bt_hci_acl_complete(const uint8_t *data, int len) +{ +    len -= HCI_ACL_HDR_SIZE; +    return len >= 0 && +            len >= le16_to_cpu(((struct hci_acl_hdr *) data)->dlen); +} + +static int usb_bt_hci_sco_complete(const uint8_t *data, int len) +{ +    len -= HCI_SCO_HDR_SIZE; +    return len >= 0 && +            len >= ((struct hci_sco_hdr *) data)->dlen; +} + +static void usb_bt_handle_reset(USBDevice *dev) +{ +    struct USBBtState *s = (struct USBBtState *) dev->opaque; + +    usb_bt_fifo_reset(&s->evt); +    usb_bt_fifo_reset(&s->acl); +    usb_bt_fifo_reset(&s->sco); +    s->outcmd.len = 0; +    s->outacl.len = 0; +    s->outsco.len = 0; +} + +static void usb_bt_handle_control(USBDevice *dev, USBPacket *p, +               int request, int value, int index, int length, uint8_t *data) +{ +    struct USBBtState *s = (struct USBBtState *) dev->opaque; +    int ret; + +    ret = usb_desc_handle_control(dev, p, request, value, index, length, data); +    if (ret >= 0) { +        switch (request) { +        case DeviceRequest | USB_REQ_GET_CONFIGURATION: +            s->config = 0; +            break; +        case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: +            s->config = 1; +            usb_bt_fifo_reset(&s->evt); +            usb_bt_fifo_reset(&s->acl); +            usb_bt_fifo_reset(&s->sco); +            break; +        } +        return; +    } + +    switch (request) { +    case InterfaceRequest | USB_REQ_GET_STATUS: +    case EndpointRequest | USB_REQ_GET_STATUS: +        data[0] = 0x00; +        data[1] = 0x00; +        p->actual_length = 2; +        break; +    case InterfaceOutRequest | USB_REQ_CLEAR_FEATURE: +    case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: +        goto fail; +    case InterfaceOutRequest | USB_REQ_SET_FEATURE: +    case EndpointOutRequest | USB_REQ_SET_FEATURE: +        goto fail; +        break; +    case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8): +        if (s->config) +            usb_bt_fifo_out_enqueue(s, &s->outcmd, s->hci->cmd_send, +                            usb_bt_hci_cmd_complete, p); +        break; +    default: +    fail: +        p->status = USB_RET_STALL; +        break; +    } +} + +static void usb_bt_handle_data(USBDevice *dev, USBPacket *p) +{ +    struct USBBtState *s = (struct USBBtState *) dev->opaque; + +    if (!s->config) +        goto fail; + +    switch (p->pid) { +    case USB_TOKEN_IN: +        switch (p->ep->nr) { +        case USB_EVT_EP: +            if (s->evt.len == 0) { +                p->status = USB_RET_NAK; +                break; +            } +            usb_bt_fifo_dequeue(&s->evt, p); +            break; + +        case USB_ACL_EP: +            if (s->evt.len == 0) { +                p->status = USB_RET_STALL; +                break; +            } +            usb_bt_fifo_dequeue(&s->acl, p); +            break; + +        case USB_SCO_EP: +            if (s->evt.len == 0) { +                p->status = USB_RET_STALL; +                break; +            } +            usb_bt_fifo_dequeue(&s->sco, p); +            break; + +        default: +            goto fail; +        } +        break; + +    case USB_TOKEN_OUT: +        switch (p->ep->nr) { +        case USB_ACL_EP: +            usb_bt_fifo_out_enqueue(s, &s->outacl, s->hci->acl_send, +                            usb_bt_hci_acl_complete, p); +            break; + +        case USB_SCO_EP: +            usb_bt_fifo_out_enqueue(s, &s->outsco, s->hci->sco_send, +                            usb_bt_hci_sco_complete, p); +            break; + +        default: +            goto fail; +        } +        break; + +    default: +    fail: +        p->status = USB_RET_STALL; +        break; +    } +} + +static void usb_bt_out_hci_packet_event(void *opaque, +                const uint8_t *data, int len) +{ +    struct USBBtState *s = (struct USBBtState *) opaque; + +    if (s->evt.len == 0) { +        usb_wakeup(s->intr, 0); +    } +    usb_bt_fifo_enqueue(&s->evt, data, len); +} + +static void usb_bt_out_hci_packet_acl(void *opaque, +                const uint8_t *data, int len) +{ +    struct USBBtState *s = (struct USBBtState *) opaque; + +    usb_bt_fifo_enqueue(&s->acl, data, len); +} + +static void usb_bt_handle_destroy(USBDevice *dev) +{ +    struct USBBtState *s = (struct USBBtState *) dev->opaque; + +    s->hci->opaque = NULL; +    s->hci->evt_recv = NULL; +    s->hci->acl_recv = NULL; +} + +static void usb_bt_realize(USBDevice *dev, Error **errp) +{ +    struct USBBtState *s = USB_BT(dev); + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); +    s->dev.opaque = s; +    if (!s->hci) { +        s->hci = bt_new_hci(qemu_find_bt_vlan(0)); +    } +    s->hci->opaque = s; +    s->hci->evt_recv = usb_bt_out_hci_packet_event; +    s->hci->acl_recv = usb_bt_out_hci_packet_acl; +    usb_bt_handle_reset(&s->dev); +    s->intr = usb_ep_get(dev, USB_TOKEN_IN, USB_EVT_EP); +} + +static USBDevice *usb_bt_init(USBBus *bus, const char *cmdline) +{ +    USBDevice *dev; +    struct USBBtState *s; +    HCIInfo *hci; +    const char *name = TYPE_USB_BT; + +    if (*cmdline) { +        hci = hci_init(cmdline); +    } else { +        hci = bt_new_hci(qemu_find_bt_vlan(0)); +    } +    if (!hci) +        return NULL; + +    dev = usb_create(bus, name); +    s = USB_BT(dev); +    s->hci = hci; +    return dev; +} + +static const VMStateDescription vmstate_usb_bt = { +    .name = "usb-bt", +    .unmigratable = 1, +}; + +static void usb_bt_class_initfn(ObjectClass *klass, void *data) +{ +    DeviceClass *dc = DEVICE_CLASS(klass); +    USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + +    uc->realize        = usb_bt_realize; +    uc->product_desc   = "QEMU BT dongle"; +    uc->usb_desc       = &desc_bluetooth; +    uc->handle_reset   = usb_bt_handle_reset; +    uc->handle_control = usb_bt_handle_control; +    uc->handle_data    = usb_bt_handle_data; +    uc->handle_destroy = usb_bt_handle_destroy; +    dc->vmsd = &vmstate_usb_bt; +    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); +} + +static const TypeInfo bt_info = { +    .name          = TYPE_USB_BT, +    .parent        = TYPE_USB_DEVICE, +    .instance_size = sizeof(struct USBBtState), +    .class_init    = usb_bt_class_initfn, +}; + +static void usb_bt_register_types(void) +{ +    type_register_static(&bt_info); +    usb_legacy_register(TYPE_USB_BT, "bt", usb_bt_init); +} + +type_init(usb_bt_register_types) diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c new file mode 100644 index 00000000..2e7dcd96 --- /dev/null +++ b/hw/usb/dev-hid.c @@ -0,0 +1,881 @@ +/* + * QEMU USB HID devices + * + * Copyright (c) 2005 Fabrice Bellard + * Copyright (c) 2007 OpenMoko, Inc.  (andrew@openedhand.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "qemu/timer.h" +#include "hw/input/hid.h" + +/* HID interface requests */ +#define GET_REPORT   0xa101 +#define GET_IDLE     0xa102 +#define GET_PROTOCOL 0xa103 +#define SET_REPORT   0x2109 +#define SET_IDLE     0x210a +#define SET_PROTOCOL 0x210b + +/* HID descriptor types */ +#define USB_DT_HID    0x21 +#define USB_DT_REPORT 0x22 +#define USB_DT_PHY    0x23 + +typedef struct USBHIDState { +    USBDevice dev; +    USBEndpoint *intr; +    HIDState hid; +    uint32_t usb_version; +    char *display; +    uint32_t head; +} USBHIDState; + +#define TYPE_USB_HID "usb-hid" +#define USB_HID(obj) OBJECT_CHECK(USBHIDState, (obj), TYPE_USB_HID) + +enum { +    STR_MANUFACTURER = 1, +    STR_PRODUCT_MOUSE, +    STR_PRODUCT_TABLET, +    STR_PRODUCT_KEYBOARD, +    STR_SERIALNUMBER, +    STR_CONFIG_MOUSE, +    STR_CONFIG_TABLET, +    STR_CONFIG_KEYBOARD, +}; + +static const USBDescStrings desc_strings = { +    [STR_MANUFACTURER]     = "QEMU", +    [STR_PRODUCT_MOUSE]    = "QEMU USB Mouse", +    [STR_PRODUCT_TABLET]   = "QEMU USB Tablet", +    [STR_PRODUCT_KEYBOARD] = "QEMU USB Keyboard", +    [STR_SERIALNUMBER]     = "42", /* == remote wakeup works */ +    [STR_CONFIG_MOUSE]     = "HID Mouse", +    [STR_CONFIG_TABLET]    = "HID Tablet", +    [STR_CONFIG_KEYBOARD]  = "HID Keyboard", +}; + +static const USBDescIface desc_iface_mouse = { +    .bInterfaceNumber              = 0, +    .bNumEndpoints                 = 1, +    .bInterfaceClass               = USB_CLASS_HID, +    .bInterfaceSubClass            = 0x01, /* boot */ +    .bInterfaceProtocol            = 0x02, +    .ndesc                         = 1, +    .descs = (USBDescOther[]) { +        { +            /* HID descriptor */ +            .data = (uint8_t[]) { +                0x09,          /*  u8  bLength */ +                USB_DT_HID,    /*  u8  bDescriptorType */ +                0x01, 0x00,    /*  u16 HID_class */ +                0x00,          /*  u8  country_code */ +                0x01,          /*  u8  num_descriptors */ +                USB_DT_REPORT, /*  u8  type: Report */ +                52, 0,         /*  u16 len */ +            }, +        }, +    }, +    .eps = (USBDescEndpoint[]) { +        { +            .bEndpointAddress      = USB_DIR_IN | 0x01, +            .bmAttributes          = USB_ENDPOINT_XFER_INT, +            .wMaxPacketSize        = 4, +            .bInterval             = 0x0a, +        }, +    }, +}; + +static const USBDescIface desc_iface_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            = 0x02, +    .ndesc                         = 1, +    .descs = (USBDescOther[]) { +        { +            /* HID descriptor */ +            .data = (uint8_t[]) { +                0x09,          /*  u8  bLength */ +                USB_DT_HID,    /*  u8  bDescriptorType */ +                0x01, 0x00,    /*  u16 HID_class */ +                0x00,          /*  u8  country_code */ +                0x01,          /*  u8  num_descriptors */ +                USB_DT_REPORT, /*  u8  type: Report */ +                74, 0,         /*  u16 len */ +            }, +        }, +    }, +    .eps = (USBDescEndpoint[]) { +        { +            .bEndpointAddress      = USB_DIR_IN | 0x01, +            .bmAttributes          = USB_ENDPOINT_XFER_INT, +            .wMaxPacketSize        = 8, +            .bInterval             = 0x0a, +        }, +    }, +}; + +static const USBDescIface desc_iface_tablet2 = { +    .bInterfaceNumber              = 0, +    .bNumEndpoints                 = 1, +    .bInterfaceClass               = USB_CLASS_HID, +    .bInterfaceProtocol            = 0x02, +    .ndesc                         = 1, +    .descs = (USBDescOther[]) { +        { +            /* HID descriptor */ +            .data = (uint8_t[]) { +                0x09,          /*  u8  bLength */ +                USB_DT_HID,    /*  u8  bDescriptorType */ +                0x01, 0x00,    /*  u16 HID_class */ +                0x00,          /*  u8  country_code */ +                0x01,          /*  u8  num_descriptors */ +                USB_DT_REPORT, /*  u8  type: Report */ +                74, 0,         /*  u16 len */ +            }, +        }, +    }, +    .eps = (USBDescEndpoint[]) { +        { +            .bEndpointAddress      = USB_DIR_IN | 0x01, +            .bmAttributes          = USB_ENDPOINT_XFER_INT, +            .wMaxPacketSize        = 8, +            .bInterval             = 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_SERIALNUMBER, +    }, +    .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_SERIALNUMBER, +    }, +    .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_SERIALNUMBER, +    }, +    .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_SERIALNUMBER, +    }, +    .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_SERIALNUMBER, +    }, +    .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_SERIALNUMBER, +    }, +    .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, 0x01,		/* Usage (Pointer) */ +    0xa1, 0x01,		/* Collection (Application) */ +    0x09, 0x01,		/*   Usage (Pointer) */ +    0xa1, 0x00,		/*   Collection (Physical) */ +    0x05, 0x09,		/*     Usage Page (Button) */ +    0x19, 0x01,		/*     Usage Minimum (1) */ +    0x29, 0x03,		/*     Usage Maximum (3) */ +    0x15, 0x00,		/*     Logical Minimum (0) */ +    0x25, 0x01,		/*     Logical Maximum (1) */ +    0x95, 0x03,		/*     Report Count (3) */ +    0x75, 0x01,		/*     Report Size (1) */ +    0x81, 0x02,		/*     Input (Data, Variable, Absolute) */ +    0x95, 0x01,		/*     Report Count (1) */ +    0x75, 0x05,		/*     Report Size (5) */ +    0x81, 0x01,		/*     Input (Constant) */ +    0x05, 0x01,		/*     Usage Page (Generic Desktop) */ +    0x09, 0x30,		/*     Usage (X) */ +    0x09, 0x31,		/*     Usage (Y) */ +    0x15, 0x00,		/*     Logical Minimum (0) */ +    0x26, 0xff, 0x7f,	/*     Logical Maximum (0x7fff) */ +    0x35, 0x00,		/*     Physical Minimum (0) */ +    0x46, 0xff, 0x7f,	/*     Physical Maximum (0x7fff) */ +    0x75, 0x10,		/*     Report Size (16) */ +    0x95, 0x02,		/*     Report Count (2) */ +    0x81, 0x02,		/*     Input (Data, Variable, Absolute) */ +    0x05, 0x01,		/*     Usage Page (Generic Desktop) */ +    0x09, 0x38,		/*     Usage (Wheel) */ +    0x15, 0x81,		/*     Logical Minimum (-0x7f) */ +    0x25, 0x7f,		/*     Logical Maximum (0x7f) */ +    0x35, 0x00,		/*     Physical Minimum (same as logical) */ +    0x45, 0x00,		/*     Physical Maximum (same as logical) */ +    0x75, 0x08,		/*     Report Size (8) */ +    0x95, 0x01,		/*     Report Count (1) */ +    0x81, 0x06,		/*     Input (Data, Variable, Relative) */ +    0xc0,		/*   End Collection */ +    0xc0,		/* End Collection */ +}; + +static const uint8_t qemu_keyboard_hid_report_descriptor[] = { +    0x05, 0x01,		/* Usage Page (Generic Desktop) */ +    0x09, 0x06,		/* Usage (Keyboard) */ +    0xa1, 0x01,		/* Collection (Application) */ +    0x75, 0x01,		/*   Report Size (1) */ +    0x95, 0x08,		/*   Report Count (8) */ +    0x05, 0x07,		/*   Usage Page (Key Codes) */ +    0x19, 0xe0,		/*   Usage Minimum (224) */ +    0x29, 0xe7,		/*   Usage Maximum (231) */ +    0x15, 0x00,		/*   Logical Minimum (0) */ +    0x25, 0x01,		/*   Logical Maximum (1) */ +    0x81, 0x02,		/*   Input (Data, Variable, Absolute) */ +    0x95, 0x01,		/*   Report Count (1) */ +    0x75, 0x08,		/*   Report Size (8) */ +    0x81, 0x01,		/*   Input (Constant) */ +    0x95, 0x05,		/*   Report Count (5) */ +    0x75, 0x01,		/*   Report Size (1) */ +    0x05, 0x08,		/*   Usage Page (LEDs) */ +    0x19, 0x01,		/*   Usage Minimum (1) */ +    0x29, 0x05,		/*   Usage Maximum (5) */ +    0x91, 0x02,		/*   Output (Data, Variable, Absolute) */ +    0x95, 0x01,		/*   Report Count (1) */ +    0x75, 0x03,		/*   Report Size (3) */ +    0x91, 0x01,		/*   Output (Constant) */ +    0x95, 0x06,		/*   Report Count (6) */ +    0x75, 0x08,		/*   Report Size (8) */ +    0x15, 0x00,		/*   Logical Minimum (0) */ +    0x25, 0xff,		/*   Logical Maximum (255) */ +    0x05, 0x07,		/*   Usage Page (Key Codes) */ +    0x19, 0x00,		/*   Usage Minimum (0) */ +    0x29, 0xff,		/*   Usage Maximum (255) */ +    0x81, 0x00,		/*   Input (Data, Array) */ +    0xc0,		/* End Collection */ +}; + +static void usb_hid_changed(HIDState *hs) +{ +    USBHIDState *us = container_of(hs, USBHIDState, hid); + +    usb_wakeup(us->intr, 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 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 SET_REPORT: +        if (hs->kind == HID_KEYBOARD) { +            p->actual_length = hid_keyboard_write(hs, data, length); +        } else { +            goto fail; +        } +        break; +    case GET_PROTOCOL: +        if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { +            goto fail; +        } +        data[0] = hs->protocol; +        p->actual_length = 1; +        break; +    case SET_PROTOCOL: +        if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { +            goto fail; +        } +        hs->protocol = value; +        break; +    case GET_IDLE: +        data[0] = hs->idle; +        p->actual_length = 1; +        break; +    case 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; +    uint8_t buf[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_handle_destroy(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; +    } + +    if (dev->serial) { +        usb_desc_set_string(dev, STR_SERIALNUMBER, dev->serial); +    } +    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->handle_destroy = usb_hid_handle_destroy; +    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; +    dc->props = 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; +    dc->props = 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; +    dc->props = 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 00000000..c8c68555 --- /dev/null +++ b/hw/usb/dev-hub.c @@ -0,0 +1,594 @@ +/* + * QEMU USB HUB emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "trace.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "qemu/error-report.h" + +#define NUM_PORTS 8 + +typedef struct USBHubPort { +    USBPort port; +    uint16_t wPortStatus; +    uint16_t wPortChange; +} USBHubPort; + +typedef struct USBHubState { +    USBDevice dev; +    USBEndpoint *intr; +    USBHubPort ports[NUM_PORTS]; +} USBHubState; + +#define TYPE_USB_HUB "usb-hub" +#define USB_HUB(obj) OBJECT_CHECK(USBHubState, (obj), TYPE_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 + (NUM_PORTS + 7) / 8, +            .bInterval             = 0xff, +        }, +    } +}; + +static const USBDescDevice desc_device_hub = { +    .bcdUSB                        = 0x0110, +    .bDeviceClass                  = USB_CLASS_HUB, +    .bMaxPacketSize0               = 8, +    .bNumConfigurations            = 1, +    .confs = (USBDescConfig[]) { +        { +            .bNumInterfaces        = 1, +            .bConfigurationValue   = 1, +            .bmAttributes          = 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 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); +    port->wPortStatus |= PORT_STAT_CONNECTION; +    port->wPortChange |= PORT_STAT_C_CONNECTION; +    if (port->port.dev->speed == USB_SPEED_LOW) { +        port->wPortStatus |= PORT_STAT_LOW_SPEED; +    } else { +        port->wPortStatus &= ~PORT_STAT_LOW_SPEED; +    } +    usb_wakeup(s->intr, 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); + +    port->wPortStatus &= ~PORT_STAT_CONNECTION; +    port->wPortChange |= PORT_STAT_C_CONNECTION; +    if (port->wPortStatus & PORT_STAT_ENABLE) { +        port->wPortStatus &= ~PORT_STAT_ENABLE; +        port->wPortChange |= PORT_STAT_C_ENABLE; +    } +    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 (port->wPortStatus & PORT_STAT_SUSPEND) { +        port->wPortChange |= PORT_STAT_C_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 < 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 < NUM_PORTS; i++) { +        port = s->ports + i; +        port->wPortStatus = PORT_STAT_POWER; +        port->wPortChange = 0; +        if (port->port.dev && port->port.dev->attached) { +            port->wPortStatus |= PORT_STAT_CONNECTION; +            port->wPortChange |= PORT_STAT_C_CONNECTION; +            if (port->port.dev->speed == USB_SPEED_LOW) { +                port->wPortStatus |= PORT_STAT_LOW_SPEED; +            } +        } +    } +} + +static 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 >= 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 >= NUM_PORTS) { +                goto fail; +            } +            port = &s->ports[n]; +            dev = port->port.dev; +            switch(value) { +            case PORT_SUSPEND: +                port->wPortStatus |= PORT_STAT_SUSPEND; +                break; +            case PORT_RESET: +                if (dev && dev->attached) { +                    usb_device_reset(dev); +                    port->wPortChange |= PORT_STAT_C_RESET; +                    /* set enable bit */ +                    port->wPortStatus |= PORT_STAT_ENABLE; +                    usb_wakeup(s->intr, 0); +                } +                break; +            case PORT_POWER: +                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 >= NUM_PORTS) { +                goto fail; +            } +            port = &s->ports[n]; +            switch(value) { +            case PORT_ENABLE: +                port->wPortStatus &= ~PORT_STAT_ENABLE; +                break; +            case PORT_C_ENABLE: +                port->wPortChange &= ~PORT_STAT_C_ENABLE; +                break; +            case PORT_SUSPEND: +                port->wPortStatus &= ~PORT_STAT_SUSPEND; +                break; +            case PORT_C_SUSPEND: +                port->wPortChange &= ~PORT_STAT_C_SUSPEND; +                break; +            case PORT_C_CONNECTION: +                port->wPortChange &= ~PORT_STAT_C_CONNECTION; +                break; +            case PORT_C_OVERCURRENT: +                port->wPortChange &= ~PORT_STAT_C_OVERCURRENT; +                break; +            case PORT_C_RESET: +                port->wPortChange &= ~PORT_STAT_C_RESET; +                break; +            default: +                goto fail; +            } +        } +        break; +    case GetHubDescriptor: +        { +            unsigned int n, limit, var_hub_size = 0; +            memcpy(data, qemu_hub_hub_descriptor, +                   sizeof(qemu_hub_hub_descriptor)); +            data[2] = NUM_PORTS; + +            /* fill DeviceRemovable bits */ +            limit = ((NUM_PORTS + 1 + 7) / 8) + 7; +            for (n = 7; n < limit; n++) { +                data[n] = 0x00; +                var_hub_size++; +            } + +            /* fill PortPwrCtrlMask bits */ +            limit = limit + ((NUM_PORTS + 7) / 8); +            for (;n < limit; n++) { +                data[n] = 0xff; +                var_hub_size++; +            } + +            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 = (NUM_PORTS + 1 + 7) / 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 < 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_handle_destroy(USBDevice *dev) +{ +    USBHubState *s = (USBHubState *)dev; +    int i; + +    for (i = 0; i < NUM_PORTS; i++) { +        usb_unregister_port(usb_bus_from_device(dev), +                            &s->ports[i].port); +    } +} + +static USBPortOps usb_hub_port_ops = { +    .attach = usb_hub_attach, +    .detach = usb_hub_detach, +    .child_detach = usb_hub_child_detach, +    .wakeup = usb_hub_wakeup, +    .complete = usb_hub_complete, +}; + +static void usb_hub_realize(USBDevice *dev, Error **errp) +{ +    USBHubState *s = USB_HUB(dev); +    USBHubPort *port; +    int i; + +    if (dev->port->hubcount == 5) { +        error_setg(errp, "usb hub chain too deep"); +        return; +    } + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); +    s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); +    for (i = 0; i < NUM_PORTS; i++) { +        port = &s->ports[i]; +        usb_register_port(usb_bus_from_device(dev), +                          &port->port, s, i, &usb_hub_port_ops, +                          USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); +        usb_port_location(&port->port, dev->port, i+1); +    } +    usb_hub_handle_reset(dev); +} + +static const VMStateDescription vmstate_usb_hub_port = { +    .name = "usb-hub-port", +    .version_id = 1, +    .minimum_version_id = 1, +    .fields = (VMStateField[]) { +        VMSTATE_UINT16(wPortStatus, USBHubPort), +        VMSTATE_UINT16(wPortChange, USBHubPort), +        VMSTATE_END_OF_LIST() +    } +}; + +static const VMStateDescription vmstate_usb_hub = { +    .name = "usb-hub", +    .version_id = 1, +    .minimum_version_id = 1, +    .fields = (VMStateField[]) { +        VMSTATE_USB_DEVICE(dev, USBHubState), +        VMSTATE_STRUCT_ARRAY(ports, USBHubState, NUM_PORTS, 0, +                             vmstate_usb_hub_port, USBHubPort), +        VMSTATE_END_OF_LIST() +    } +}; + +static void usb_hub_class_initfn(ObjectClass *klass, void *data) +{ +    DeviceClass *dc = DEVICE_CLASS(klass); +    USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + +    uc->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->handle_destroy = usb_hub_handle_destroy; +    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); +    dc->fw_name = "hub"; +    dc->vmsd = &vmstate_usb_hub; +} + +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 00000000..809b1cb1 --- /dev/null +++ b/hw/usb/dev-mtp.c @@ -0,0 +1,1134 @@ +/* + * 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 <wchar.h> +#include <dirent.h> +#include <unistd.h> + +#include <sys/stat.h> +#include <sys/statvfs.h> + +#include "qemu-common.h" +#include "qemu/iov.h" +#include "trace.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" + +/* ----------------------------------------------------------------------- */ + +enum mtp_container_type { +    TYPE_COMMAND  = 1, +    TYPE_DATA     = 2, +    TYPE_RESPONSE = 3, +    TYPE_EVENT    = 4, +}; + +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_GET_PARTIAL_OBJECT         = 0x101b, + +    /* 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_SPEC_BY_FORMAT_UNSUPPORTED = 0x2014, +    RES_INVALID_PARENT_OBJECT      = 0x201a, +    RES_INVALID_PARAMETER          = 0x201d, +    RES_SESSION_ALREADY_OPEN       = 0x201e, + +    /* format codes */ +    FMT_UNDEFINED_OBJECT           = 0x3000, +    FMT_ASSOCIATION                = 0x3001, +}; + +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, +}; + +struct MTPControl { +    uint16_t     code; +    uint32_t     trans; +    int          argc; +    uint32_t     argv[5]; +}; + +struct MTPData { +    uint16_t     code; +    uint32_t     trans; +    uint32_t     offset; +    uint32_t     length; +    uint32_t     alloc; +    uint8_t      *data; +    bool         first; +    int          fd; +}; + +struct MTPObject { +    uint32_t     handle; +    uint16_t     format; +    char         *name; +    char         *path; +    struct stat  stat; +    MTPObject    *parent; +    MTPObject    **children; +    uint32_t     nchildren; +    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; + +    QTAILQ_HEAD(, MTPObject) objects; +}; + +#define TYPE_USB_MTP "usb-mtp" +#define USB_MTP(obj) OBJECT_CHECK(MTPState, (obj), TYPE_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" + +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        = 8, +            .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        = 8, +            .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, char *name) +{ +    MTPObject *o = g_new0(MTPObject, 1); + +    if (name[0] == '.') { +        goto ignore; +    } + +    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) +{ +    int i; + +    trace_usb_mtp_object_free(s->dev.addr, o->handle, o->path); + +    QTAILQ_REMOVE(&s->objects, o, next); +    for (i = 0; i < o->nchildren; i++) { +        usb_mtp_object_free(s, o->children[i]); +    } +    g_free(o->children); +    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 void usb_mtp_object_readdir(MTPState *s, MTPObject *o) +{ +    struct dirent *entry; +    DIR *dir; + +    if (o->have_children) { +        return; +    } +    o->have_children = true; + +    dir = opendir(o->path); +    if (!dir) { +        return; +    } +    while ((entry = readdir(dir)) != NULL) { +        if ((o->nchildren % 32) == 0) { +            o->children = g_realloc(o->children, +                                    (o->nchildren + 32) * sizeof(MTPObject *)); +        } +        o->children[o->nchildren] = +            usb_mtp_object_alloc(s, s->next_handle++, o, entry->d_name); +        if (o->children[o->nchildren] != NULL) { +            o->nchildren++; +        } +    } +    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[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); +    } +} + +static void usb_mtp_add_time(MTPData *data, time_t time) +{ +    char buf[16]; +    struct tm tm; + +    gmtime_r(&time, &tm); +    strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", &tm); +    usb_mtp_add_str(data, buf); +} + +/* ----------------------------------------------------------------------- */ + +static void usb_mtp_queue_result(MTPState *s, uint16_t code, uint32_t trans, +                                 int argc, uint32_t arg0, uint32_t arg1) +{ +    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; +    } + +    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_GET_OBJECT, +        CMD_GET_PARTIAL_OBJECT, +    }; +    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, 0xffffffff); +    usb_mtp_add_u16(d, 0x0101); +    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, handles[o->nchildren]; + +    trace_usb_mtp_op_get_object_handles(s->dev.addr, o->handle, o->path); + +    for (i = 0; i < o->nchildren; i++) { +        handles[i] = o->children[i]->handle; +    } +    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); +    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); +    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 = usb_mtp_data_alloc(c); +    off_t offset; + +    trace_usb_mtp_op_get_partial_object(s->dev.addr, o->handle, o->path, +                                        c->argv[1], c->argv[2]); + +    d->fd = open(o->path, O_RDONLY); +    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 void usb_mtp_command(MTPState *s, MTPControl *c) +{ +    MTPData *data_in = NULL; +    MTPObject *o; +    uint32_t nres = 0, res0 = 0; + +    /* 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); +        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); +            return; +        } +        if (c->argv[0] == 0) { +            usb_mtp_queue_result(s, RES_INVALID_PARAMETER, +                                 c->trans, 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); +        break; +    case CMD_CLOSE_SESSION: +        trace_usb_mtp_op_close_session(s->dev.addr); +        s->session = 0; +        s->next_handle = 0; +        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); +            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); +            return; +        } +        if (c->argv[1] != 0x00000000) { +            usb_mtp_queue_result(s, RES_SPEC_BY_FORMAT_UNSUPPORTED, +                                 c->trans, 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); +            return; +        } +        if (o->format != FMT_ASSOCIATION) { +            usb_mtp_queue_result(s, RES_INVALID_PARENT_OBJECT, +                                 c->trans, 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); +            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); +            return; +        } +        if (o->format == FMT_ASSOCIATION) { +            usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, +                                 c->trans, 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); +            return; +        } +        break; +    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); +            return; +        } +        if (o->format == FMT_ASSOCIATION) { +            usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, +                                 c->trans, 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); +            return; +        } +        nres = 1; +        res0 = data_in->length; +        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); +        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); +} + +/* ----------------------------------------------------------------------- */ + +static void usb_mtp_handle_reset(USBDevice *dev) +{ +    MTPState *s = USB_MTP(dev); + +    trace_usb_mtp_reset(s->dev.addr); + +    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; + +    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"); +    p->status = USB_RET_STALL; +} + +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 void usb_mtp_handle_data(USBDevice *dev, USBPacket *p) +{ +    MTPState *s = USB_MTP(dev); +    MTPControl cmd; +    mtp_container container; +    uint32_t params[5]; +    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; +            int dlen = d->length - d->offset; +            if (d->first) { +                trace_usb_mtp_data_in(s->dev.addr, d->trans, d->length); +                container.length = cpu_to_le32(d->length + sizeof(container)); +                container.type   = cpu_to_le16(TYPE_DATA); +                container.code   = cpu_to_le16(d->code); +                container.trans  = cpu_to_le32(d->trans); +                usb_packet_copy(p, &container, sizeof(container)); +                d->first = false; +                if (dlen > p->iov.size - sizeof(container)) { +                    dlen = p->iov.size - sizeof(container); +                } +            } else { +                if (dlen > p->iov.size) { +                    dlen = p->iov.size; +                } +            } +            if (d->fd == -1) { +                usb_packet_copy(p, d->data + d->offset, dlen); +            } else { +                if (d->alloc < p->iov.size) { +                    d->alloc = p->iov.size; +                    d->data = g_realloc(d->data, d->alloc); +                } +                rc = read(d->fd, d->data, dlen); +                if (rc != dlen) { +                    memset(d->data, 0, dlen); +                    s->result->code = RES_INCOMPLETE_TRANSFER; +                } +                usb_packet_copy(p, d->data, dlen); +            } +            d->offset += dlen; +            if (d->offset == d->length) { +                usb_mtp_data_free(s->data_in); +                s->data_in = NULL; +            } +        } else if (s->result != NULL) { +            MTPControl *r = s->result; +            int length = sizeof(container) + r->argc * sizeof(uint32_t); +            if (r->code == RES_OK) { +                trace_usb_mtp_success(s->dev.addr, r->trans, +                                      (r->argc > 0) ? r->argv[0] : 0, +                                      (r->argc > 1) ? r->argv[1] : 0); +            } else { +                trace_usb_mtp_error(s->dev.addr, r->code, r->trans, +                                    (r->argc > 0) ? r->argv[0] : 0, +                                    (r->argc > 1) ? r->argv[1] : 0); +            } +            container.length = cpu_to_le32(length); +            container.type   = cpu_to_le16(TYPE_RESPONSE); +            container.code   = cpu_to_le16(r->code); +            container.trans  = cpu_to_le32(r->trans); +            for (i = 0; i < r->argc; i++) { +                params[i] = cpu_to_le32(r->argv[i]); +            } +            usb_packet_copy(p, &container, sizeof(container)); +            usb_packet_copy(p, ¶ms, length - sizeof(container)); +            g_free(s->result); +            s->result = NULL; +        } +        break; +    case EP_DATA_OUT: +        if (p->iov.size < sizeof(container)) { +            trace_usb_mtp_stall(s->dev.addr, "packet too small"); +            p->status = USB_RET_STALL; +            return; +        } +        usb_packet_copy(p, &container, sizeof(container)); +        switch (le16_to_cpu(container.type)) { +        case TYPE_COMMAND: +            if (s->data_in || s->data_out || s->result) { +                trace_usb_mtp_stall(s->dev.addr, "transaction inflight"); +                p->status = USB_RET_STALL; +                return; +            } +            cmd.code = le16_to_cpu(container.code); +            cmd.argc = (le32_to_cpu(container.length) - sizeof(container)) +                / sizeof(uint32_t); +            cmd.trans = le32_to_cpu(container.trans); +            if (cmd.argc > ARRAY_SIZE(cmd.argv)) { +                cmd.argc = ARRAY_SIZE(cmd.argv); +            } +            if (p->iov.size < sizeof(container) + cmd.argc * sizeof(uint32_t)) { +                trace_usb_mtp_stall(s->dev.addr, "packet too small"); +                p->status = USB_RET_STALL; +                return; +            } +            usb_packet_copy(p, ¶ms, cmd.argc * sizeof(uint32_t)); +            for (i = 0; i < cmd.argc; i++) { +                cmd.argv[i] = le32_to_cpu(params[i]); +            } +            trace_usb_mtp_command(s->dev.addr, cmd.code, cmd.trans, +                                  (cmd.argc > 0) ? cmd.argv[0] : 0, +                                  (cmd.argc > 1) ? cmd.argv[1] : 0, +                                  (cmd.argc > 2) ? cmd.argv[2] : 0, +                                  (cmd.argc > 3) ? cmd.argv[3] : 0, +                                  (cmd.argc > 4) ? cmd.argv[4] : 0); +            usb_mtp_command(s, &cmd); +            break; +        default: +            /* not needed as long as the mtp device is read-only */ +            p->status = USB_RET_STALL; +            return; +        } +        break; +    case EP_EVENT: +        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); + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); +    QTAILQ_INIT(&s->objects); +    if (s->desc == NULL) { +        if (s->root == NULL) { +            error_setg(errp, "usb-mtp: x-root property must be configured"); +            return; +        } +        s->desc = strrchr(s->root, '/'); +        if (s->desc && s->desc[0]) { +            s->desc = g_strdup(s->desc + 1); +        } else { +            s->desc = g_strdup("none"); +        } +    } +} + +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("x-root", MTPState, root), +    DEFINE_PROP_STRING("desc", MTPState, desc), +    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; +    dc->fw_name = "mtp"; +    dc->vmsd = &vmstate_usb_mtp; +    dc->props = 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 00000000..7800ceea --- /dev/null +++ b/hw/usb/dev-network.c @@ -0,0 +1,1448 @@ +/* + * QEMU USB Net devices + * + * Copyright (c) 2006 Thomas Sailer + * Copyright (c) 2008 Andrzej Zaborowski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "net/net.h" +#include "qemu/error-report.h" +#include "qemu/queue.h" +#include "qemu/config-file.h" +#include "sysemu/sysemu.h" +#include "qemu/iov.h" + +/*#define TRAFFIC_DEBUG*/ +/* Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only CDC Ethernet configurations. + */ +#define CDC_VENDOR_NUM          0x0525  /* NetChip */ +#define CDC_PRODUCT_NUM         0xa4a1  /* Linux-USB Ethernet Gadget */ +/* For hardware that can talk RNDIS and either of the above protocols, + * use this ID ... the windows INF files will know it. + */ +#define RNDIS_VENDOR_NUM        0x0525  /* NetChip */ +#define RNDIS_PRODUCT_NUM       0xa4a2  /* Ethernet/RNDIS Gadget */ + +enum usbstring_idx { +    STRING_MANUFACTURER		= 1, +    STRING_PRODUCT, +    STRING_ETHADDR, +    STRING_DATA, +    STRING_CONTROL, +    STRING_RNDIS_CONTROL, +    STRING_CDC, +    STRING_SUBSET, +    STRING_RNDIS, +    STRING_SERIALNUMBER, +}; + +#define DEV_CONFIG_VALUE		1	/* CDC or a subset */ +#define DEV_RNDIS_CONFIG_VALUE		2	/* RNDIS; optional */ + +#define USB_CDC_SUBCLASS_ACM		0x02 +#define USB_CDC_SUBCLASS_ETHERNET	0x06 + +#define USB_CDC_PROTO_NONE		0 +#define USB_CDC_ACM_PROTO_VENDOR	0xff + +#define USB_CDC_HEADER_TYPE		0x00	/* header_desc */ +#define USB_CDC_CALL_MANAGEMENT_TYPE	0x01	/* call_mgmt_descriptor */ +#define USB_CDC_ACM_TYPE		0x02	/* acm_descriptor */ +#define USB_CDC_UNION_TYPE		0x06	/* union_desc */ +#define USB_CDC_ETHERNET_TYPE		0x0f	/* ether_desc */ + +#define USB_CDC_SEND_ENCAPSULATED_COMMAND	0x00 +#define USB_CDC_GET_ENCAPSULATED_RESPONSE	0x01 +#define USB_CDC_REQ_SET_LINE_CODING		0x20 +#define USB_CDC_REQ_GET_LINE_CODING		0x21 +#define USB_CDC_REQ_SET_CONTROL_LINE_STATE	0x22 +#define USB_CDC_REQ_SEND_BREAK			0x23 +#define USB_CDC_SET_ETHERNET_MULTICAST_FILTERS	0x40 +#define USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER	0x41 +#define USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER	0x42 +#define USB_CDC_SET_ETHERNET_PACKET_FILTER	0x43 +#define USB_CDC_GET_ETHERNET_STATISTIC		0x44 + +#define LOG2_STATUS_INTERVAL_MSEC	5    /* 1 << 5 == 32 msec */ +#define STATUS_BYTECOUNT		16   /* 8 byte header + data */ + +#define ETH_FRAME_LEN			1514 /* Max. octets in frame sans FCS */ + +static const USBDescStrings usb_net_stringtable = { +    [STRING_MANUFACTURER]       = "QEMU", +    [STRING_PRODUCT]            = "RNDIS/QEMU USB Network Device", +    [STRING_ETHADDR]            = "400102030405", +    [STRING_DATA]               = "QEMU USB Net Data Interface", +    [STRING_CONTROL]            = "QEMU USB Net Control Interface", +    [STRING_RNDIS_CONTROL]      = "QEMU USB Net RNDIS Control Interface", +    [STRING_CDC]                = "QEMU USB Net CDC", +    [STRING_SUBSET]             = "QEMU USB Net Subset", +    [STRING_RNDIS]              = "QEMU USB Net RNDIS", +    [STRING_SERIALNUMBER]       = "1", +}; + +static const USBDescIface desc_iface_rndis[] = { +    { +        /* RNDIS Control Interface */ +        .bInterfaceNumber              = 0, +        .bNumEndpoints                 = 1, +        .bInterfaceClass               = USB_CLASS_COMM, +        .bInterfaceSubClass            = USB_CDC_SUBCLASS_ACM, +        .bInterfaceProtocol            = USB_CDC_ACM_PROTO_VENDOR, +        .iInterface                    = STRING_RNDIS_CONTROL, +        .ndesc                         = 4, +        .descs = (USBDescOther[]) { +            { +                /* Header Descriptor */ +                .data = (uint8_t[]) { +                    0x05,                       /*  u8    bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8    bDescriptorType */ +                    USB_CDC_HEADER_TYPE,        /*  u8    bDescriptorSubType */ +                    0x10, 0x01,                 /*  le16  bcdCDC */ +                }, +            },{ +                /* Call Management Descriptor */ +                .data = (uint8_t[]) { +                    0x05,                       /*  u8    bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8    bDescriptorType */ +                    USB_CDC_CALL_MANAGEMENT_TYPE, /*  u8    bDescriptorSubType */ +                    0x00,                       /*  u8    bmCapabilities */ +                    0x01,                       /*  u8    bDataInterface */ +                }, +            },{ +                /* ACM Descriptor */ +                .data = (uint8_t[]) { +                    0x04,                       /*  u8    bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8    bDescriptorType */ +                    USB_CDC_ACM_TYPE,           /*  u8    bDescriptorSubType */ +                    0x00,                       /*  u8    bmCapabilities */ +                }, +            },{ +                /* Union Descriptor */ +                .data = (uint8_t[]) { +                    0x05,                       /*  u8    bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8    bDescriptorType */ +                    USB_CDC_UNION_TYPE,         /*  u8    bDescriptorSubType */ +                    0x00,                       /*  u8    bMasterInterface0 */ +                    0x01,                       /*  u8    bSlaveInterface0 */ +                }, +            }, +        }, +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_IN | 0x01, +                .bmAttributes          = USB_ENDPOINT_XFER_INT, +                .wMaxPacketSize        = STATUS_BYTECOUNT, +                .bInterval             = 1 << LOG2_STATUS_INTERVAL_MSEC, +            }, +        } +    },{ +        /* RNDIS Data Interface */ +        .bInterfaceNumber              = 1, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = USB_CLASS_CDC_DATA, +        .iInterface                    = STRING_DATA, +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_IN | 0x02, +                .bmAttributes          = USB_ENDPOINT_XFER_BULK, +                .wMaxPacketSize        = 0x40, +            },{ +                .bEndpointAddress      = USB_DIR_OUT | 0x02, +                .bmAttributes          = USB_ENDPOINT_XFER_BULK, +                .wMaxPacketSize        = 0x40, +            } +        } +    } +}; + +static const USBDescIface desc_iface_cdc[] = { +    { +        /* CDC Control Interface */ +        .bInterfaceNumber              = 0, +        .bNumEndpoints                 = 1, +        .bInterfaceClass               = USB_CLASS_COMM, +        .bInterfaceSubClass            = USB_CDC_SUBCLASS_ETHERNET, +        .bInterfaceProtocol            = USB_CDC_PROTO_NONE, +        .iInterface                    = STRING_CONTROL, +        .ndesc                         = 3, +        .descs = (USBDescOther[]) { +            { +                /* Header Descriptor */ +                .data = (uint8_t[]) { +                    0x05,                       /*  u8    bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8    bDescriptorType */ +                    USB_CDC_HEADER_TYPE,        /*  u8    bDescriptorSubType */ +                    0x10, 0x01,                 /*  le16  bcdCDC */ +                }, +            },{ +                /* Union Descriptor */ +                .data = (uint8_t[]) { +                    0x05,                       /*  u8    bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8    bDescriptorType */ +                    USB_CDC_UNION_TYPE,         /*  u8    bDescriptorSubType */ +                    0x00,                       /*  u8    bMasterInterface0 */ +                    0x01,                       /*  u8    bSlaveInterface0 */ +                }, +            },{ +                /* Ethernet Descriptor */ +                .data = (uint8_t[]) { +                    0x0d,                       /*  u8    bLength */ +                    USB_DT_CS_INTERFACE,        /*  u8    bDescriptorType */ +                    USB_CDC_ETHERNET_TYPE,      /*  u8    bDescriptorSubType */ +                    STRING_ETHADDR,             /*  u8    iMACAddress */ +                    0x00, 0x00, 0x00, 0x00,     /*  le32  bmEthernetStatistics */ +                    ETH_FRAME_LEN & 0xff, +                    ETH_FRAME_LEN >> 8,         /*  le16  wMaxSegmentSize */ +                    0x00, 0x00,                 /*  le16  wNumberMCFilters */ +                    0x00,                       /*  u8    bNumberPowerFilters */ +                }, +            }, +        }, +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_IN | 0x01, +                .bmAttributes          = USB_ENDPOINT_XFER_INT, +                .wMaxPacketSize        = STATUS_BYTECOUNT, +                .bInterval             = 1 << LOG2_STATUS_INTERVAL_MSEC, +            }, +        } +    },{ +        /* CDC Data Interface (off) */ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 0, +        .bNumEndpoints                 = 0, +        .bInterfaceClass               = USB_CLASS_CDC_DATA, +    },{ +        /* CDC Data Interface */ +        .bInterfaceNumber              = 1, +        .bAlternateSetting             = 1, +        .bNumEndpoints                 = 2, +        .bInterfaceClass               = USB_CLASS_CDC_DATA, +        .iInterface                    = STRING_DATA, +        .eps = (USBDescEndpoint[]) { +            { +                .bEndpointAddress      = USB_DIR_IN | 0x02, +                .bmAttributes          = USB_ENDPOINT_XFER_BULK, +                .wMaxPacketSize        = 0x40, +            },{ +                .bEndpointAddress      = USB_DIR_OUT | 0x02, +                .bmAttributes          = USB_ENDPOINT_XFER_BULK, +                .wMaxPacketSize        = 0x40, +            } +        } +    } +}; + +static const USBDescDevice desc_device_net = { +    .bcdUSB                        = 0x0200, +    .bDeviceClass                  = USB_CLASS_COMM, +    .bMaxPacketSize0               = 0x40, +    .bNumConfigurations            = 2, +    .confs = (USBDescConfig[]) { +        { +            .bNumInterfaces        = 2, +            .bConfigurationValue   = DEV_RNDIS_CONFIG_VALUE, +            .iConfiguration        = STRING_RNDIS, +            .bmAttributes          = 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[0]; +}; + +typedef struct USBNetState { +    USBDevice dev; + +    enum rndis_state rndis_state; +    uint32_t medium; +    uint32_t speed; +    uint32_t media_state; +    uint16_t filter; +    uint32_t vendorid; + +    unsigned int out_ptr; +    uint8_t out_buf[2048]; + +    unsigned int in_ptr, in_len; +    uint8_t in_buf[2048]; + +    USBEndpoint *intr; + +    char usbstring_mac[13]; +    NICState *nic; +    NICConf conf; +    QTAILQ_HEAD(rndis_resp_head, rndis_response) rndis_resp; +} USBNetState; + +#define TYPE_USB_NET "usb-net" +#define USB_NET(obj) OBJECT_CHECK(USBNetState, (obj), TYPE_USB_NET) + +static int is_rndis(USBNetState *s) +{ +    return s->dev.config->bConfigurationValue == DEV_RNDIS_CONFIG_VALUE; +} + +static int ndis_query(USBNetState *s, uint32_t oid, +                      uint8_t *inbuf, unsigned int inlen, uint8_t *outbuf, +                      size_t outlen) +{ +    unsigned int i; + +    switch (oid) { +    /* general oids (table 4-1) */ +    /* mandatory */ +    case OID_GEN_SUPPORTED_LIST: +        for (i = 0; i < ARRAY_SIZE(oid_supported_list); i++) +            ((le32 *) outbuf)[i] = cpu_to_le32(oid_supported_list[i]); +        return sizeof(oid_supported_list); + +    /* mandatory */ +    case OID_GEN_HARDWARE_STATUS: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_MEDIA_SUPPORTED: +        *((le32 *) outbuf) = cpu_to_le32(s->medium); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_MEDIA_IN_USE: +        *((le32 *) outbuf) = cpu_to_le32(s->medium); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_MAXIMUM_FRAME_SIZE: +        *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_LINK_SPEED: +        *((le32 *) outbuf) = cpu_to_le32(s->speed); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_TRANSMIT_BLOCK_SIZE: +        *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_RECEIVE_BLOCK_SIZE: +        *((le32 *) outbuf) = cpu_to_le32(ETH_FRAME_LEN); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_VENDOR_ID: +        *((le32 *) outbuf) = cpu_to_le32(s->vendorid); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_VENDOR_DESCRIPTION: +        pstrcpy((char *)outbuf, outlen, "QEMU USB RNDIS Net"); +        return strlen((char *)outbuf) + 1; + +    case OID_GEN_VENDOR_DRIVER_VERSION: +        *((le32 *) outbuf) = cpu_to_le32(1); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_CURRENT_PACKET_FILTER: +        *((le32 *) outbuf) = cpu_to_le32(s->filter); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_MAXIMUM_TOTAL_SIZE: +        *((le32 *) outbuf) = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_MEDIA_CONNECT_STATUS: +        *((le32 *) outbuf) = cpu_to_le32(s->media_state); +        return sizeof(le32); + +    case OID_GEN_PHYSICAL_MEDIUM: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    case OID_GEN_MAC_OPTIONS: +        *((le32 *) outbuf) = cpu_to_le32( +                        NDIS_MAC_OPTION_RECEIVE_SERIALIZED | +                        NDIS_MAC_OPTION_FULL_DUPLEX); +        return sizeof(le32); + +    /* statistics OIDs (table 4-2) */ +    /* mandatory */ +    case OID_GEN_XMIT_OK: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_RCV_OK: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_XMIT_ERROR: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_RCV_ERROR: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* mandatory */ +    case OID_GEN_RCV_NO_BUFFER: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* ieee802.3 OIDs (table 4-3) */ +    /* mandatory */ +    case OID_802_3_PERMANENT_ADDRESS: +        memcpy(outbuf, s->conf.macaddr.a, 6); +        return 6; + +    /* mandatory */ +    case OID_802_3_CURRENT_ADDRESS: +        memcpy(outbuf, s->conf.macaddr.a, 6); +        return 6; + +    /* mandatory */ +    case OID_802_3_MULTICAST_LIST: +        *((le32 *) outbuf) = cpu_to_le32(0xe0000000); +        return sizeof(le32); + +    /* mandatory */ +    case OID_802_3_MAXIMUM_LIST_SIZE: +        *((le32 *) outbuf) = cpu_to_le32(1); +        return sizeof(le32); + +    case OID_802_3_MAC_OPTIONS: +        return 0; + +    /* ieee802.3 statistics OIDs (table 4-4) */ +    /* mandatory */ +    case OID_802_3_RCV_ERROR_ALIGNMENT: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* mandatory */ +    case OID_802_3_XMIT_ONE_COLLISION: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    /* mandatory */ +    case OID_802_3_XMIT_MORE_COLLISIONS: +        *((le32 *) outbuf) = cpu_to_le32(0); +        return sizeof(le32); + +    default: +        fprintf(stderr, "usbnet: unknown OID 0x%08x\n", oid); +        return 0; +    } +    return -1; +} + +static int ndis_set(USBNetState *s, uint32_t oid, +                uint8_t *inbuf, unsigned int inlen) +{ +    switch (oid) { +    case OID_GEN_CURRENT_PACKET_FILTER: +        s->filter = le32_to_cpup((le32 *) inbuf); +        if (s->filter) { +            s->rndis_state = RNDIS_DATA_INITIALIZED; +        } else { +            s->rndis_state = RNDIS_INITIALIZED; +        } +        return 0; + +    case OID_802_3_MULTICAST_LIST: +        return 0; +    } +    return -1; +} + +static int rndis_get_response(USBNetState *s, uint8_t *buf) +{ +    int ret = 0; +    struct rndis_response *r = s->rndis_resp.tqh_first; + +    if (!r) +        return ret; + +    QTAILQ_REMOVE(&s->rndis_resp, r, entries); +    ret = r->length; +    memcpy(buf, r->buf, r->length); +    g_free(r); + +    return ret; +} + +static void *rndis_queue_response(USBNetState *s, unsigned int length) +{ +    struct rndis_response *r = +            g_malloc0(sizeof(struct rndis_response) + length); + +    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 (bufoffs + buflen > length) +        return USB_RET_STALL; + +    infobuflen = ndis_query(s, le32_to_cpu(buf->OID), +                            bufoffs + (uint8_t *) buf, buflen, infobuf, +                            sizeof(infobuf)); +    resplen = sizeof(rndis_query_cmplt_type) + +            ((infobuflen < 0) ? 0 : infobuflen); +    resp = rndis_queue_response(s, resplen); +    if (!resp) +        return USB_RET_STALL; + +    resp->MessageType = cpu_to_le32(RNDIS_QUERY_CMPLT); +    resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ +    resp->MessageLength = cpu_to_le32(resplen); + +    if (infobuflen < 0) { +        /* OID not supported */ +        resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); +        resp->InformationBufferLength = cpu_to_le32(0); +        resp->InformationBufferOffset = cpu_to_le32(0); +        return 0; +    } + +    resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); +    resp->InformationBufferOffset = +            cpu_to_le32(infobuflen ? sizeof(rndis_query_cmplt_type) - 8 : 0); +    resp->InformationBufferLength = cpu_to_le32(infobuflen); +    memcpy(resp + 1, infobuf, infobuflen); + +    return 0; +} + +static int rndis_set_response(USBNetState *s, +                rndis_set_msg_type *buf, unsigned int length) +{ +    rndis_set_cmplt_type *resp = +            rndis_queue_response(s, sizeof(rndis_set_cmplt_type)); +    uint32_t bufoffs, buflen; +    int ret; + +    if (!resp) +        return USB_RET_STALL; + +    bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8; +    buflen = le32_to_cpu(buf->InformationBufferLength); +    if (bufoffs + buflen > length) +        return USB_RET_STALL; + +    ret = ndis_set(s, le32_to_cpu(buf->OID), +                    bufoffs + (uint8_t *) buf, buflen); +    resp->MessageType = cpu_to_le32(RNDIS_SET_CMPLT); +    resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ +    resp->MessageLength = cpu_to_le32(sizeof(rndis_set_cmplt_type)); +    if (ret < 0) { +        /* OID not supported */ +        resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); +        return 0; +    } +    resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + +    return 0; +} + +static int rndis_reset_response(USBNetState *s, rndis_reset_msg_type *buf) +{ +    rndis_reset_cmplt_type *resp = +            rndis_queue_response(s, sizeof(rndis_reset_cmplt_type)); + +    if (!resp) +        return USB_RET_STALL; + +    resp->MessageType = cpu_to_le32(RNDIS_RESET_CMPLT); +    resp->MessageLength = cpu_to_le32(sizeof(rndis_reset_cmplt_type)); +    resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); +    resp->AddressingReset = cpu_to_le32(1); /* reset information */ + +    return 0; +} + +static int rndis_keepalive_response(USBNetState *s, +                rndis_keepalive_msg_type *buf) +{ +    rndis_keepalive_cmplt_type *resp = +            rndis_queue_response(s, sizeof(rndis_keepalive_cmplt_type)); + +    if (!resp) +        return USB_RET_STALL; + +    resp->MessageType = cpu_to_le32(RNDIS_KEEPALIVE_CMPLT); +    resp->MessageLength = cpu_to_le32(sizeof(rndis_keepalive_cmplt_type)); +    resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ +    resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + +    return 0; +} + +/* 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; +    le32 *tmp = (le32 *) data; + +    msg_type = le32_to_cpup(tmp); + +    switch (msg_type) { +    case RNDIS_INITIALIZE_MSG: +        s->rndis_state = RNDIS_INITIALIZED; +        return rndis_init_response(s, (rndis_init_msg_type *) data); + +    case RNDIS_HALT_MSG: +        s->rndis_state = RNDIS_UNINITIALIZED; +        return 0; + +    case RNDIS_QUERY_MSG: +        return rndis_query_response(s, (rndis_query_msg_type *) data, length); + +    case RNDIS_SET_MSG: +        return rndis_set_response(s, (rndis_set_msg_type *) data, length); + +    case RNDIS_RESET_MSG: +        rndis_clear_responsequeue(s); +        s->out_ptr = 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 + 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_handle_destroy(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_OPTIONS_KIND_NIC, +    .size = sizeof(NICState), +    .receive = usbnet_receive, +    .cleanup = usbnet_cleanup, +}; + +static void usb_net_realize(USBDevice *dev, Error **errrp) +{ +    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, NULL); +} + +static USBDevice *usb_net_init(USBBus *bus, const char *cmdline) +{ +    Error *local_err = NULL; +    USBDevice *dev; +    QemuOpts *opts; +    int idx; + +    opts = qemu_opts_parse_noisily(qemu_find_opts("net"), cmdline, false); +    if (!opts) { +        return NULL; +    } +    qemu_opt_set(opts, "type", "nic", &error_abort); +    qemu_opt_set(opts, "model", "usb", &error_abort); + +    idx = net_client_init(opts, 0, &local_err); +    if (local_err) { +        error_report_err(local_err); +        return NULL; +    } + +    dev = usb_create(bus, "usb-net"); +    qdev_set_nic_properties(&dev->qdev, &nd_table[idx]); +    return dev; +} + +static const VMStateDescription vmstate_usb_net = { +    .name = "usb-net", +    .unmigratable = 1, +}; + +static Property net_properties[] = { +    DEFINE_NIC_PROPERTIES(USBNetState, conf), +    DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_net_class_initfn(ObjectClass *klass, void *data) +{ +    DeviceClass *dc = DEVICE_CLASS(klass); +    USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + +    uc->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->handle_destroy = usb_net_handle_destroy; +    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); +    dc->fw_name = "network"; +    dc->vmsd = &vmstate_usb_net; +    dc->props = 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); +    usb_legacy_register(TYPE_USB_NET, "net", usb_net_init); +} + +type_init(usb_net_register_types) diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c new file mode 100644 index 00000000..a6a66008 --- /dev/null +++ b/hw/usb/dev-serial.c @@ -0,0 +1,649 @@ +/* + * FTDI FT232BM Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault + * + * This code is licensed under the LGPL. + */ + +#include "qemu-common.h" +#include "qemu/error-report.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "sysemu/char.h" + +//#define DEBUG_Serial + +#ifdef DEBUG_Serial +#define DPRINTF(fmt, ...) \ +do { printf("usb-serial: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +#define RECV_BUF 384 + +/* Commands */ +#define FTDI_RESET		0 +#define FTDI_SET_MDM_CTRL	1 +#define FTDI_SET_FLOW_CTRL	2 +#define FTDI_SET_BAUD		3 +#define FTDI_SET_DATA		4 +#define FTDI_GET_MDM_ST		5 +#define FTDI_SET_EVENT_CHR	6 +#define FTDI_SET_ERROR_CHR	7 +#define FTDI_SET_LATENCY	9 +#define FTDI_GET_LATENCY	10 + +#define DeviceOutVendor	((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8) +#define DeviceInVendor	((USB_DIR_IN |USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8) + +/* RESET */ + +#define FTDI_RESET_SIO	0 +#define FTDI_RESET_RX	1 +#define FTDI_RESET_TX	2 + +/* SET_MDM_CTRL */ + +#define FTDI_DTR	1 +#define FTDI_SET_DTR	(FTDI_DTR << 8) +#define FTDI_RTS	2 +#define FTDI_SET_RTS	(FTDI_RTS << 8) + +/* SET_FLOW_CTRL */ + +#define FTDI_RTS_CTS_HS		1 +#define FTDI_DTR_DSR_HS		2 +#define FTDI_XON_XOFF_HS	4 + +/* SET_DATA */ + +#define FTDI_PARITY	(0x7 << 8) +#define FTDI_ODD	(0x1 << 8) +#define FTDI_EVEN	(0x2 << 8) +#define FTDI_MARK	(0x3 << 8) +#define FTDI_SPACE	(0x4 << 8) + +#define FTDI_STOP	(0x3 << 11) +#define FTDI_STOP1	(0x0 << 11) +#define FTDI_STOP15	(0x1 << 11) +#define FTDI_STOP2	(0x2 << 11) + +/* GET_MDM_ST */ +/* TODO: should be sent every 40ms */ +#define FTDI_CTS  (1<<4)        // CTS line status +#define FTDI_DSR  (1<<5)        // DSR line status +#define FTDI_RI   (1<<6)        // RI line status +#define FTDI_RLSD (1<<7)        // Receive Line Signal Detect + +/* Status */ + +#define FTDI_DR   (1<<0)        // Data Ready +#define FTDI_OE   (1<<1)        // Overrun Err +#define FTDI_PE   (1<<2)        // Parity Err +#define FTDI_FE   (1<<3)        // Framing Err +#define FTDI_BI   (1<<4)        // Break Interrupt +#define FTDI_THRE (1<<5)        // Transmitter Holding Register +#define FTDI_TEMT (1<<6)        // Transmitter Empty +#define FTDI_FIFO (1<<7)        // Error in FIFO + +typedef struct { +    USBDevice dev; +    uint8_t recv_buf[RECV_BUF]; +    uint16_t recv_ptr; +    uint16_t recv_used; +    uint8_t event_chr; +    uint8_t error_chr; +    uint8_t event_trigger; +    QEMUSerialSetParams params; +    int latency;        /* ms */ +    CharDriverState *cs; +} USBSerialState; + +#define TYPE_USB_SERIAL "usb-serial-dev" +#define USB_SERIAL_DEV(obj) OBJECT_CHECK(USBSerialState, (obj), TYPE_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, +            .bMaxPower             = 50, +            .nif = 1, +            .ifs = &desc_iface0, +        }, +    }, +}; + +static const USBDesc desc_serial = { +    .id = { +        .idVendor          = 0x0403, +        .idProduct         = 0x6001, +        .bcdDevice         = 0x0400, +        .iManufacturer     = STR_MANUFACTURER, +        .iProduct          = STR_PRODUCT_SERIAL, +        .iSerialNumber     = STR_SERIALNUMBER, +    }, +    .full = &desc_device, +    .str  = desc_strings, +}; + +static const USBDesc desc_braille = { +    .id = { +        .idVendor          = 0x0403, +        .idProduct         = 0xfe72, +        .bcdDevice         = 0x0400, +        .iManufacturer     = STR_MANUFACTURER, +        .iProduct          = STR_PRODUCT_BRAILLE, +        .iSerialNumber     = STR_SERIALNUMBER, +    }, +    .full = &desc_device, +    .str  = desc_strings, +}; + +static void usb_serial_reset(USBSerialState *s) +{ +    /* TODO: Set flow control to none */ +    s->event_chr = 0x0d; +    s->event_trigger = 0; +    s->recv_ptr = 0; +    s->recv_used = 0; +    /* TODO: purge in char driver */ +} + +static void usb_serial_handle_reset(USBDevice *dev) +{ +    USBSerialState *s = (USBSerialState *)dev; + +    DPRINTF("Reset\n"); + +    usb_serial_reset(s); +    /* TODO: Reset char device, send BREAK? */ +} + +static uint8_t usb_get_modem_lines(USBSerialState *s) +{ +    int flags; +    uint8_t ret; + +    if (qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP) +        return FTDI_CTS|FTDI_DSR|FTDI_RLSD; + +    ret = 0; +    if (flags & CHR_TIOCM_CTS) +        ret |= FTDI_CTS; +    if (flags & CHR_TIOCM_DSR) +        ret |= FTDI_DSR; +    if (flags & CHR_TIOCM_RI) +        ret |= FTDI_RI; +    if (flags & CHR_TIOCM_CAR) +        ret |= FTDI_RLSD; + +    return ret; +} + +static void usb_serial_handle_control(USBDevice *dev, USBPacket *p, +               int request, int value, int index, int length, uint8_t *data) +{ +    USBSerialState *s = (USBSerialState *)dev; +    int ret; + +    DPRINTF("got control %x, value %x\n",request, value); +    ret = usb_desc_handle_control(dev, p, request, value, index, length, data); +    if (ret >= 0) { +        return; +    } + +    switch (request) { +    case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: +        break; + +        /* Class specific requests.  */ +    case DeviceOutVendor | FTDI_RESET: +        switch (value) { +        case FTDI_RESET_SIO: +            usb_serial_reset(s); +            break; +        case FTDI_RESET_RX: +            s->recv_ptr = 0; +            s->recv_used = 0; +            /* TODO: purge from char device */ +            break; +        case FTDI_RESET_TX: +            /* TODO: purge from char device */ +            break; +        } +        break; +    case DeviceOutVendor | FTDI_SET_MDM_CTRL: +    { +        static int flags; +        qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_GET_TIOCM, &flags); +        if (value & FTDI_SET_RTS) { +            if (value & FTDI_RTS) +                flags |= CHR_TIOCM_RTS; +            else +                flags &= ~CHR_TIOCM_RTS; +        } +        if (value & FTDI_SET_DTR) { +            if (value & FTDI_DTR) +                flags |= CHR_TIOCM_DTR; +            else +                flags &= ~CHR_TIOCM_DTR; +        } +        qemu_chr_fe_ioctl(s->cs,CHR_IOCTL_SERIAL_SET_TIOCM, &flags); +        break; +    } +    case DeviceOutVendor | FTDI_SET_FLOW_CTRL: +        /* TODO: ioctl */ +        break; +    case DeviceOutVendor | FTDI_SET_BAUD: { +        static const int subdivisors8[8] = { 0, 4, 2, 1, 3, 5, 6, 7 }; +        int subdivisor8 = subdivisors8[((value & 0xc000) >> 14) +                                     | ((index & 1) << 2)]; +        int divisor = value & 0x3fff; + +        /* chip special cases */ +        if (divisor == 1 && subdivisor8 == 0) +            subdivisor8 = 4; +        if (divisor == 0 && subdivisor8 == 0) +            divisor = 1; + +        s->params.speed = (48000000 / 2) / (8 * divisor + subdivisor8); +        qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); +        break; +    } +    case DeviceOutVendor | FTDI_SET_DATA: +        switch (value & FTDI_PARITY) { +            case 0: +                s->params.parity = 'N'; +                break; +            case FTDI_ODD: +                s->params.parity = 'O'; +                break; +            case FTDI_EVEN: +                s->params.parity = 'E'; +                break; +            default: +                DPRINTF("unsupported parity %d\n", value & FTDI_PARITY); +                goto fail; +        } +        switch (value & FTDI_STOP) { +            case FTDI_STOP1: +                s->params.stop_bits = 1; +                break; +            case FTDI_STOP2: +                s->params.stop_bits = 2; +                break; +            default: +                DPRINTF("unsupported stop bits %d\n", value & FTDI_STOP); +                goto fail; +        } +        qemu_chr_fe_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); +        /* TODO: TX ON/OFF */ +        break; +    case DeviceInVendor | FTDI_GET_MDM_ST: +        data[0] = usb_get_modem_lines(s) | 1; +        data[1] = 0; +        p->actual_length = 2; +        break; +    case DeviceOutVendor | FTDI_SET_EVENT_CHR: +        /* TODO: handle it */ +        s->event_chr = value; +        break; +    case DeviceOutVendor | FTDI_SET_ERROR_CHR: +        /* TODO: handle it */ +        s->error_chr = value; +        break; +    case DeviceOutVendor | FTDI_SET_LATENCY: +        s->latency = value; +        break; +    case DeviceInVendor | FTDI_GET_LATENCY: +        data[0] = s->latency; +        p->actual_length = 1; +        break; +    default: +    fail: +        DPRINTF("got unsupported/bogus control %x, value %x\n", request, value); +        p->status = USB_RET_STALL; +        break; +    } +} + +static void usb_serial_handle_data(USBDevice *dev, USBPacket *p) +{ +    USBSerialState *s = (USBSerialState *)dev; +    uint8_t devep = p->ep->nr; +    struct iovec *iov; +    uint8_t header[2]; +    int i, first_len, len; + +    switch (p->pid) { +    case USB_TOKEN_OUT: +        if (devep != 2) +            goto fail; +        for (i = 0; i < p->iov.niov; i++) { +            iov = p->iov.iov + i; +            qemu_chr_fe_write(s->cs, iov->iov_base, iov->iov_len); +        } +        p->actual_length = p->iov.size; +        break; + +    case USB_TOKEN_IN: +        if (devep != 1) +            goto fail; +        first_len = RECV_BUF - s->recv_ptr; +        len = p->iov.size; +        if (len <= 2) { +            p->status = USB_RET_NAK; +            break; +        } +        header[0] = usb_get_modem_lines(s) | 1; +        /* We do not have the uart details */ +        /* handle serial break */ +        if (s->event_trigger && s->event_trigger & FTDI_BI) { +            s->event_trigger &= ~FTDI_BI; +            header[1] = FTDI_BI; +            usb_packet_copy(p, header, 2); +            break; +        } else { +            header[1] = 0; +        } +        len -= 2; +        if (len > s->recv_used) +            len = s->recv_used; +        if (!len) { +            p->status = USB_RET_NAK; +            break; +        } +        if (first_len > len) +            first_len = len; +        usb_packet_copy(p, header, 2); +        usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len); +        if (len > first_len) +            usb_packet_copy(p, s->recv_buf, len - first_len); +        s->recv_used -= len; +        s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; +        break; + +    default: +        DPRINTF("Bad token\n"); +    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; +} + +static void usb_serial_event(void *opaque, int event) +{ +    USBSerialState *s = opaque; + +    switch (event) { +        case CHR_EVENT_BREAK: +            s->event_trigger |= FTDI_BI; +            break; +        case CHR_EVENT_FOCUS: +            break; +        case CHR_EVENT_OPENED: +            if (!s->dev.attached) { +                usb_device_attach(&s->dev, &error_abort); +            } +            break; +        case CHR_EVENT_CLOSED: +            if (s->dev.attached) { +                usb_device_detach(&s->dev); +            } +            break; +    } +} + +static void usb_serial_realize(USBDevice *dev, Error **errp) +{ +    USBSerialState *s = USB_SERIAL_DEV(dev); +    Error *local_err = NULL; + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); +    dev->auto_attach = 0; + +    if (!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_add_handlers(s->cs, usb_serial_can_read, usb_serial_read, +                          usb_serial_event, s); +    usb_serial_handle_reset(dev); + +    if (s->cs->be_open && !dev->attached) { +        usb_device_attach(dev, &error_abort); +    } +} + +static USBDevice *usb_serial_init(USBBus *bus, const char *filename) +{ +    USBDevice *dev; +    CharDriverState *cdrv; +    uint32_t vendorid = 0, productid = 0; +    char label[32]; +    static int index; + +    while (*filename && *filename != ':') { +        const char *p; +        char *e; +        if (strstart(filename, "vendorid=", &p)) { +            vendorid = strtol(p, &e, 16); +            if (e == p || (*e && *e != ',' && *e != ':')) { +                error_report("bogus vendor ID %s", p); +                return NULL; +            } +            filename = e; +        } else if (strstart(filename, "productid=", &p)) { +            productid = strtol(p, &e, 16); +            if (e == p || (*e && *e != ',' && *e != ':')) { +                error_report("bogus product ID %s", p); +                return NULL; +            } +            filename = e; +        } else { +            error_report("unrecognized serial USB option %s", filename); +            return NULL; +        } +        while(*filename == ',') +            filename++; +    } +    if (!*filename) { +        error_report("character device specification needed"); +        return NULL; +    } +    filename++; + +    snprintf(label, sizeof(label), "usbserial%d", index++); +    cdrv = qemu_chr_new(label, filename, NULL); +    if (!cdrv) +        return NULL; + +    dev = usb_create(bus, "usb-serial"); +    qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); +    if (vendorid) +        qdev_prop_set_uint16(&dev->qdev, "vendorid", vendorid); +    if (productid) +        qdev_prop_set_uint16(&dev->qdev, "productid", productid); +    return dev; +} + +static USBDevice *usb_braille_init(USBBus *bus, const char *unused) +{ +    USBDevice *dev; +    CharDriverState *cdrv; + +    cdrv = qemu_chr_new("braille", "braille", NULL); +    if (!cdrv) +        return NULL; + +    dev = usb_create(bus, "usb-braille"); +    qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); +    return dev; +} + +static const VMStateDescription vmstate_usb_serial = { +    .name = "usb-serial", +    .unmigratable = 1, +}; + +static Property serial_properties[] = { +    DEFINE_PROP_CHR("chardev", USBSerialState, cs), +    DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_serial_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; +    dc->props = 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; +    dc->props = 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); +    usb_legacy_register("usb-serial", "serial", usb_serial_init); +    type_register_static(&braille_info); +    usb_legacy_register("usb-braille", "braille", usb_braille_init); +} + +type_init(usb_serial_register_types) diff --git a/hw/usb/dev-smartcard-reader.c b/hw/usb/dev-smartcard-reader.c new file mode 100644 index 00000000..8952efff --- /dev/null +++ b/hw/usb/dev-smartcard-reader.c @@ -0,0 +1,1505 @@ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * CCID Device emulation + * + * Written by Alon Levy, with contributions from Robert Relyea. + * + * Based on usb-serial.c, see its copyright and attributions below. + * + * This work is licensed under the terms of the GNU GPL, version 2.1 or later. + * See the COPYING file in the top-level directory. + * ------- (original copyright & attribution for usb-serial.c below) -------- + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault, + */ + +/* + * References: + * + * CCID Specification Revision 1.1 April 22nd 2005 + *  "Universal Serial Bus, Device Class: Smart Card" + *  Specification for Integrated Circuit(s) Cards Interface Devices + * + * Endianness note: from the spec (1.3) + *  "Fields that are larger than a byte are stored in little endian" + * + * KNOWN BUGS + * 1. remove/insert can sometimes result in removed state instead of inserted. + * This is a result of the following: + *  symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This can happen + *  when a short packet is sent, as seen in uhci-usb.c, resulting from a urb + *  from the guest requesting SPD and us returning a smaller packet. + *  Not sure which messages trigger this. + */ + +#include "qemu-common.h" +#include "qemu/error-report.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" + +#include "ccid.h" + +#define DPRINTF(s, lvl, fmt, ...) \ +do { \ +    if (lvl <= s->debug) { \ +        printf("usb-ccid: " fmt , ## __VA_ARGS__); \ +    } \ +} while (0) + +#define D_WARN 1 +#define D_INFO 2 +#define D_MORE_INFO 3 +#define D_VERBOSE 4 + +#define CCID_DEV_NAME "usb-ccid" +#define USB_CCID_DEV(obj) OBJECT_CHECK(USBCCIDState, (obj), CCID_DEV_NAME) +/* + * The two options for variable sized buffers: + * make them constant size, for large enough constant, + * or handle the migration complexity - VMState doesn't handle this case. + * sizes are expected never to be exceeded, unless guest misbehaves. + */ +#define BULK_OUT_DATA_SIZE 65536 +#define PENDING_ANSWERS_NUM 128 + +#define BULK_IN_BUF_SIZE 384 +#define BULK_IN_PENDING_NUM 8 + +#define 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[0]; +} CCID_DataBlock; + +/* 6.1.4 PC_to_RDR_XfrBlock */ +typedef struct QEMU_PACKED CCID_XferBlock { +    CCID_Header  hdr; +    uint8_t      bBWI; /* Block Waiting Timeout */ +    uint16_t     wLevelParameter; /* XXX currently unused */ +    uint8_t      abData[0]; +} CCID_XferBlock; + +typedef struct QEMU_PACKED CCID_IccPowerOn { +    CCID_Header hdr; +    uint8_t     bPowerSelect; +    uint16_t    abRFU; +} CCID_IccPowerOn; + +typedef struct QEMU_PACKED CCID_IccPowerOff { +    CCID_Header hdr; +    uint16_t    abRFU; +} CCID_IccPowerOff; + +typedef struct QEMU_PACKED CCID_SetParameters { +    CCID_Header hdr; +    uint8_t     bProtocolNum; +    uint16_t   abRFU; +    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; + +enum { +    MIGRATION_NONE, +    MIGRATION_MIGRATED, +}; + +typedef struct CCIDBus { +    BusState qbus; +} CCIDBus; + +/* + * powered - defaults to true, changed by PowerOn/PowerOff messages + */ +typedef 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; +    uint32_t migration_target_ip; +    uint16_t migration_target_port; +    uint8_t  migration_state; +    uint8_t  bmSlotICCState; +    uint8_t  powered; +    uint8_t  notify_slot_change; +    uint8_t  debug; +} USBCCIDState; + +/* + * CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, + * "USB Device Framework", section 9.6.1, in the Universal Serial Bus + * Specification. + * + * This device implemented based on the spec and with an Athena Smart Card + * Reader as reference: + *   0dc3:1004 Athena Smartcard Solutions, Inc. + */ + +static const uint8_t qemu_ccid_descriptor[] = { +        /* Smart Card Device Class Descriptor */ +        0x36,       /* u8  bLength; */ +        0x21,       /* u8  bDescriptorType; Functional */ +        0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ +        0x00,       /* +                     * u8  bMaxSlotIndex; The index of the highest available +                     * slot on this device. All slots are consecutive starting +                     * at 00h. +                     */ +        0x07,       /* u8  bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ + +        0x00, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ +        0x01, 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 int ccid_card_exitfn(CCIDCardState *card) +{ +    CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + +    if (cc->exitfn) { +        return cc->exitfn(card); +    } +    return 0; +} + +static int ccid_card_initfn(CCIDCardState *card) +{ +    CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + +    if (cc->initfn) { +        return cc->initfn(card); +    } +    return 0; +} + +static bool ccid_has_pending_answers(USBCCIDState *s) +{ +    return s->pending_answers_num > 0; +} + +static void ccid_clear_pending_answers(USBCCIDState *s) +{ +    s->pending_answers_num = 0; +    s->pending_answers_start = 0; +    s->pending_answers_end = 0; +} + +static void ccid_print_pending_answers(USBCCIDState *s) +{ +    Answer *answer; +    int i, count; + +    DPRINTF(s, D_VERBOSE, "usb-ccid: pending answers:"); +    if (!ccid_has_pending_answers(s)) { +        DPRINTF(s, D_VERBOSE, " empty\n"); +        return; +    } +    for (i = s->pending_answers_start, count = s->pending_answers_num ; +         count > 0; count--, i++) { +        answer = &s->pending_answers[i % PENDING_ANSWERS_NUM]; +        if (count == 1) { +            DPRINTF(s, D_VERBOSE, "%d:%d\n", answer->slot, answer->seq); +        } else { +            DPRINTF(s, D_VERBOSE, "%d:%d,", answer->slot, answer->seq); +        } +    } +} + +static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) +{ +    Answer *answer; + +    assert(s->pending_answers_num < PENDING_ANSWERS_NUM); +    s->pending_answers_num++; +    answer = +        &s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; +    answer->slot = hdr->bSlot; +    answer->seq = hdr->bSeq; +    ccid_print_pending_answers(s); +} + +static void ccid_remove_pending_answer(USBCCIDState *s, +    uint8_t *slot, uint8_t *seq) +{ +    Answer *answer; + +    assert(s->pending_answers_num > 0); +    s->pending_answers_num--; +    answer = +        &s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; +    *slot = answer->slot; +    *seq = answer->seq; +    ccid_print_pending_answers(s); +} + +static void ccid_bulk_in_clear(USBCCIDState *s) +{ +    s->bulk_in_pending_start = 0; +    s->bulk_in_pending_end = 0; +    s->bulk_in_pending_num = 0; +} + +static void ccid_bulk_in_release(USBCCIDState *s) +{ +    assert(s->current_bulk_in != NULL); +    s->current_bulk_in->pos = 0; +    s->current_bulk_in = NULL; +} + +static void ccid_bulk_in_get(USBCCIDState *s) +{ +    if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { +        return; +    } +    assert(s->bulk_in_pending_num > 0); +    s->bulk_in_pending_num--; +    s->current_bulk_in = +        &s->bulk_in_pending[(s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; +} + +static void *ccid_reserve_recv_buf(USBCCIDState *s, uint16_t len) +{ +    BulkIn *bulk_in; + +    DPRINTF(s, D_VERBOSE, "%s: QUEUE: reserve %d bytes\n", __func__, len); + +    /* look for an existing element */ +    if (len > BULK_IN_BUF_SIZE) { +        DPRINTF(s, D_WARN, "usb-ccid.c: %s: len larger then max (%d>%d). " +                           "discarding message.\n", +                           __func__, len, BULK_IN_BUF_SIZE); +        return NULL; +    } +    if (s->bulk_in_pending_num >= BULK_IN_PENDING_NUM) { +        DPRINTF(s, D_WARN, "usb-ccid.c: %s: No free bulk_in buffers. " +                           "discarding message.\n", __func__); +        return NULL; +    } +    bulk_in = +        &s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; +    s->bulk_in_pending_num++; +    bulk_in->len = len; +    return bulk_in->data; +} + +static void ccid_reset(USBCCIDState *s) +{ +    ccid_bulk_in_clear(s); +    ccid_clear_pending_answers(s); +} + +static void ccid_detach(USBCCIDState *s) +{ +    ccid_reset(s); +} + +static void ccid_handle_reset(USBDevice *dev) +{ +    USBCCIDState *s = 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); +    } +    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 %d\n", __func__, +                recv->hdr.bSeq, len); +    ccid_add_pending_answer(s, (CCID_Header *)recv); +    if (s->card) { +        ccid_card_apdu_from_guest(s->card, recv->abData, len); +    } else { +        DPRINTF(s, D_WARN, "warning: discarded apdu\n"); +    } +} + +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) { +        p->status = USB_RET_STALL; +        return; +    } +    ccid_header = (CCID_Header *)s->bulk_out_data; +    usb_packet_copy(p, s->bulk_out_data + s->bulk_out_pos, p->iov.size); +    s->bulk_out_pos += p->iov.size; +    if (p->iov.size == CCID_MAX_PACKET_SIZE) { +        DPRINTF(s, D_VERBOSE, +            "usb-ccid: bulk_in: expecting more packets (%zd/%d)\n", +            p->iov.size, ccid_header->dwLength); +        return; +    } +    if (s->bulk_out_pos < 10) { +        DPRINTF(s, 1, +                "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", +                __func__); +    } else { +        DPRINTF(s, D_MORE_INFO, "%s %x %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; +} + +static void ccid_bulk_in_copy_to_guest(USBCCIDState *s, USBPacket *p) +{ +    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); +        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) { +            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); +            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_handle_destroy(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" +#define CCID_BUS(obj) OBJECT_CHECK(CCIDBus, (obj), TYPE_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 %d (answer seq %d, slot %d)\n", +        len, answer->seq, answer->slot); +    ccid_write_data_block_answer(s, apdu, len); +} + +void ccid_card_card_removed(CCIDCardState *card) +{ +    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"); +    if (s->migration_state == MIGRATION_MIGRATED) { +        s->migration_state = MIGRATION_NONE; +    } +    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 int ccid_card_exit(DeviceState *qdev) +{ +    int ret = 0; +    CCIDCardState *card = CCID_CARD(qdev); +    USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); +    USBCCIDState *s = USB_CCID_DEV(dev); + +    if (ccid_card_inserted(s)) { +        ccid_card_card_removed(card); +    } +    ret = ccid_card_exitfn(card); +    s->card = NULL; +    return ret; +} + +static int ccid_card_init(DeviceState *qdev) +{ +    CCIDCardState *card = CCID_CARD(qdev); +    USBDevice *dev = USB_DEVICE(qdev->parent_bus->parent); +    USBCCIDState *s = USB_CCID_DEV(dev); +    int ret = 0; + +    if (card->slot != 0) { +        error_report("Warning: usb-ccid supports one slot, can't add %d", +                card->slot); +        return -1; +    } +    if (s->card != NULL) { +        error_report("Warning: usb-ccid card already full, not adding"); +        return -1; +    } +    ret = ccid_card_initfn(card); +    if (ret == 0) { +        s->card = card; +    } +    return ret; +} + +static void ccid_realize(USBDevice *dev, Error **errp) +{ +    USBCCIDState *s = USB_CCID_DEV(dev); + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); +    qbus_create_inplace(&s->bus, sizeof(s->bus), TYPE_CCID_BUS, DEVICE(dev), +                        NULL); +    qbus_set_hotplug_handler(BUS(&s->bus), DEVICE(dev), &error_abort); +    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->migration_state = MIGRATION_NONE; +    s->migration_target_ip = 0; +    s->migration_target_port = 0; +    s->dev.speed = USB_SPEED_FULL; +    s->dev.speedmask = USB_SPEED_MASK_FULL; +    s->notify_slot_change = false; +    s->powered = true; +    s->pending_answers_num = 0; +    s->last_answer_error = 0; +    s->bulk_in_pending_start = 0; +    s->bulk_in_pending_end = 0; +    s->current_bulk_in = NULL; +    ccid_reset_error_status(s); +    s->bulk_out_pos = 0; +    ccid_reset_parameters(s); +    ccid_reset(s); +    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 void ccid_pre_save(void *opaque) +{ +    USBCCIDState *s = opaque; + +    s->state_vmstate = s->dev.state; +    if (s->dev.attached) { +        /* +         * Migrating an open device, ignore reconnection CHR_EVENT to avoid an +         * erroneous detach. +         */ +        s->migration_state = MIGRATION_MIGRATED; +    } +} + +static VMStateDescription bulk_in_vmstate = { +    .name = "CCID BulkIn state", +    .version_id = 1, +    .minimum_version_id = 1, +    .fields = (VMStateField[]) { +        VMSTATE_BUFFER(data, BulkIn), +        VMSTATE_UINT32(len, BulkIn), +        VMSTATE_UINT32(pos, BulkIn), +        VMSTATE_END_OF_LIST() +    } +}; + +static VMStateDescription answer_vmstate = { +    .name = "CCID Answer state", +    .version_id = 1, +    .minimum_version_id = 1, +    .fields = (VMStateField[]) { +        VMSTATE_UINT8(slot, Answer), +        VMSTATE_UINT8(seq, Answer), +        VMSTATE_END_OF_LIST() +    } +}; + +static VMStateDescription usb_device_vmstate = { +    .name = "usb_device", +    .version_id = 1, +    .minimum_version_id = 1, +    .fields = (VMStateField[]) { +        VMSTATE_UINT8(addr, USBDevice), +        VMSTATE_BUFFER(setup_buf, USBDevice), +        VMSTATE_BUFFER(data_buf, USBDevice), +        VMSTATE_END_OF_LIST() +    } +}; + +static VMStateDescription ccid_vmstate = { +    .name = "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_UINT8(migration_state, USBCCIDState), +        VMSTATE_UINT32(state_vmstate, USBCCIDState), +        VMSTATE_END_OF_LIST() +    } +}; + +static Property ccid_properties[] = { +    DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), +    DEFINE_PROP_END_OF_LIST(), +}; + +static void ccid_class_initfn(ObjectClass *klass, void *data) +{ +    DeviceClass *dc = DEVICE_CLASS(klass); +    USBDeviceClass *uc = USB_DEVICE_CLASS(klass); +    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->handle_destroy = ccid_handle_destroy; +    dc->desc = "CCID Rev 1.1 smartcard reader"; +    dc->vmsd = &ccid_vmstate; +    dc->props = ccid_properties; +    set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +    hc->unplug = qdev_simple_device_unplug_cb; +} + +static const TypeInfo ccid_info = { +    .name          = CCID_DEV_NAME, +    .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->init = ccid_card_init; +    k->exit = ccid_card_exit; +    k->props = 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); +    usb_legacy_register(CCID_DEV_NAME, "ccid", NULL); +} + +type_init(ccid_register_types) diff --git a/hw/usb/dev-storage.c b/hw/usb/dev-storage.c new file mode 100644 index 00000000..9a4e7dc0 --- /dev/null +++ b/hw/usb/dev-storage.c @@ -0,0 +1,867 @@ +/* + * USB Mass Storage Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the LGPL. + */ + +#include "qemu-common.h" +#include "qemu/error-report.h" +#include "qemu/option.h" +#include "qemu/config-file.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/scsi/scsi.h" +#include "ui/console.h" +#include "monitor/monitor.h" +#include "sysemu/sysemu.h" +#include "sysemu/block-backend.h" +#include "sysemu/blockdev.h" +#include "qapi/visitor.h" + +//#define DEBUG_MSD + +#ifdef DEBUG_MSD +#define DPRINTF(fmt, ...) \ +do { printf("usb-msd: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +/* USB requests.  */ +#define MassStorageReset  0xff +#define GetMaxLun         0xfe + +enum USBMSDMode { +    USB_MSDM_CBW, /* Command Block.  */ +    USB_MSDM_DATAOUT, /* Transfer data to device.  */ +    USB_MSDM_DATAIN, /* Transfer data from device.  */ +    USB_MSDM_CSW /* Command Status.  */ +}; + +struct usb_msd_csw { +    uint32_t sig; +    uint32_t tag; +    uint32_t residue; +    uint8_t status; +}; + +typedef struct { +    USBDevice dev; +    enum USBMSDMode mode; +    uint32_t scsi_off; +    uint32_t scsi_len; +    uint32_t data_len; +    struct usb_msd_csw csw; +    SCSIRequest *req; +    SCSIBus bus; +    /* For async completion.  */ +    USBPacket *packet; +    /* usb-storage only */ +    BlockConf conf; +    uint32_t removable; +    SCSIDevice *scsi_dev; +} MSDState; + +#define TYPE_USB_STORAGE "usb-storage-dev" +#define USB_STORAGE_DEV(obj) OBJECT_CHECK(MSDState, (obj), TYPE_USB_STORAGE) + +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; +    s->data_len -= len; +    if (s->scsi_len == 0 || s->data_len == 0) { +        scsi_req_continue(s->req); +    } +} + +static void usb_msd_send_status(MSDState *s, USBPacket *p) +{ +    int len; + +    DPRINTF("Command status %d tag 0x%x, len %zd\n", +            s->csw.status, 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.  */ +    DPRINTF("Packet complete %p\n", p); +    s->packet = NULL; +    usb_packet_complete(&s->dev, p); +} + +static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len) +{ +    MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); +    USBPacket *p = s->packet; + +    assert((s->mode == USB_MSDM_DATAOUT) == (req->cmd.mode == SCSI_XFER_TO_DEV)); +    s->scsi_len = len; +    s->scsi_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); +        } +    } +} + +static void usb_msd_command_complete(SCSIRequest *req, uint32_t status, size_t resid) +{ +    MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); +    USBPacket *p = s->packet; + +    DPRINTF("Command complete %d tag 0x%x\n", status, req->tag); + +    s->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 = 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); +                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; +} + +static void usb_msd_request_cancelled(SCSIRequest *req) +{ +    MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); + +    if (req == s->req) { +        scsi_req_unref(s->req); +        s->req = NULL; +        s->scsi_len = 0; +    } +} + +static void usb_msd_handle_reset(USBDevice *dev) +{ +    MSDState *s = (MSDState *)dev; + +    DPRINTF("Reset\n"); +    if (s->req) { +        scsi_req_cancel(s->req); +    } +    assert(s->req == NULL); + +    if (s->packet) { +        s->packet->status = USB_RET_STALL; +        usb_msd_packet_complete(s); +    } + +    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++; +        } +        DPRINTF("MaxLun %d\n", 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; +            } +            DPRINTF("Command on LUN %d\n", cbw.lun); +            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; +            } +            DPRINTF("Command tag 0x%x flags %08x len %d data %d\n", +                    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); +#ifdef DEBUG_MSD +            scsi_req_print(s->req); +#endif +            len = scsi_req_enqueue(s->req); +            if (len) { +                scsi_req_continue(s->req); +            } +            break; + +        case USB_MSDM_DATAOUT: +            DPRINTF("Data out %zd/%d\n", p->iov.size, s->data_len); +            if (p->iov.size > s->data_len) { +                goto fail; +            } + +            if (s->scsi_len) { +                usb_msd_copy_data(s, p); +            } +            if (le32_to_cpu(s->csw.residue)) { +                int len = p->iov.size - p->actual_length; +                if (len) { +                    usb_packet_skip(p, len); +                    s->data_len -= len; +                    if (s->data_len == 0) { +                        s->mode = USB_MSDM_CSW; +                    } +                } +            } +            if (p->actual_length < p->iov.size) { +                DPRINTF("Deferring packet %p [wait data-out]\n", p); +                s->packet = p; +                p->status = USB_RET_ASYNC; +            } +            break; + +        default: +            DPRINTF("Unexpected write (len %zd)\n", p->iov.size); +            goto fail; +        } +        break; + +    case USB_TOKEN_IN: +        if (devep != 1) +            goto fail; + +        switch (s->mode) { +        case USB_MSDM_DATAOUT: +            if (s->data_len != 0 || p->iov.size < 13) { +                goto fail; +            } +            /* Waiting for SCSI write to complete.  */ +            s->packet = p; +            p->status = USB_RET_ASYNC; +            break; + +        case USB_MSDM_CSW: +            if (p->iov.size < 13) { +                goto fail; +            } + +            if (s->req) { +                /* still in flight */ +                DPRINTF("Deferring packet %p [wait status]\n", p); +                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: +            DPRINTF("Data in %zd/%d, scsi_len %d\n", +                    p->iov.size, s->data_len, s->scsi_len); +            if (s->scsi_len) { +                usb_msd_copy_data(s, p); +            } +            if (le32_to_cpu(s->csw.residue)) { +                int len = p->iov.size - p->actual_length; +                if (len) { +                    usb_packet_skip(p, len); +                    s->data_len -= len; +                    if (s->data_len == 0) { +                        s->mode = USB_MSDM_CSW; +                    } +                } +            } +            if (p->actual_length < p->iov.size) { +                DPRINTF("Deferring packet %p [wait data-in]\n", p); +                s->packet = p; +                p->status = USB_RET_ASYNC; +            } +            break; + +        default: +            DPRINTF("Unexpected read (len %zd)\n", p->iov.size); +            goto fail; +        } +        break; + +    default: +        DPRINTF("Bad token\n"); +    fail: +        p->status = USB_RET_STALL; +        break; +    } +} + +static void usb_msd_password_cb(void *opaque, int err) +{ +    MSDState *s = opaque; +    Error *local_err = NULL; + +    if (!err) { +        usb_device_attach(&s->dev, &local_err); +    } + +    if (local_err) { +        error_report_err(local_err); +        qdev_unplug(&s->dev.qdev, NULL); +    } +} + +static 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 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 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_realize_storage(USBDevice *dev, Error **errp) +{ +    MSDState *s = USB_STORAGE_DEV(dev); +    BlockBackend *blk = s->conf.blk; +    SCSIDevice *scsi_dev; +    Error *err = NULL; + +    if (!blk) { +        error_setg(errp, "drive property not set"); +        return; +    } + +    bdrv_add_key(blk_bs(blk), NULL, &err); +    if (err) { +        if (monitor_cur_is_qmp()) { +            error_propagate(errp, err); +            return; +        } +        error_free(err); +        err = NULL; +        if (cur_mon) { +            monitor_read_bdrv_key_start(cur_mon, blk_bs(blk), +                                        usb_msd_password_cb, s); +            s->dev.auto_attach = 0; +        } else { +            autostart = 0; +        } +    } + +    blkconf_serial(&s->conf, &dev->serial); +    blkconf_blocksizes(&s->conf); + +    /* +     * Hack alert: this pretends to be a block device, but it's really +     * a SCSI bus that can serve only a single device, which it +     * creates automatically.  But first it needs to detach from its +     * blockdev, or else scsi_bus_legacy_add_drive() dies when it +     * attaches again. +     * +     * The hack is probably a bad idea. +     */ +    blk_detach_dev(blk, &s->dev.qdev); +    s->conf.blk = NULL; + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); +    scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev), +                 &usb_msd_scsi_info_storage, NULL); +    scsi_dev = scsi_bus_legacy_add_drive(&s->bus, blk, 0, !!s->removable, +                                         s->conf.bootindex, dev->serial, +                                         &err); +    if (!scsi_dev) { +        error_propagate(errp, err); +        return; +    } +    usb_msd_handle_reset(dev); +    s->scsi_dev = scsi_dev; +} + +static void usb_msd_realize_bot(USBDevice *dev, Error **errp) +{ +    MSDState *s = USB_STORAGE_DEV(dev); + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); +    scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev), +                 &usb_msd_scsi_info_bot, NULL); +    usb_msd_handle_reset(dev); +} + +static USBDevice *usb_msd_init(USBBus *bus, const char *filename) +{ +    static int nr=0; +    Error *err = NULL; +    char id[8]; +    QemuOpts *opts; +    DriveInfo *dinfo; +    USBDevice *dev; +    const char *p1; +    char fmt[32]; + +    /* parse -usbdevice disk: syntax into drive opts */ +    do { +        snprintf(id, sizeof(id), "usb%d", nr++); +        opts = qemu_opts_create(qemu_find_opts("drive"), id, 1, NULL); +    } while (!opts); + +    p1 = strchr(filename, ':'); +    if (p1++) { +        const char *p2; + +        if (strstart(filename, "format=", &p2)) { +            int len = MIN(p1 - p2, sizeof(fmt)); +            pstrcpy(fmt, len, p2); +            qemu_opt_set(opts, "format", fmt, &error_abort); +        } else if (*filename != ':') { +            error_report("unrecognized USB mass-storage option %s", filename); +            return NULL; +        } +        filename = p1; +    } +    if (!*filename) { +        error_report("block device specification needed"); +        return NULL; +    } +    qemu_opt_set(opts, "file", filename, &error_abort); +    qemu_opt_set(opts, "if", "none", &error_abort); + +    /* create host drive */ +    dinfo = drive_new(opts, 0); +    if (!dinfo) { +        qemu_opts_del(opts); +        return NULL; +    } + +    /* create guest device */ +    dev = usb_create(bus, "usb-storage"); +    qdev_prop_set_drive(&dev->qdev, "drive", blk_by_legacy_dinfo(dinfo), +                        &err); +    if (err) { +        error_report_err(err); +        object_unparent(OBJECT(dev)); +        return NULL; +    } +    return dev; +} + +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 Property msd_properties[] = { +    DEFINE_BLOCK_PROPERTIES(MSDState, conf), +    DEFINE_PROP_BIT("removable", MSDState, removable, 0, false), +    DEFINE_PROP_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 void usb_msd_class_initfn_storage(ObjectClass *klass, void *data) +{ +    DeviceClass *dc = DEVICE_CLASS(klass); +    USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + +    uc->realize = usb_msd_realize_storage; +    dc->props = msd_properties; +} + +static void usb_msd_get_bootindex(Object *obj, Visitor *v, void *opaque, +                                  const char *name, Error **errp) +{ +    USBDevice *dev = USB_DEVICE(obj); +    MSDState *s = USB_STORAGE_DEV(dev); + +    visit_type_int32(v, &s->conf.bootindex, name, errp); +} + +static void usb_msd_set_bootindex(Object *obj, Visitor *v, void *opaque, +                                  const char *name, Error **errp) +{ +    USBDevice *dev = USB_DEVICE(obj); +    MSDState *s = USB_STORAGE_DEV(dev); +    int32_t boot_index; +    Error *local_err = NULL; + +    visit_type_int32(v, &boot_index, name, &local_err); +    if (local_err) { +        goto out; +    } +    /* 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), boot_index, "bootindex", +                                &error_abort); +    } + +out: +    if (local_err) { +        error_propagate(errp, local_err); +    } +} + +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_instance_init(Object *obj) +{ +    object_property_add(obj, "bootindex", "int32", +                        usb_msd_get_bootindex, +                        usb_msd_set_bootindex, NULL, NULL, NULL); +    object_property_set_int(obj, -1, "bootindex", NULL); +} + +static void usb_msd_class_initfn_bot(ObjectClass *klass, void *data) +{ +    USBDeviceClass *uc = USB_DEVICE_CLASS(klass); +    DeviceClass *dc = DEVICE_CLASS(klass); + +    uc->realize = usb_msd_realize_bot; +    dc->hotpluggable = false; +} + +static const TypeInfo msd_info = { +    .name          = "usb-storage", +    .parent        = TYPE_USB_STORAGE, +    .class_init    = usb_msd_class_initfn_storage, +    .instance_init = usb_msd_instance_init, +}; + +static const TypeInfo bot_info = { +    .name          = "usb-bot", +    .parent        = TYPE_USB_STORAGE, +    .class_init    = usb_msd_class_initfn_bot, +}; + +static void usb_msd_register_types(void) +{ +    type_register_static(&usb_storage_dev_type_info); +    type_register_static(&msd_info); +    type_register_static(&bot_info); +    usb_legacy_register("usb-storage", "disk", usb_msd_init); +} + +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 00000000..38b26c58 --- /dev/null +++ b/hw/usb/dev-uas.c @@ -0,0 +1,960 @@ +/* + * 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-common.h" +#include "qemu/option.h" +#include "qemu/config-file.h" +#include "trace.h" +#include "qemu/error-report.h" + +#include "hw/usb.h" +#include "hw/usb/desc.h" +#include "hw/scsi/scsi.h" +#include "block/scsi.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[]; +} 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" +#define USB_UAS(obj) OBJECT_CHECK(UASDevice, (obj), TYPE_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               = 64, +    .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, +                                          uint32_t status, size_t resid) +{ +    UASRequest *req = r->hba_private; + +    trace_usb_uas_scsi_complete(req->uas->dev.addr, req->tag, status, resid); +    req->complete = true; +    if (req->data) { +        usb_uas_complete_data_packet(req); +    } +    usb_uas_queue_sense(req, 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", __func__); +    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 (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; + +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) { +            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) { +            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; +    } +} + +static void usb_uas_handle_destroy(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); + +    usb_desc_create_serial(dev); +    usb_desc_init(dev); + +    QTAILQ_INIT(&uas->results); +    QTAILQ_INIT(&uas->requests); +    uas->status_bh = qemu_bh_new(usb_uas_send_status_bh, uas); + +    scsi_bus_new(&uas->bus, sizeof(uas->bus), DEVICE(dev), +                 &usb_uas_scsi_info, NULL); +} + +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->handle_destroy = usb_uas_handle_destroy; +    set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); +    dc->fw_name = "storage"; +    dc->vmsd = &vmstate_usb_uas; +    dc->props = 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 00000000..c2450e72 --- /dev/null +++ b/hw/usb/dev-wacom.c @@ -0,0 +1,385 @@ +/* + * Wacom PenPartner USB tablet emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Author: Andrzej Zaborowski <balrog@zabor.org> + * + * Based on hw/usb-hid.c: + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/usb.h" +#include "hw/usb/desc.h" + +/* Interface requests */ +#define WACOM_GET_REPORT	0x2101 +#define WACOM_SET_REPORT	0x2109 + +/* HID interface requests */ +#define HID_GET_REPORT		0xa101 +#define HID_GET_IDLE		0xa102 +#define HID_GET_PROTOCOL	0xa103 +#define HID_SET_IDLE		0x210a +#define HID_SET_PROTOCOL	0x210b + +typedef struct USBWacomState { +    USBDevice dev; +    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; +} USBWacomState; + +#define TYPE_USB_WACOM "usb-wacom-tablet" +#define USB_WACOM(obj) OBJECT_CHECK(USBWacomState, (obj), TYPE_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 */ +                0x21,          /*  u8  bDescriptorType */ +                0x01, 0x10,    /*  u16 HID_class */ +                0x00,          /*  u8  country_code */ +                0x01,          /*  u8  num_descriptors */ +                0x22,          /*  u8  type: Report */ +                0x6e, 0,       /*  u16 len */ +            }, +        }, +    }, +    .eps = (USBDescEndpoint[]) { +        { +            .bEndpointAddress      = USB_DIR_IN | 0x01, +            .bmAttributes          = USB_ENDPOINT_XFER_INT, +            .wMaxPacketSize        = 8, +            .bInterval             = 0x0a, +        }, +    }, +}; + +static const USBDescDevice desc_device_wacom = { +    .bcdUSB                        = 0x0110, +    .bMaxPacketSize0               = 8, +    .bNumConfigurations            = 1, +    .confs = (USBDescConfig[]) { +        { +            .bNumInterfaces        = 1, +            .bConfigurationValue   = 1, +            .bmAttributes          = 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; +    uint8_t buf[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_handle_destroy(USBDevice *dev) +{ +    USBWacomState *s = (USBWacomState *) dev; + +    if (s->mouse_grabbed) { +        qemu_remove_mouse_event_handler(s->eh_entry); +        s->mouse_grabbed = 0; +    } +} + +static 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->handle_destroy = usb_wacom_handle_destroy; +    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-ehci-pci.c b/hw/usb/hcd-ehci-pci.c new file mode 100644 index 00000000..7afa5f9d --- /dev/null +++ b/hw/usb/hcd-ehci-pci.c @@ -0,0 +1,273 @@ +/* + * 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 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/usb/hcd-ehci.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_exit(PCIDevice *dev) +{ +    EHCIPCIState *i = PCI_EHCI(dev); +    EHCIState *s = &i->ehci; + +    usb_ehci_unrealize(s, DEVICE(dev), NULL); + +    if (s->irq) { +        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; +    dc->props = 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, +    .abstract = true, +    .class_init = ehci_class_init, +}; + +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) + +struct ehci_companions { +    const char *name; +    int func; +    int port; +}; + +static const struct ehci_companions ich9_1d[] = { +    { .name = "ich9-usb-uhci1", .func = 0, .port = 0 }, +    { .name = "ich9-usb-uhci2", .func = 1, .port = 2 }, +    { .name = "ich9-usb-uhci3", .func = 2, .port = 4 }, +}; + +static const struct ehci_companions ich9_1a[] = { +    { .name = "ich9-usb-uhci4", .func = 0, .port = 0 }, +    { .name = "ich9-usb-uhci5", .func = 1, .port = 2 }, +    { .name = "ich9-usb-uhci6", .func = 2, .port = 4 }, +}; + +int ehci_create_ich9_with_companions(PCIBus *bus, int slot) +{ +    const struct ehci_companions *comp; +    PCIDevice *ehci, *uhci; +    BusState *usbbus; +    const char *name; +    int i; + +    switch (slot) { +    case 0x1d: +        name = "ich9-usb-ehci1"; +        comp = ich9_1d; +        break; +    case 0x1a: +        name = "ich9-usb-ehci2"; +        comp = ich9_1a; +        break; +    default: +        return -1; +    } + +    ehci = pci_create_multifunction(bus, PCI_DEVFN(slot, 7), true, name); +    qdev_init_nofail(&ehci->qdev); +    usbbus = QLIST_FIRST(&ehci->qdev.child_bus); + +    for (i = 0; i < 3; i++) { +        uhci = pci_create_multifunction(bus, PCI_DEVFN(slot, comp[i].func), +                                        true, comp[i].name); +        qdev_prop_set_string(&uhci->qdev, "masterbus", usbbus->name); +        qdev_prop_set_uint32(&uhci->qdev, "firstport", comp[i].port); +        qdev_init_nofail(&uhci->qdev); +    } +    return 0; +} diff --git a/hw/usb/hcd-ehci-sysbus.c b/hw/usb/hcd-ehci-sysbus.c new file mode 100644 index 00000000..cd1cc142 --- /dev/null +++ b/hw/usb/hcd-ehci-sysbus.c @@ -0,0 +1,229 @@ +/* + * 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 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/usb/hcd-ehci.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_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_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; +    dc->props = 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, +    .abstract      = true, +    .class_init    = ehci_sysbus_class_init, +    .class_size    = sizeof(SysBusEHCIClass), +}; + +static void ehci_xlnx_class_init(ObjectClass *oc, void *data) +{ +    SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc); +    DeviceClass *dc = DEVICE_CLASS(oc); + +    set_bit(DEVICE_CATEGORY_USB, dc->categories); +    sec->capsbase = 0x100; +    sec->opregbase = 0x140; +} + +static const TypeInfo ehci_xlnx_type_info = { +    .name          = "xlnx,ps7-usb", +    .parent        = TYPE_SYS_BUS_EHCI, +    .class_init    = ehci_xlnx_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_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, +}; + +/* + * 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_xlnx_type_info); +    type_register_static(&ehci_exynos4210_type_info); +    type_register_static(&ehci_tegra2_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 00000000..25c225ae --- /dev/null +++ b/hw/usb/hcd-ehci.c @@ -0,0 +1,2534 @@ +/* + * 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 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/usb/ehci-regs.h" +#include "hw/usb/hcd-ehci.h" +#include "trace.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); +    fprintf(stderr, "ehci warning: %s\n", 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 |= 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; + +    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 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 (%d)\n", cpage); +            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(); +        break; +    } + +    /* 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); +        usb_packet_map(&p->packet, &p->sgl); +        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, xfers = 0; +    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; +            ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK); +            ptr2 = (itd->bufptr[pg+1] & ITD_BUFPTR_MASK); +            len  = get_field(itd->transact[i], ITD_XACT_LENGTH); + +            if (len > max * mult) { +                len = max * mult; +            } + +            if (len > BUFF_SIZE) { +                return -1; +            } + +            qemu_sglist_init(&ehci->isgl, ehci->device, 2, ehci->as); +            if (off + len > 4096) { +                /* transfer crosses page border */ +                uint32_t len2 = off + len - 4096; +                uint32_t len1 = len - len2; +                qemu_sglist_add(&ehci->isgl, ptr1 + off, len1); +                qemu_sglist_add(&ehci->isgl, ptr2, len2); +            } else { +                qemu_sglist_add(&ehci->isgl, ptr1 + off, len); +            } + +            pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT; + +            dev = ehci_find_device(ehci, devaddr); +            ep = usb_ep_get(dev, pid, endp); +            if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) { +                usb_packet_setup(&ehci->ipacket, pid, ep, 0, addr, false, +                                 (itd->transact[i] & ITD_XACT_IOC) != 0); +                usb_packet_map(&ehci->ipacket, &ehci->isgl); +                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; +            xfers++; +        } +    } +    return xfers ? 0 : -1; +} + + +/*  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 %d " +                "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry)); +        return -1; +    } + +out: +    return again; +} + +static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) +{ +    uint32_t entry; +    EHCIQueue *q; +    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->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 */ +        fprintf(stderr, "WARNING: Skipping active siTD\n"); +    } + +    ehci_set_fetch_addr(ehci, async, sitd.next); +    ehci_set_state(ehci, async, EST_FETCHENTRY); +    return 1; +} + +/* Section 4.10.2 - paragraph 3 */ +static int ehci_state_advqueue(EHCIQueue *q) +{ +#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; + +    if (get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd, +                   sizeof(EHCIqtd) >> 2) < 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, pkts_head)); +            /* 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 { +        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 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); +            break; + +        case EST_FETCHSITD: +            again = ehci_state_fetchsitd(ehci, async); +            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"); +            again = -1; +            g_assert_not_reached(); +            break; +        } + +        if (again < 0) { +            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) +{ +    int i; + +    if (!ehci_enabled(ehci) && ehci->pstate == EST_INACTIVE) { +        return; +    } + +    for (i = 0; i < uframes; i++) { +        ehci->frindex++; + +        if (ehci->frindex == 0x00002000) { +            ehci_raise_irq(ehci, USBSTS_FLR); +        } + +        if (ehci->frindex == 0x00004000) { +            ehci_raise_irq(ehci, USBSTS_FLR); +            ehci->frindex = 0; +            if (ehci->usbsts_frindex >= 0x00004000) { +                ehci->usbsts_frindex -= 0x00004000; +            } else { +                ehci->usbsts_frindex = 0; +            } +        } +    } +} + +static void ehci_frame_timer(void *opaque) +{ +    EHCIState *ehci = opaque; +    int need_timer = 0; +    int64_t expire_time, t_now; +    uint64_t ns_elapsed; +    int uframes, skipped_uframes; +    int i; + +    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 + get_ticks_per_sec() / (FRAME_TIMER_FREQ * 4); +            ehci->int_req_by_async = false; +        } else { +            expire_time = t_now + (get_ticks_per_sec() +                               * (ehci->async_stepdown+1) / FRAME_TIMER_FREQ); +        } +        timer_mod(ehci->frame_timer, expire_time); +    } +} + +static const MemoryRegionOps ehci_mmio_caps_ops = { +    .read = ehci_caps_read, +    .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 void 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; +} + +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, int 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; +    } + +    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_frame_timer, s); +    s->async_bh = qemu_bh_new(ehci_frame_timer, 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, Error **errp) +{ +    trace_usb_ehci_unrealize(); + +    if (s->frame_timer) { +        timer_del(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); + +    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); +} + +/* + * vim: expandtab ts=4 + */ diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h new file mode 100644 index 00000000..30218423 --- /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 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HW_USB_EHCI_H +#define HW_USB_EHCI_H 1 + +#include "hw/hw.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "sysemu/dma.h" +#include "sysemu/sysemu.h" +#include "hw/pci/pci.h" +#include "hw/sysbus.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(pkts_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; +    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_realize(EHCIState *s, DeviceState *dev, Error **errp); +void usb_ehci_unrealize(EHCIState *s, DeviceState *dev, Error **errp); +void ehci_reset(void *opaque); + +#define TYPE_PCI_EHCI "pci-ehci-usb" +#define PCI_EHCI(obj) OBJECT_CHECK(EHCIPCIState, (obj), TYPE_PCI_EHCI) + +typedef struct EHCIPCIState { +    /*< private >*/ +    PCIDevice pcidev; +    /*< public >*/ + +    EHCIState ehci; +} EHCIPCIState; + + +#define TYPE_SYS_BUS_EHCI "sysbus-ehci-usb" +#define TYPE_EXYNOS4210_EHCI "exynos4210-ehci-usb" +#define TYPE_TEGRA2_EHCI "tegra2-ehci-usb" +#define TYPE_FUSBH200_EHCI "fusbh200-ehci-usb" + +#define SYS_BUS_EHCI(obj) \ +    OBJECT_CHECK(EHCISysBusState, (obj), TYPE_SYS_BUS_EHCI) +#define SYS_BUS_EHCI_CLASS(class) \ +    OBJECT_CLASS_CHECK(SysBusEHCIClass, (class), TYPE_SYS_BUS_EHCI) +#define SYS_BUS_EHCI_GET_CLASS(obj) \ +    OBJECT_GET_CLASS(SysBusEHCIClass, (obj), TYPE_SYS_BUS_EHCI) + +typedef struct EHCISysBusState { +    /*< private >*/ +    SysBusDevice parent_obj; +    /*< public >*/ + +    EHCIState ehci; +} EHCISysBusState; + +typedef struct SysBusEHCIClass { +    /*< private >*/ +    SysBusDeviceClass parent_class; +    /*< public >*/ + +    uint16_t capsbase; +    uint16_t opregbase; +    uint16_t portscbase; +    uint16_t portnr; +} SysBusEHCIClass; + +#define FUSBH200_EHCI(obj) \ +    OBJECT_CHECK(FUSBH200EHCIState, (obj), TYPE_FUSBH200_EHCI) + +typedef struct FUSBH200EHCIState { +    /*< private >*/ +    EHCISysBusState parent_obj; +    /*< public >*/ + +    MemoryRegion mem_vendor; +} FUSBH200EHCIState; + +#endif diff --git a/hw/usb/hcd-musb.c b/hw/usb/hcd-musb.c new file mode 100644 index 00000000..61cc8789 --- /dev/null +++ b/hw/usb/hcd-musb.c @@ -0,0 +1,1552 @@ +/* + * "Inventra" High-speed Dual-Role Controller (MUSB-HDRC), Mentor Graphics, + * USB2.0 OTG compliant core used in various chips. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Only host-mode and non-DMA accesses are currently supported. + */ +#include "qemu-common.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "hw/irq.h" +#include "hw/hw.h" + +/* Common USB registers */ +#define MUSB_HDRC_FADDR		0x00	/* 8-bit */ +#define MUSB_HDRC_POWER		0x01	/* 8-bit */ + +#define MUSB_HDRC_INTRTX	0x02	/* 16-bit */ +#define MUSB_HDRC_INTRRX	0x04 +#define MUSB_HDRC_INTRTXE	0x06   +#define MUSB_HDRC_INTRRXE	0x08   +#define MUSB_HDRC_INTRUSB	0x0a	/* 8 bit */ +#define MUSB_HDRC_INTRUSBE	0x0b	/* 8 bit */ +#define MUSB_HDRC_FRAME		0x0c	/* 16-bit */ +#define MUSB_HDRC_INDEX		0x0e	/* 8 bit */ +#define MUSB_HDRC_TESTMODE	0x0f	/* 8 bit */ + +/* Per-EP registers in indexed mode */ +#define MUSB_HDRC_EP_IDX	0x10	/* 8-bit */ + +/* EP FIFOs */ +#define MUSB_HDRC_FIFO		0x20 + +/* Additional Control Registers */ +#define	MUSB_HDRC_DEVCTL	0x60	/* 8 bit */ + +/* These are indexed */ +#define MUSB_HDRC_TXFIFOSZ	0x62	/* 8 bit (see masks) */ +#define MUSB_HDRC_RXFIFOSZ	0x63	/* 8 bit (see masks) */ +#define MUSB_HDRC_TXFIFOADDR	0x64	/* 16 bit offset shifted right 3 */ +#define MUSB_HDRC_RXFIFOADDR	0x66	/* 16 bit offset shifted right 3 */ + +/* Some more registers */ +#define MUSB_HDRC_VCTRL		0x68	/* 8 bit */ +#define MUSB_HDRC_HWVERS	0x6c	/* 8 bit */ + +/* Added in HDRC 1.9(?) & MHDRC 1.4 */ +/* ULPI pass-through */ +#define MUSB_HDRC_ULPI_VBUSCTL	0x70 +#define MUSB_HDRC_ULPI_REGDATA	0x74 +#define MUSB_HDRC_ULPI_REGADDR	0x75 +#define MUSB_HDRC_ULPI_REGCTL	0x76 + +/* Extended config & PHY control */ +#define MUSB_HDRC_ENDCOUNT	0x78	/* 8 bit */ +#define MUSB_HDRC_DMARAMCFG	0x79	/* 8 bit */ +#define MUSB_HDRC_PHYWAIT	0x7a	/* 8 bit */ +#define MUSB_HDRC_PHYVPLEN	0x7b	/* 8 bit */ +#define MUSB_HDRC_HS_EOF1	0x7c	/* 8 bit, units of 546.1 us */ +#define MUSB_HDRC_FS_EOF1	0x7d	/* 8 bit, units of 533.3 ns */ +#define MUSB_HDRC_LS_EOF1	0x7e	/* 8 bit, units of 1.067 us */ + +/* Per-EP BUSCTL registers */ +#define MUSB_HDRC_BUSCTL	0x80 + +/* Per-EP registers in flat mode */ +#define MUSB_HDRC_EP		0x100 + +/* offsets to registers in flat model */ +#define MUSB_HDRC_TXMAXP	0x00	/* 16 bit apparently */ +#define MUSB_HDRC_TXCSR		0x02	/* 16 bit apparently */ +#define MUSB_HDRC_CSR0		MUSB_HDRC_TXCSR		/* re-used for EP0 */ +#define MUSB_HDRC_RXMAXP	0x04	/* 16 bit apparently */ +#define MUSB_HDRC_RXCSR		0x06	/* 16 bit apparently */ +#define MUSB_HDRC_RXCOUNT	0x08	/* 16 bit apparently */ +#define MUSB_HDRC_COUNT0	MUSB_HDRC_RXCOUNT	/* re-used for EP0 */ +#define MUSB_HDRC_TXTYPE	0x0a	/* 8 bit apparently */ +#define MUSB_HDRC_TYPE0		MUSB_HDRC_TXTYPE	/* re-used for EP0 */ +#define MUSB_HDRC_TXINTERVAL	0x0b	/* 8 bit apparently */ +#define MUSB_HDRC_NAKLIMIT0	MUSB_HDRC_TXINTERVAL	/* re-used for EP0 */ +#define MUSB_HDRC_RXTYPE	0x0c	/* 8 bit apparently */ +#define MUSB_HDRC_RXINTERVAL	0x0d	/* 8 bit apparently */ +#define MUSB_HDRC_FIFOSIZE	0x0f	/* 8 bit apparently */ +#define MUSB_HDRC_CONFIGDATA	MGC_O_HDRC_FIFOSIZE	/* re-used for EP0 */ + +/* "Bus control" registers */ +#define MUSB_HDRC_TXFUNCADDR	0x00 +#define MUSB_HDRC_TXHUBADDR	0x02 +#define MUSB_HDRC_TXHUBPORT	0x03 + +#define MUSB_HDRC_RXFUNCADDR	0x04 +#define MUSB_HDRC_RXHUBADDR	0x06 +#define MUSB_HDRC_RXHUBPORT	0x07 + +/* + * MUSBHDRC Register bit masks + */ + +/* POWER */ +#define MGC_M_POWER_ISOUPDATE		0x80  +#define	MGC_M_POWER_SOFTCONN		0x40 +#define	MGC_M_POWER_HSENAB		0x20 +#define	MGC_M_POWER_HSMODE		0x10 +#define MGC_M_POWER_RESET		0x08 +#define MGC_M_POWER_RESUME		0x04 +#define MGC_M_POWER_SUSPENDM		0x02 +#define MGC_M_POWER_ENSUSPEND		0x01 + +/* INTRUSB */ +#define MGC_M_INTR_SUSPEND		0x01 +#define MGC_M_INTR_RESUME		0x02 +#define MGC_M_INTR_RESET		0x04 +#define MGC_M_INTR_BABBLE		0x04 +#define MGC_M_INTR_SOF			0x08  +#define MGC_M_INTR_CONNECT		0x10 +#define MGC_M_INTR_DISCONNECT		0x20 +#define MGC_M_INTR_SESSREQ		0x40 +#define MGC_M_INTR_VBUSERROR		0x80	/* FOR SESSION END */ +#define MGC_M_INTR_EP0			0x01	/* FOR EP0 INTERRUPT */ + +/* DEVCTL */ +#define MGC_M_DEVCTL_BDEVICE		0x80    +#define MGC_M_DEVCTL_FSDEV		0x40 +#define MGC_M_DEVCTL_LSDEV		0x20 +#define MGC_M_DEVCTL_VBUS		0x18 +#define MGC_S_DEVCTL_VBUS		3 +#define MGC_M_DEVCTL_HM			0x04 +#define MGC_M_DEVCTL_HR			0x02 +#define MGC_M_DEVCTL_SESSION		0x01 + +/* TESTMODE */ +#define MGC_M_TEST_FORCE_HOST		0x80 +#define MGC_M_TEST_FIFO_ACCESS		0x40 +#define MGC_M_TEST_FORCE_FS		0x20 +#define MGC_M_TEST_FORCE_HS		0x10 +#define MGC_M_TEST_PACKET		0x08 +#define MGC_M_TEST_K			0x04 +#define MGC_M_TEST_J			0x02 +#define MGC_M_TEST_SE0_NAK		0x01 + +/* CSR0 */ +#define	MGC_M_CSR0_FLUSHFIFO		0x0100 +#define MGC_M_CSR0_TXPKTRDY		0x0002 +#define MGC_M_CSR0_RXPKTRDY		0x0001 + +/* CSR0 in Peripheral mode */ +#define MGC_M_CSR0_P_SVDSETUPEND	0x0080 +#define MGC_M_CSR0_P_SVDRXPKTRDY	0x0040 +#define MGC_M_CSR0_P_SENDSTALL		0x0020 +#define MGC_M_CSR0_P_SETUPEND		0x0010 +#define MGC_M_CSR0_P_DATAEND		0x0008 +#define MGC_M_CSR0_P_SENTSTALL		0x0004 + +/* CSR0 in Host mode */ +#define MGC_M_CSR0_H_NO_PING		0x0800 +#define MGC_M_CSR0_H_WR_DATATOGGLE	0x0400	/* set to allow setting: */ +#define MGC_M_CSR0_H_DATATOGGLE		0x0200	/* data toggle control */ +#define	MGC_M_CSR0_H_NAKTIMEOUT		0x0080 +#define MGC_M_CSR0_H_STATUSPKT		0x0040 +#define MGC_M_CSR0_H_REQPKT		0x0020 +#define MGC_M_CSR0_H_ERROR		0x0010 +#define MGC_M_CSR0_H_SETUPPKT		0x0008 +#define MGC_M_CSR0_H_RXSTALL		0x0004 + +/* CONFIGDATA */ +#define MGC_M_CONFIGDATA_MPRXE		0x80	/* auto bulk pkt combining */ +#define MGC_M_CONFIGDATA_MPTXE		0x40	/* auto bulk pkt splitting */ +#define MGC_M_CONFIGDATA_BIGENDIAN	0x20 +#define MGC_M_CONFIGDATA_HBRXE		0x10	/* HB-ISO for RX */ +#define MGC_M_CONFIGDATA_HBTXE		0x08	/* HB-ISO for TX */ +#define MGC_M_CONFIGDATA_DYNFIFO	0x04	/* dynamic FIFO sizing */ +#define MGC_M_CONFIGDATA_SOFTCONE	0x02	/* SoftConnect */ +#define MGC_M_CONFIGDATA_UTMIDW		0x01	/* Width, 0 => 8b, 1 => 16b */ + +/* TXCSR in Peripheral and Host mode */ +#define MGC_M_TXCSR_AUTOSET		0x8000 +#define MGC_M_TXCSR_ISO			0x4000 +#define MGC_M_TXCSR_MODE		0x2000 +#define MGC_M_TXCSR_DMAENAB		0x1000 +#define MGC_M_TXCSR_FRCDATATOG		0x0800 +#define MGC_M_TXCSR_DMAMODE		0x0400 +#define MGC_M_TXCSR_CLRDATATOG		0x0040 +#define MGC_M_TXCSR_FLUSHFIFO		0x0008 +#define MGC_M_TXCSR_FIFONOTEMPTY	0x0002 +#define MGC_M_TXCSR_TXPKTRDY		0x0001 + +/* TXCSR in Peripheral mode */ +#define MGC_M_TXCSR_P_INCOMPTX		0x0080 +#define MGC_M_TXCSR_P_SENTSTALL		0x0020 +#define MGC_M_TXCSR_P_SENDSTALL		0x0010 +#define MGC_M_TXCSR_P_UNDERRUN		0x0004 + +/* TXCSR in Host mode */ +#define MGC_M_TXCSR_H_WR_DATATOGGLE	0x0200 +#define MGC_M_TXCSR_H_DATATOGGLE	0x0100 +#define MGC_M_TXCSR_H_NAKTIMEOUT	0x0080 +#define MGC_M_TXCSR_H_RXSTALL		0x0020 +#define MGC_M_TXCSR_H_ERROR		0x0004 + +/* RXCSR in Peripheral and Host mode */ +#define MGC_M_RXCSR_AUTOCLEAR		0x8000 +#define MGC_M_RXCSR_DMAENAB		0x2000 +#define MGC_M_RXCSR_DISNYET		0x1000 +#define MGC_M_RXCSR_DMAMODE		0x0800 +#define MGC_M_RXCSR_INCOMPRX		0x0100 +#define MGC_M_RXCSR_CLRDATATOG		0x0080 +#define MGC_M_RXCSR_FLUSHFIFO		0x0010 +#define MGC_M_RXCSR_DATAERROR		0x0008 +#define MGC_M_RXCSR_FIFOFULL		0x0002 +#define MGC_M_RXCSR_RXPKTRDY		0x0001 + +/* RXCSR in Peripheral mode */ +#define MGC_M_RXCSR_P_ISO		0x4000 +#define MGC_M_RXCSR_P_SENTSTALL		0x0040 +#define MGC_M_RXCSR_P_SENDSTALL		0x0020 +#define MGC_M_RXCSR_P_OVERRUN		0x0004 + +/* RXCSR in Host mode */ +#define MGC_M_RXCSR_H_AUTOREQ		0x4000 +#define MGC_M_RXCSR_H_WR_DATATOGGLE	0x0400 +#define MGC_M_RXCSR_H_DATATOGGLE	0x0200 +#define MGC_M_RXCSR_H_RXSTALL		0x0040 +#define MGC_M_RXCSR_H_REQPKT		0x0020 +#define MGC_M_RXCSR_H_ERROR		0x0004 + +/* HUBADDR */ +#define MGC_M_HUBADDR_MULTI_TT		0x80 + +/* ULPI: Added in HDRC 1.9(?) & MHDRC 1.4 */ +#define MGC_M_ULPI_VBCTL_USEEXTVBUSIND	0x02 +#define MGC_M_ULPI_VBCTL_USEEXTVBUS	0x01 +#define MGC_M_ULPI_REGCTL_INT_ENABLE	0x08 +#define MGC_M_ULPI_REGCTL_READNOTWRITE	0x04 +#define MGC_M_ULPI_REGCTL_COMPLETE	0x02 +#define MGC_M_ULPI_REGCTL_REG		0x01 + +/* #define MUSB_DEBUG */ + +#ifdef MUSB_DEBUG +#define TRACE(fmt,...) fprintf(stderr, "%s@%d: " fmt "\n", __FUNCTION__, \ +                               __LINE__, ##__VA_ARGS__) +#else +#define TRACE(...) +#endif + + +static void musb_attach(USBPort *port); +static void musb_detach(USBPort *port); +static void musb_child_detach(USBPort *port, USBDevice *child); +static void musb_schedule_cb(USBPort *port, USBPacket *p); +static void musb_async_cancel_device(MUSBState *s, USBDevice *dev); + +static USBPortOps musb_port_ops = { +    .attach = musb_attach, +    .detach = musb_detach, +    .child_detach = musb_child_detach, +    .complete = musb_schedule_cb, +}; + +static USBBusOps musb_bus_ops = { +}; + +typedef struct MUSBPacket MUSBPacket; +typedef struct MUSBEndPoint MUSBEndPoint; + +struct MUSBPacket { +    USBPacket p; +    MUSBEndPoint *ep; +    int dir; +}; + +struct MUSBEndPoint { +    uint16_t faddr[2]; +    uint8_t haddr[2]; +    uint8_t hport[2]; +    uint16_t csr[2]; +    uint16_t maxp[2]; +    uint16_t rxcount; +    uint8_t type[2]; +    uint8_t interval[2]; +    uint8_t config; +    uint8_t fifosize; +    int timeout[2];	/* Always in microframes */ + +    uint8_t *buf[2]; +    int fifolen[2]; +    int fifostart[2]; +    int fifoaddr[2]; +    MUSBPacket packey[2]; +    int status[2]; +    int ext_size[2]; + +    /* For callbacks' use */ +    int epnum; +    int interrupt[2]; +    MUSBState *musb; +    USBCallback *delayed_cb[2]; +    QEMUTimer *intv_timer[2]; +}; + +struct MUSBState { +    qemu_irq irqs[musb_irq_max]; +    USBBus bus; +    USBPort port; + +    int idx; +    uint8_t devctl; +    uint8_t power; +    uint8_t faddr; + +    uint8_t intr; +    uint8_t mask; +    uint16_t tx_intr; +    uint16_t tx_mask; +    uint16_t rx_intr; +    uint16_t rx_mask; + +    int setup_len; +    int session; + +    uint8_t buf[0x8000]; + +        /* Duplicating the world since 2008!...  probably we should have 32 +         * logical, single endpoints instead.  */ +    MUSBEndPoint ep[16]; +}; + +void musb_reset(MUSBState *s) +{ +    int i; + +    s->faddr = 0x00; +    s->devctl = 0; +    s->power = MGC_M_POWER_HSENAB; +    s->tx_intr = 0x0000; +    s->rx_intr = 0x0000; +    s->tx_mask = 0xffff; +    s->rx_mask = 0xffff; +    s->intr = 0x00; +    s->mask = 0x06; +    s->idx = 0; + +    s->setup_len = 0; +    s->session = 0; +    memset(s->buf, 0, sizeof(s->buf)); + +    /* TODO: _DW */ +    s->ep[0].config = MGC_M_CONFIGDATA_SOFTCONE | MGC_M_CONFIGDATA_DYNFIFO; +    for (i = 0; i < 16; i ++) { +        s->ep[i].fifosize = 64; +        s->ep[i].maxp[0] = 0x40; +        s->ep[i].maxp[1] = 0x40; +        s->ep[i].musb = s; +        s->ep[i].epnum = i; +        usb_packet_init(&s->ep[i].packey[0].p); +        usb_packet_init(&s->ep[i].packey[1].p); +    } +} + +struct MUSBState *musb_init(DeviceState *parent_device, int gpio_base) +{ +    MUSBState *s = g_malloc0(sizeof(*s)); +    int i; + +    for (i = 0; i < musb_irq_max; i++) { +        s->irqs[i] = qdev_get_gpio_in(parent_device, gpio_base + i); +    } + +    musb_reset(s); + +    usb_bus_new(&s->bus, 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, get_ticks_per_sec(), 8000)); +} + +static int musb_timeout(int ttype, int speed, int val) +{ +#if 1 +    return val << 3; +#endif + +    switch (ttype) { +    case USB_ENDPOINT_XFER_CONTROL: +        if (val < 2) +            return 0; +        else if (speed == USB_SPEED_HIGH) +            return 1 << (val - 1); +        else +            return 8 << (val - 1); + +    case USB_ENDPOINT_XFER_INT: +        if (speed == USB_SPEED_HIGH) +            if (val < 2) +                return 0; +            else +                return 1 << (val - 1); +        else +            return val << 3; + +    case USB_ENDPOINT_XFER_BULK: +    case USB_ENDPOINT_XFER_ISOC: +        if (val < 2) +            return 0; +        else if (speed == USB_SPEED_HIGH) +            return 1 << (val - 1); +        else +            return 8 << (val - 1); +        /* TODO: what with low-speed Bulk and Isochronous?  */ +    } + +    hw_error("bad interval\n"); +} + +static void musb_packet(MUSBState *s, MUSBEndPoint *ep, +                int epnum, int pid, int len, USBCallback cb, int dir) +{ +    USBDevice *dev; +    USBEndpoint *uep; +    int 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]); +    uep = usb_ep_get(dev, pid, ep->type[idx] & 0xf); +    id = pid; +    if (uep) { +        id |= (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; +    }; +} + +CPUReadMemoryFunc * const musb_read[] = { +    musb_readb, +    musb_readh, +    musb_readw, +}; + +CPUWriteMemoryFunc * const musb_write[] = { +    musb_writeb, +    musb_writeh, +    musb_writew, +}; diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c new file mode 100644 index 00000000..7d658180 --- /dev/null +++ b/hw/usb/hcd-ohci.c @@ -0,0 +1,2157 @@ +/* + * QEMU USB OHCI Emulation + * Copyright (c) 2004 Gianni Tedesco + * Copyright (c) 2006 CodeSourcery + * Copyright (c) 2006 Openedhand Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * TODO: + *  o Isochronous transfers + *  o Allocate bandwidth in frames properly + *  o Disable timers when nothing needs to be done, or remove timer usage + *    all together. + *  o BIOS work to boot from USB storage +*/ + +#include "hw/hw.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "hw/pci/pci.h" +#include "hw/sysbus.h" +#include "hw/qdev-dma.h" +#include "trace.h" + +/* This causes frames to occur 1000x slower */ +//#define OHCI_TIME_WARP 1 + +/* Number of Downstream Ports on the root hub.  */ + +#define OHCI_MAX_PORTS 15 + +static int64_t usb_frame_time; +static int64_t usb_bit_time; + +typedef struct OHCIPort { +    USBPort port; +    uint32_t ctrl; +} OHCIPort; + +typedef struct { +    USBBus bus; +    qemu_irq irq; +    MemoryRegion mem; +    AddressSpace *as; +    int num_ports; +    const char *name; + +    QEMUTimer *eof_timer; +    int64_t sof_time; + +    /* OHCI state */ +    /* Control partition */ +    uint32_t ctl, status; +    uint32_t intr_status; +    uint32_t intr; + +    /* memory pointer partition */ +    uint32_t hcca; +    uint32_t ctrl_head, ctrl_cur; +    uint32_t bulk_head, bulk_cur; +    uint32_t per_cur; +    uint32_t done; +    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; + +} OHCIState; + +/* 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_bus_stop(OHCIState *ohci); +static void ohci_async_cancel_device(OHCIState *ohci, USBDevice *dev); + +/* Bitfields for the first word of an Endpoint Desciptor.  */ +#define OHCI_ED_FA_SHIFT  0 +#define OHCI_ED_FA_MASK   (0x7f<<OHCI_ED_FA_SHIFT) +#define OHCI_ED_EN_SHIFT  7 +#define OHCI_ED_EN_MASK   (0xf<<OHCI_ED_EN_SHIFT) +#define OHCI_ED_D_SHIFT   11 +#define OHCI_ED_D_MASK    (3<<OHCI_ED_D_SHIFT) +#define OHCI_ED_S         (1<<13) +#define OHCI_ED_K         (1<<14) +#define OHCI_ED_F         (1<<15) +#define OHCI_ED_MPS_SHIFT 16 +#define OHCI_ED_MPS_MASK  (0x7ff<<OHCI_ED_MPS_SHIFT) + +/* Flags in the head field of an Endpoint Desciptor.  */ +#define OHCI_ED_H         1 +#define OHCI_ED_C         2 + +/* Bitfields for the first word of a Transfer Desciptor.  */ +#define OHCI_TD_R         (1<<18) +#define OHCI_TD_DP_SHIFT  19 +#define OHCI_TD_DP_MASK   (3<<OHCI_TD_DP_SHIFT) +#define OHCI_TD_DI_SHIFT  21 +#define OHCI_TD_DI_MASK   (7<<OHCI_TD_DI_SHIFT) +#define OHCI_TD_T0        (1<<24) +#define OHCI_TD_T1        (1<<25) +#define OHCI_TD_EC_SHIFT  26 +#define OHCI_TD_EC_MASK   (3<<OHCI_TD_EC_SHIFT) +#define OHCI_TD_CC_SHIFT  28 +#define OHCI_TD_CC_MASK   (0xf<<OHCI_TD_CC_SHIFT) + +/* Bitfields for the first word of an Isochronous Transfer Desciptor.  */ +/* CC & DI - same as in the General Transfer Desciptor */ +#define OHCI_TD_SF_SHIFT  0 +#define OHCI_TD_SF_MASK   (0xffff<<OHCI_TD_SF_SHIFT) +#define OHCI_TD_FC_SHIFT  24 +#define OHCI_TD_FC_MASK   (7<<OHCI_TD_FC_SHIFT) + +/* Isochronous Transfer Desciptor - Offset / PacketStatusWord */ +#define OHCI_TD_PSW_CC_SHIFT 12 +#define OHCI_TD_PSW_CC_MASK  (0xf<<OHCI_TD_PSW_CC_SHIFT) +#define OHCI_TD_PSW_SIZE_SHIFT 0 +#define OHCI_TD_PSW_SIZE_MASK  (0xfff<<OHCI_TD_PSW_SIZE_SHIFT) + +#define OHCI_PAGE_MASK    0xfffff000 +#define OHCI_OFFSET_MASK  0xfff + +#define OHCI_DPTR_MASK    0xfffffff0 + +#define OHCI_BM(val, field) \ +  (((val) & OHCI_##field##_MASK) >> OHCI_##field##_SHIFT) + +#define OHCI_SET_BM(val, field, newval) do { \ +    val &= ~OHCI_##field##_MASK; \ +    val |= ((newval) << OHCI_##field##_SHIFT) & OHCI_##field##_MASK; \ +    } while(0) + +/* endpoint descriptor */ +struct ohci_ed { +    uint32_t flags; +    uint32_t tail; +    uint32_t head; +    uint32_t next; +}; + +/* General transfer descriptor */ +struct ohci_td { +    uint32_t flags; +    uint32_t cbp; +    uint32_t next; +    uint32_t be; +}; + +/* Isochronous transfer descriptor */ +struct ohci_iso_td { +    uint32_t flags; +    uint32_t bp; +    uint32_t next; +    uint32_t be; +    uint16_t offset[8]; +}; + +#define USB_HZ                      12000000 + +/* OHCI Local stuff */ +#define OHCI_CTL_CBSR         ((1<<0)|(1<<1)) +#define OHCI_CTL_PLE          (1<<2) +#define OHCI_CTL_IE           (1<<3) +#define OHCI_CTL_CLE          (1<<4) +#define OHCI_CTL_BLE          (1<<5) +#define OHCI_CTL_HCFS         ((1<<6)|(1<<7)) +#define  OHCI_USB_RESET       0x00 +#define  OHCI_USB_RESUME      0x40 +#define  OHCI_USB_OPERATIONAL 0x80 +#define  OHCI_USB_SUSPEND     0xc0 +#define OHCI_CTL_IR           (1<<8) +#define OHCI_CTL_RWC          (1<<9) +#define OHCI_CTL_RWE          (1<<10) + +#define OHCI_STATUS_HCR       (1<<0) +#define OHCI_STATUS_CLF       (1<<1) +#define OHCI_STATUS_BLF       (1<<2) +#define OHCI_STATUS_OCR       (1<<3) +#define OHCI_STATUS_SOC       ((1<<6)|(1<<7)) + +#define OHCI_INTR_SO          (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); + +/* 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; +} + +static 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]); +            } +        } +    } +} + +/* Reset the controller */ +static void ohci_reset(void *opaque) +{ +    OHCIState *ohci = opaque; +    OHCIPort *port; +    int i; + +    ohci_bus_stop(ohci); +    ohci->ctl = 0; +    ohci->old_ctl = 0; +    ohci->status = 0; +    ohci->intr_status = 0; +    ohci->intr = OHCI_INTR_MIE; + +    ohci->hcca = 0; +    ohci->ctrl_head = ohci->ctrl_cur = 0; +    ohci->bulk_head = ohci->bulk_cur = 0; +    ohci->per_cur = 0; +    ohci->done = 0; +    ohci->done_count = 7; + +    /* FSMPS is marked TBD in OCHI 1.0, what gives ffs? +     * I took the value linux sets ... +     */ +    ohci->fsmps = 0x2778; +    ohci->fi = 0x2edf; +    ohci->fit = 0; +    ohci->frt = 0; +    ohci->frame_number = 0; +    ohci->pstart = 0; +    ohci->lst = OHCI_LS_THRESH; + +    ohci->rhdesc_a = OHCI_RHA_NPS | ohci->num_ports; +    ohci->rhdesc_b = 0x0; /* Impl. specific */ +    ohci->rhstatus = 0; + +    for (i = 0; i < ohci->num_ports; i++) +      { +        port = &ohci->rhport[i]; +        port->ctrl = 0; +        if (port->port.dev && port->port.dev->attached) { +            usb_port_reset(&port->port); +        } +      } +    if (ohci->async_td) { +        usb_cancel_packet(&ohci->usb_packet); +        ohci->async_td = 0; +    } +    ohci_stop_endpoints(ohci); +    trace_usb_ohci_reset(ohci->name); +} + +/* 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 0; +    } + +    starting_frame = OHCI_BM(iso_td.flags, TD_SF); +    frame_count = OHCI_BM(iso_td.flags, TD_FC); +    relative_frame_number = USUB(ohci->frame_number, starting_frame);  + +    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); +        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]; +    next_offset = iso_td.offset[relative_frame_number + 1]; + +    if (!(OHCI_BM(start_offset, TD_PSW_CC) & 0xe) ||  +        ((relative_frame_number < frame_count) &&  +         !(OHCI_BM(next_offset, TD_PSW_CC) & 0xe))) { +        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 = iso_td.be; +    } + +    if ((start_addr & OHCI_PAGE_MASK) != (end_addr & OHCI_PAGE_MASK)) { +        len = (end_addr & OHCI_OFFSET_MASK) + 0x1001 +            - (start_addr & OHCI_OFFSET_MASK); +    } else { +        len = end_addr - start_addr + 1; +    } + +    if (len && dir != OHCI_TD_DIR_IN) { +        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)); +        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; +} + +#ifdef trace_event_get_state +static void ohci_td_pkt(const char *msg, const uint8_t *buf, size_t len) +{ +    bool print16 = !!trace_event_get_state(TRACE_USB_OHCI_TD_PKT_SHORT); +    bool printall = !!trace_event_get_state(TRACE_USB_OHCI_TD_PKT_FULL); +    const int width = 16; +    int i; +    char tmp[3 * width + 1]; +    char *p = tmp; + +    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]); +    } +} +#else +static void ohci_td_pkt(const char *msg, const uint8_t *buf, size_t len) +{ +} +#endif + +/* 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 0; +    } + +    dir = OHCI_BM(ed->flags, ED_D); +    switch (dir) { +    case OHCI_TD_DIR_OUT: +    case OHCI_TD_DIR_IN: +        /* Same value.  */ +        break; +    default: +        dir = OHCI_BM(td.flags, TD_DP); +        break; +    } + +    switch (dir) { +    case OHCI_TD_DIR_IN: +        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 { +            len = (td.be - td.cbp) + 1; +        } + +        pktlen = len; +        if (len && dir != OHCI_TD_DIR_IN) { +            /* The endpoint may not allow us to transfer it all now */ +            pktlen = (ed->flags & OHCI_ED_MPS_MASK) >> OHCI_ED_MPS_SHIFT; +            if (pktlen > len) { +                pktlen = len; +            } +            if (!completion) { +                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)); +        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; +            } +        } +        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; + +    active = 0; + +    if (head == 0) +        return 0; + +    for (cur = head; cur; 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; +} + +/* Generate a SOF event, and set a timer for EOF */ +static void ohci_sof(OHCIState *ohci) +{ +    ohci->sof_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +    timer_mod(ohci->eof_timer, ohci->sof_time + usb_frame_time); +    ohci_set_interrupt(ohci, OHCI_INTR_SF); +} + +/* Process Control and Bulk lists.  */ +static void ohci_process_lists(OHCIState *ohci, int completion) +{ +    if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) { +        if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head) { +            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) +{ +    ohci->eof_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, +                    ohci_frame_boundary, +                    ohci); + +    if (ohci->eof_timer == NULL) { +        trace_usb_ohci_bus_eof_timer_failed(ohci->name); +        ohci_die(ohci); +        return 0; +    } + +    trace_usb_ohci_start(ohci->name); + +    ohci_sof(ohci); + +    return 1; +} + +/* Stop sending SOF tokens on the bus */ +static void ohci_bus_stop(OHCIState *ohci) +{ +    trace_usb_ohci_stop(ohci->name); +    if (ohci->eof_timer) { +        timer_del(ohci->eof_timer); +        timer_free(ohci->eof_timer); +    } +    ohci->eof_timer = NULL; +} + +/* Sets a flag in a port status register but only set it if the port is + * connected, if not set ConnectStatusChange flag. If flag is enabled + * return 1. + */ +static int ohci_port_set_if_connected(OHCIState *ohci, int i, uint32_t val) +{ +    int ret = 1; + +    /* writing a 0 has no effect */ +    if (val == 0) +        return 0; + +    /* If CurrentConnectStatus is cleared we set +     * ConnectStatusChange +     */ +    if (!(ohci->rhport[i].ctrl & OHCI_PORT_CCS)) { +        ohci->rhport[i].ctrl |= OHCI_PORT_CSC; +        if (ohci->rhstatus & OHCI_RHS_DRWE) { +            /* TODO: CSC is a wakeup event */ +        } +        return 0; +    } + +    if (ohci->rhport[i].ctrl & val) +        ret = 0; + +    /* set the bit */ +    ohci->rhport[i].ctrl |= val; + +    return ret; +} + +/* Set the frame interval - frame interval toggle is manipulated by the hcd only */ +static void ohci_set_frame_interval(OHCIState *ohci, uint16_t val) +{ +    val &= OHCI_FMI_FI; + +    if (val != ohci->fi) { +        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); +        break; +    case OHCI_USB_RESUME: +        trace_usb_ohci_resume(ohci->name); +        break; +    case OHCI_USB_RESET: +        ohci_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; + +    /* avoid muldiv if possible */ +    if (tks >= usb_frame_time) +        return (ohci->frt << 31); + +    tks = muldiv64(1, tks, usb_bit_time); +    fr = (uint16_t)(ohci->fi - tks); + +    return (ohci->frt << 31) | fr; +} + + +/* Set root hub status */ +static void ohci_set_hub_status(OHCIState *ohci, uint32_t val) +{ +    uint32_t old_state; + +    old_state = ohci->rhstatus; + +    /* write 1 to clear OCIC */ +    if (val & OHCI_RHS_OCIC) +        ohci->rhstatus &= ~OHCI_RHS_OCIC; + +    if (val & OHCI_RHS_LPS) { +        int i; + +        for (i = 0; i < ohci->num_ports; i++) +            ohci_port_power(ohci, i, 0); +        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_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_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 = { +}; + +static void usb_ohci_init(OHCIState *ohci, DeviceState *dev, +                          int num_ports, dma_addr_t localmem_base, +                          char *masterbus, uint32_t firstport, +                          AddressSpace *as, Error **errp) +{ +    Error *err = NULL; +    int i; + +    ohci->as = as; + +    if (usb_frame_time == 0) { +#ifdef OHCI_TIME_WARP +        usb_frame_time = get_ticks_per_sec(); +        usb_bit_time = muldiv64(1, get_ticks_per_sec(), USB_HZ/1000); +#else +        usb_frame_time = muldiv64(1, get_ticks_per_sec(), 1000); +        if (get_ticks_per_sec() >= USB_HZ) { +            usb_bit_time = muldiv64(1, get_ticks_per_sec(), USB_HZ); +        } else { +            usb_bit_time = 1; +        } +#endif +        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; +} + +#define TYPE_PCI_OHCI "pci-ohci" +#define PCI_OHCI(obj) OBJECT_CHECK(OHCIPCIState, (obj), TYPE_PCI_OHCI) + +typedef struct { +    /*< private >*/ +    PCIDevice parent_obj; +    /*< public >*/ + +    OHCIState state; +    char *masterbus; +    uint32_t num_ports; +    uint32_t firstport; +} OHCIPCIState; + +/** A typical O/EHCI will stop operating, set itself into error state + * (which can be queried by MMIO) and will set PERR in its config + * space to signal that it got an error + */ +static void ohci_die(OHCIState *ohci) +{ +    OHCIPCIState *dev = container_of(ohci, OHCIPCIState, state); + +    trace_usb_ohci_die(); + +    ohci_set_interrupt(ohci, OHCI_INTR_UE); +    ohci_bus_stop(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), &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); +    } +} + +static void usb_ohci_reset_pci(DeviceState *d) +{ +    PCIDevice *dev = PCI_DEVICE(d); +    OHCIPCIState *ohci = PCI_OHCI(dev); +    OHCIState *s = &ohci->state; + +    ohci_reset(s); +} + +#define TYPE_SYSBUS_OHCI "sysbus-ohci" +#define SYSBUS_OHCI(obj) OBJECT_CHECK(OHCISysBusState, (obj), TYPE_SYSBUS_OHCI) + +typedef struct { +    /*< private >*/ +    SysBusDevice parent_obj; +    /*< public >*/ + +    OHCIState ohci; +    uint32_t num_ports; +    dma_addr_t dma_offset; +} OHCISysBusState; + +static void ohci_realize_pxa(DeviceState *dev, Error **errp) +{ +    OHCISysBusState *s = SYSBUS_OHCI(dev); +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + +    /* Cannot fail as we pass NULL for masterbus */ +    usb_ohci_init(&s->ohci, dev, s->num_ports, s->dma_offset, NULL, 0, +                  &address_space_memory, &error_abort); +    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_reset(ohci); +} + +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_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 ohci->eof_timer != NULL; +} + +static int ohci_eof_timer_pre_load(void *opaque) +{ +    OHCIState *ohci = opaque; + +    ohci_bus_start(ohci); + +    return 0; +} + +static const VMStateDescription vmstate_ohci_eof_timer = { +    .name = "ohci-core/eof-timer", +    .version_id = 1, +    .minimum_version_id = 1, +    .pre_load = ohci_eof_timer_pre_load, +    .needed = ohci_eof_timer_needed, +    .fields = (VMStateField[]) { +        VMSTATE_TIMER_PTR(eof_timer, OHCIState), +        VMSTATE_END_OF_LIST() +    }, +}; + +static 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 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"; +    dc->props = 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, +}; + +static Property ohci_sysbus_properties[] = { +    DEFINE_PROP_UINT32("num-ports", OHCISysBusState, num_ports, 3), +    DEFINE_PROP_DMAADDR("dma-offset", OHCISysBusState, dma_offset, 3), +    DEFINE_PROP_END_OF_LIST(), +}; + +static void ohci_sysbus_class_init(ObjectClass *klass, void *data) +{ +    DeviceClass *dc = DEVICE_CLASS(klass); + +    dc->realize = ohci_realize_pxa; +    set_bit(DEVICE_CATEGORY_USB, dc->categories); +    dc->desc = "OHCI USB Controller"; +    dc->props = 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_pci_info); +    type_register_static(&ohci_sysbus_info); +} + +type_init(ohci_register_types) diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c new file mode 100644 index 00000000..3f0ed626 --- /dev/null +++ b/hw/usb/hcd-uhci.c @@ -0,0 +1,1423 @@ +/* + * USB UHCI controller emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * Copyright (c) 2008 Max Krasnyansky + *     Magor rewrite of the UHCI data structures parser and frame processor + *     Support for fully async operation and multiple outstanding transactions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "hw/usb.h" +#include "hw/usb/uhci-regs.h" +#include "hw/pci/pci.h" +#include "qemu/timer.h" +#include "qemu/iov.h" +#include "sysemu/dma.h" +#include "trace.h" +#include "qemu/main-loop.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) + +#define NB_PORTS 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 UHCIQueue UHCIQueue; +typedef struct UHCIInfo UHCIInfo; +typedef struct UHCIPCIDeviceClass UHCIPCIDeviceClass; + +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; +}; + +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(asyncs_head, UHCIAsync) asyncs; +    int8_t    valid; +}; + +typedef struct UHCIPort { +    USBPort port; +    uint16_t ctrl; +} UHCIPort; + +struct UHCIState { +    PCIDevice dev; +    MemoryRegion io_bar; +    USBBus bus; /* Note unused when we're a companion controller */ +    uint16_t cmd; /* cmd register */ +    uint16_t status; +    uint16_t intr; /* interrupt enable register */ +    uint16_t frnum; /* frame number */ +    uint32_t fl_base_addr; /* frame list base address */ +    uint8_t sof_timing; +    uint8_t status2; /* bit 0 and 1 are used to generate UHCI_STS_USBINT */ +    int64_t expire_time; +    QEMUTimer *frame_timer; +    QEMUBH *bh; +    uint32_t frame_bytes; +    uint32_t frame_bandwidth; +    bool completions_only; +    UHCIPort ports[NB_PORTS]; + +    /* Interrupts that should be raised at the end of the current frame.  */ +    uint32_t pending_int_mask; + +    /* Active packets */ +    QTAILQ_HEAD(, UHCIQueue) queues; +    uint8_t num_ports_vmstate; + +    /* Properties */ +    char *masterbus; +    uint32_t firstport; +    uint32_t maxframes; +}; + +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); + +#define TYPE_UHCI "pci-uhci-usb" +#define UHCI(obj) OBJECT_CHECK(UHCIState, (obj), TYPE_UHCI) + +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; +    if (((s->status2 & 1) && (s->intr & (1 << 2))) || +        ((s->status2 & 2) && (s->intr & (1 << 3))) || +        ((s->status & UHCI_STS_USBERR) && (s->intr & (1 << 0))) || +        ((s->status & UHCI_STS_RD) && (s->intr & (1 << 1))) || +        (s->status & UHCI_STS_HSERR) || +        (s->status & UHCI_STS_HCPERR)) { +        level = 1; +    } else { +        level = 0; +    } +    pci_set_irq(&s->dev, 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) + +            (get_ticks_per_sec() / 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), +        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) + +                (get_ticks_per_sec() / 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 = 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; +    } + +    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, asyncs_head); +            /* +             * 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 = uhci_find_device(s, (td->token >> 8) & 0x7f); +        USBEndpoint *ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf); + +        if (ep == NULL) { +            return uhci_handle_td_error(s, td, td_addr, USB_RET_NODEV, +                                        int_mask); +        } +        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: +        /* invalid pid : frame interrupted */ +        uhci_async_free(async); +        s->status |= UHCI_STS_HCPERR; +        uhci_update_irq(s); +        return TD_RESULT_STOP_FRAME; +    } + +    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 = get_ticks_per_sec() / 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 = { +}; + +static 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); + +    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_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); +} + +static void usb_uhci_exit(PCIDevice *dev) +{ +    UHCIState *s = UHCI(dev); + +    trace_usb_uhci_exit(); + +    if (s->frame_timer) { +        timer_del(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, +}; + +static 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; +        dc->props = uhci_properties_companion; +    } else { +        dc->props = uhci_properties_standalone; +    } +    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      = "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, +    },{ +        .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-xhci.c b/hw/usb/hcd-xhci.c new file mode 100644 index 00000000..c673bed4 --- /dev/null +++ b/hw/usb/hcd-xhci.c @@ -0,0 +1,3916 @@ +/* + * USB xHCI controller emulation + * + * Copyright (c) 2011 Securiforest + * Date: 2011-05-11 ;  Author: Hector Martin <hector@marcansoft.com> + * Based on usb-ohci.c, emulates Renesas NEC USB 3.0 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "hw/pci/pci.h" +#include "hw/pci/msi.h" +#include "hw/pci/msix.h" +#include "trace.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 MAXPORTS_2 15 +#define MAXPORTS_3 15 + +#define MAXPORTS (MAXPORTS_2+MAXPORTS_3) +#define MAXSLOTS 64 +#define MAXINTRS 16 + +#define TD_QUEUE 24 + +/* Very pessimistic, let's hope it's enough for all cases */ +#define EV_QUEUE (((3*TD_QUEUE)+16)*MAXSLOTS) +/* Do not deliver ER Full events. NEC's driver does some things not bound + * to the specs when it gets them */ +#define ER_FULL_HACK + +#define LEN_CAP         0x40 +#define LEN_OPER        (0x400 + 0x10 * MAXPORTS) +#define LEN_RUNTIME     ((MAXINTRS + 1) * 0x20) +#define LEN_DOORBELL    ((MAXSLOTS + 1) * 0x20) + +#define OFF_OPER        LEN_CAP +#define OFF_RUNTIME     0x1000 +#define OFF_DOORBELL    0x2000 +#define OFF_MSIX_TABLE  0x3000 +#define OFF_MSIX_PBA    0x3800 +/* must be power of 2 */ +#define LEN_REGS        0x4000 + +#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) > LEN_REGS +# error Increase 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, +}; + +typedef enum TRBType { +    TRB_RESERVED = 0, +    TR_NORMAL, +    TR_SETUP, +    TR_DATA, +    TR_STATUS, +    TR_ISOCH, +    TR_LINK, +    TR_EVDATA, +    TR_NOOP, +    CR_ENABLE_SLOT, +    CR_DISABLE_SLOT, +    CR_ADDRESS_DEVICE, +    CR_CONFIGURE_ENDPOINT, +    CR_EVALUATE_CONTEXT, +    CR_RESET_ENDPOINT, +    CR_STOP_ENDPOINT, +    CR_SET_TR_DEQUEUE, +    CR_RESET_DEVICE, +    CR_FORCE_EVENT, +    CR_NEGOTIATE_BW, +    CR_SET_LATENCY_TOLERANCE, +    CR_GET_PORT_BANDWIDTH, +    CR_FORCE_HEADER, +    CR_NOOP, +    ER_TRANSFER = 32, +    ER_COMMAND_COMPLETE, +    ER_PORT_STATUS_CHANGE, +    ER_BANDWIDTH_REQUEST, +    ER_DOORBELL, +    ER_HOST_CONTROLLER, +    ER_DEVICE_NOTIFICATION, +    ER_MFINDEX_WRAP, +    /* vendor specific bits */ +    CR_VENDOR_VIA_CHALLENGE_RESPONSE = 48, +    CR_VENDOR_NEC_FIRMWARE_REVISION  = 49, +    CR_VENDOR_NEC_CHALLENGE_RESPONSE = 50, +} TRBType; + +#define CR_LINK TR_LINK + +typedef enum TRBCCode { +    CC_INVALID = 0, +    CC_SUCCESS, +    CC_DATA_BUFFER_ERROR, +    CC_BABBLE_DETECTED, +    CC_USB_TRANSACTION_ERROR, +    CC_TRB_ERROR, +    CC_STALL_ERROR, +    CC_RESOURCE_ERROR, +    CC_BANDWIDTH_ERROR, +    CC_NO_SLOTS_ERROR, +    CC_INVALID_STREAM_TYPE_ERROR, +    CC_SLOT_NOT_ENABLED_ERROR, +    CC_EP_NOT_ENABLED_ERROR, +    CC_SHORT_PACKET, +    CC_RING_UNDERRUN, +    CC_RING_OVERRUN, +    CC_VF_ER_FULL, +    CC_PARAMETER_ERROR, +    CC_BANDWIDTH_OVERRUN, +    CC_CONTEXT_STATE_ERROR, +    CC_NO_PING_RESPONSE_ERROR, +    CC_EVENT_RING_FULL_ERROR, +    CC_INCOMPATIBLE_DEVICE_ERROR, +    CC_MISSED_SERVICE_ERROR, +    CC_COMMAND_RING_STOPPED, +    CC_COMMAND_ABORTED, +    CC_STOPPED, +    CC_STOPPED_LENGTH_INVALID, +    CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR = 29, +    CC_ISOCH_BUFFER_OVERRUN = 31, +    CC_EVENT_LOST_ERROR, +    CC_UNDEFINED_ERROR, +    CC_INVALID_STREAM_ID_ERROR, +    CC_SECONDARY_BANDWIDTH_ERROR, +    CC_SPLIT_TRANSACTION_ERROR +} TRBCCode; + +#define TRB_C               (1<<0) +#define TRB_TYPE_SHIFT          10 +#define TRB_TYPE_MASK       0x3f +#define TRB_TYPE(t)         (((t).control >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK) + +#define TRB_EV_ED           (1<<2) + +#define TRB_TR_ENT          (1<<1) +#define TRB_TR_ISP          (1<<2) +#define TRB_TR_NS           (1<<3) +#define TRB_TR_CH           (1<<4) +#define TRB_TR_IOC          (1<<5) +#define TRB_TR_IDT          (1<<6) +#define TRB_TR_TBC_SHIFT        7 +#define TRB_TR_TBC_MASK     0x3 +#define TRB_TR_BEI          (1<<9) +#define TRB_TR_TLBPC_SHIFT      16 +#define TRB_TR_TLBPC_MASK   0xf +#define TRB_TR_FRAMEID_SHIFT    20 +#define TRB_TR_FRAMEID_MASK 0x7ff +#define TRB_TR_SIA          (1<<31) + +#define TRB_TR_DIR          (1<<16) + +#define TRB_CR_SLOTID_SHIFT     24 +#define TRB_CR_SLOTID_MASK  0xff +#define TRB_CR_EPID_SHIFT       16 +#define TRB_CR_EPID_MASK    0x1f + +#define TRB_CR_BSR          (1<<9) +#define TRB_CR_DC           (1<<9) + +#define TRB_LK_TC           (1<<1) + +#define 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 + +typedef struct XHCIState XHCIState; +typedef struct XHCIStreamContext XHCIStreamContext; +typedef struct XHCIEPContext XHCIEPContext; + +#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 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[16]; +    MemoryRegion mem; +} XHCIPort; + +typedef struct XHCITransfer { +    XHCIState *xhci; +    USBPacket packet; +    QEMUSGList sgl; +    bool running_async; +    bool running_retry; +    bool complete; +    bool int_req; +    unsigned int iso_pkts; +    unsigned int slotid; +    unsigned int epid; +    unsigned int streamid; +    bool in_xfer; +    bool iso_xfer; +    bool timed_xfer; + +    unsigned int trb_count; +    unsigned int trb_alloced; +    XHCITRB *trbs; + +    TRBCCode status; + +    unsigned int pkts; +    unsigned int pktsize; +    unsigned int cur_pkt; + +    uint64_t mfindex_kick; +} XHCITransfer; + +struct XHCIStreamContext { +    dma_addr_t pctx; +    unsigned int sct; +    XHCIRing ring; +}; + +struct XHCIEPContext { +    XHCIState *xhci; +    unsigned int slotid; +    unsigned int epid; + +    XHCIRing ring; +    unsigned int next_xfer; +    unsigned int comp_xfer; +    XHCITransfer transfers[TD_QUEUE]; +    XHCITransfer *retry; +    EPType type; +    dma_addr_t pctx; +    unsigned int max_psize; +    uint32_t state; + +    /* 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 XHCISlot { +    bool enabled; +    bool addressed; +    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, er_full; + +    dma_addr_t er_start; +    uint32_t er_size; +    unsigned int er_ep_idx; + +    XHCIEvent ev_buffer[EV_QUEUE]; +    unsigned int ev_buffer_put; +    unsigned int ev_buffer_get; + +} XHCIInterrupter; + +struct XHCIState { +    /*< private >*/ +    PCIDevice parent_obj; +    /*< public >*/ + +    USBBus bus; +    MemoryRegion mem; +    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; + +    /* 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(MAXPORTS_2, MAXPORTS_3)]; +    XHCIPort ports[MAXPORTS]; +    XHCISlot slots[MAXSLOTS]; +    uint32_t numports; + +    /* Runtime Registers */ +    int64_t mfindex_start; +    QEMUTimer *mfwrap_timer; +    XHCIInterrupter intr[MAXINTRS]; + +    XHCIRing cmd_ring; +}; + +#define TYPE_XHCI "nec-usb-xhci" + +#define XHCI(obj) \ +    OBJECT_CHECK(XHCIState, (obj), TYPE_XHCI) + +typedef struct XHCIEvRingSeg { +    uint32_t addr_low; +    uint32_t addr_high; +    uint32_t size; +    uint32_t rsvd; +} XHCIEvRingSeg; + +enum xhci_flags { +    XHCI_FLAG_USE_MSI = 1, +    XHCI_FLAG_USE_MSI_X, +    XHCI_FLAG_SS_FIRST, +    XHCI_FLAG_FORCE_PCIE_ENDCAP, +    XHCI_FLAG_ENABLE_STREAMS, +}; + +static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, +                         unsigned int epid, 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(XHCIState *xhci, +                                       unsigned int slotid, unsigned int epid); + +static const char *TRBType_names[] = { +    [TRB_RESERVED]                     = "TRB_RESERVED", +    [TR_NORMAL]                        = "TR_NORMAL", +    [TR_SETUP]                         = "TR_SETUP", +    [TR_DATA]                          = "TR_DATA", +    [TR_STATUS]                        = "TR_STATUS", +    [TR_ISOCH]                         = "TR_ISOCH", +    [TR_LINK]                          = "TR_LINK", +    [TR_EVDATA]                        = "TR_EVDATA", +    [TR_NOOP]                          = "TR_NOOP", +    [CR_ENABLE_SLOT]                   = "CR_ENABLE_SLOT", +    [CR_DISABLE_SLOT]                  = "CR_DISABLE_SLOT", +    [CR_ADDRESS_DEVICE]                = "CR_ADDRESS_DEVICE", +    [CR_CONFIGURE_ENDPOINT]            = "CR_CONFIGURE_ENDPOINT", +    [CR_EVALUATE_CONTEXT]              = "CR_EVALUATE_CONTEXT", +    [CR_RESET_ENDPOINT]                = "CR_RESET_ENDPOINT", +    [CR_STOP_ENDPOINT]                 = "CR_STOP_ENDPOINT", +    [CR_SET_TR_DEQUEUE]                = "CR_SET_TR_DEQUEUE", +    [CR_RESET_DEVICE]                  = "CR_RESET_DEVICE", +    [CR_FORCE_EVENT]                   = "CR_FORCE_EVENT", +    [CR_NEGOTIATE_BW]                  = "CR_NEGOTIATE_BW", +    [CR_SET_LATENCY_TOLERANCE]         = "CR_SET_LATENCY_TOLERANCE", +    [CR_GET_PORT_BANDWIDTH]            = "CR_GET_PORT_BANDWIDTH", +    [CR_FORCE_HEADER]                  = "CR_FORCE_HEADER", +    [CR_NOOP]                          = "CR_NOOP", +    [ER_TRANSFER]                      = "ER_TRANSFER", +    [ER_COMMAND_COMPLETE]              = "ER_COMMAND_COMPLETE", +    [ER_PORT_STATUS_CHANGE]            = "ER_PORT_STATUS_CHANGE", +    [ER_BANDWIDTH_REQUEST]             = "ER_BANDWIDTH_REQUEST", +    [ER_DOORBELL]                      = "ER_DOORBELL", +    [ER_HOST_CONTROLLER]               = "ER_HOST_CONTROLLER", +    [ER_DEVICE_NOTIFICATION]           = "ER_DEVICE_NOTIFICATION", +    [ER_MFINDEX_WRAP]                  = "ER_MFINDEX_WRAP", +    [CR_VENDOR_VIA_CHALLENGE_RESPONSE] = "CR_VENDOR_VIA_CHALLENGE_RESPONSE", +    [CR_VENDOR_NEC_FIRMWARE_REVISION]  = "CR_VENDOR_NEC_FIRMWARE_REVISION", +    [CR_VENDOR_NEC_CHALLENGE_RESPONSE] = "CR_VENDOR_NEC_CHALLENGE_RESPONSE", +}; + +static const char *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)); +} + +static bool xhci_get_flag(XHCIState *xhci, enum xhci_flags bit) +{ +    return 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); + +    pci_dma_read(PCI_DEVICE(xhci), 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[len / sizeof(uint32_t)]; + +    assert((len % sizeof(uint32_t)) == 0); + +    for (i = 0; i < (len / sizeof(uint32_t)); i++) { +        tmp[i] = cpu_to_le32(buf[i]); +    } +    pci_dma_write(PCI_DEVICE(xhci), 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_intx_update(XHCIState *xhci) +{ +    PCIDevice *pci_dev = PCI_DEVICE(xhci); +    int level = 0; + +    if (msix_enabled(pci_dev) || +        msi_enabled(pci_dev)) { +        return; +    } + +    if (xhci->intr[0].iman & IMAN_IP && +        xhci->intr[0].iman & IMAN_IE && +        xhci->usbcmd & USBCMD_INTE) { +        level = 1; +    } + +    trace_usb_xhci_irq_intx(level); +    pci_set_irq(pci_dev, level); +} + +static void xhci_msix_update(XHCIState *xhci, int v) +{ +    PCIDevice *pci_dev = PCI_DEVICE(xhci); +    bool enabled; + +    if (!msix_enabled(pci_dev)) { +        return; +    } + +    enabled = xhci->intr[v].iman & IMAN_IE; +    if (enabled == xhci->intr[v].msix_used) { +        return; +    } + +    if (enabled) { +        trace_usb_xhci_irq_msix_use(v); +        msix_vector_use(pci_dev, v); +        xhci->intr[v].msix_used = true; +    } else { +        trace_usb_xhci_irq_msix_unuse(v); +        msix_vector_unuse(pci_dev, v); +        xhci->intr[v].msix_used = false; +    } +} + +static void xhci_intr_raise(XHCIState *xhci, int v) +{ +    PCIDevice *pci_dev = PCI_DEVICE(xhci); + +    xhci->intr[v].erdp_low |= ERDP_EHB; +    xhci->intr[v].iman |= IMAN_IP; +    xhci->usbsts |= USBSTS_EINT; + +    if (!(xhci->intr[v].iman & IMAN_IE)) { +        return; +    } + +    if (!(xhci->usbcmd & USBCMD_INTE)) { +        return; +    } + +    if (msix_enabled(pci_dev)) { +        trace_usb_xhci_irq_msix(v); +        msix_notify(pci_dev, v); +        return; +    } + +    if (msi_enabled(pci_dev)) { +        trace_usb_xhci_irq_msi(v); +        msi_notify(pci_dev, v); +        return; +    } + +    if (v == 0) { +        trace_usb_xhci_irq_intx(1); +        pci_irq_assert(pci_dev); +    } +} + +static inline int xhci_running(XHCIState *xhci) +{ +    return !(xhci->usbsts & USBSTS_HCH) && !xhci->intr[0].er_full; +} + +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) +{ +    PCIDevice *pci_dev = PCI_DEVICE(xhci); +    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; +    pci_dma_write(pci_dev, 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_events_update(XHCIState *xhci, int v) +{ +    XHCIInterrupter *intr = &xhci->intr[v]; +    dma_addr_t erdp; +    unsigned int dp_idx; +    bool do_irq = 0; + +    if (xhci->usbsts & USBSTS_HCH) { +        return; +    } + +    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); + +    /* NEC didn't read section 4.9.4 of the spec (v1.0 p139 top Note) and thus +     * deadlocks when the ER is full. Hack it by holding off events until +     * the driver decides to free at least half of the ring */ +    if (intr->er_full) { +        int er_free = dp_idx - intr->er_ep_idx; +        if (er_free <= 0) { +            er_free += intr->er_size; +        } +        if (er_free < (intr->er_size/2)) { +            DPRINTF("xhci_events_update(): event ring still " +                    "more than half full (hack)\n"); +            return; +        } +    } + +    while (intr->ev_buffer_put != intr->ev_buffer_get) { +        assert(intr->er_full); +        if (((intr->er_ep_idx+1) % intr->er_size) == dp_idx) { +            DPRINTF("xhci_events_update(): event ring full again\n"); +#ifndef ER_FULL_HACK +            XHCIEvent full = {ER_HOST_CONTROLLER, CC_EVENT_RING_FULL_ERROR}; +            xhci_write_event(xhci, &full, v); +#endif +            do_irq = 1; +            break; +        } +        XHCIEvent *event = &intr->ev_buffer[intr->ev_buffer_get]; +        xhci_write_event(xhci, event, v); +        intr->ev_buffer_get++; +        do_irq = 1; +        if (intr->ev_buffer_get == EV_QUEUE) { +            intr->ev_buffer_get = 0; +        } +    } + +    if (do_irq) { +        xhci_intr_raise(xhci, v); +    } + +    if (intr->er_full && intr->ev_buffer_put == intr->ev_buffer_get) { +        DPRINTF("xhci_events_update(): event ring no longer full\n"); +        intr->er_full = 0; +    } +} + +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]; + +    if (intr->er_full) { +        DPRINTF("xhci_event(): ER full, queueing\n"); +        if (((intr->ev_buffer_put+1) % EV_QUEUE) == intr->ev_buffer_get) { +            DPRINTF("xhci: event queue full, dropping event!\n"); +            return; +        } +        intr->ev_buffer[intr->ev_buffer_put++] = *event; +        if (intr->ev_buffer_put == EV_QUEUE) { +            intr->ev_buffer_put = 0; +        } +        return; +    } + +    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+1) % intr->er_size == dp_idx) { +        DPRINTF("xhci_event(): ER full, queueing\n"); +#ifndef ER_FULL_HACK +        XHCIEvent full = {ER_HOST_CONTROLLER, CC_EVENT_RING_FULL_ERROR}; +        xhci_write_event(xhci, &full); +#endif +        intr->er_full = 1; +        if (((intr->ev_buffer_put+1) % EV_QUEUE) == intr->ev_buffer_get) { +            DPRINTF("xhci: event queue full, dropping event!\n"); +            return; +        } +        intr->ev_buffer[intr->ev_buffer_put++] = *event; +        if (intr->ev_buffer_put == EV_QUEUE) { +            intr->ev_buffer_put = 0; +        } +    } 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) +{ +    PCIDevice *pci_dev = PCI_DEVICE(xhci); + +    while (1) { +        TRBType type; +        pci_dma_read(pci_dev, 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 { +            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) +{ +    PCIDevice *pci_dev = PCI_DEVICE(xhci); +    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; + +    while (1) { +        TRBType type; +        pci_dma_read(pci_dev, 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) { +            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; + +    if (intr->erstsz == 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_addr_t erstba = xhci_addr64(intr->erstba_low, intr->erstba_high); +    pci_dma_read(PCI_DEVICE(xhci), 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; +    intr->er_full = 0; + +    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(xhci, slotid, i); +        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_ep(epctx->xhci, epctx->slotid, epctx->epid, 0); +} + +static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci, +                                       unsigned int slotid, +                                       unsigned int epid) +{ +    XHCIEPContext *epctx; +    int i; + +    epctx = g_new0(XHCIEPContext, 1); +    epctx->xhci = xhci; +    epctx->slotid = slotid; +    epctx->epid = epid; + +    for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) { +        epctx->transfers[i].xhci = xhci; +        epctx->transfers[i].slotid = slotid; +        epctx->transfers[i].epid = epid; +        usb_packet_init(&epctx->transfers[i].packet); +    } +    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 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) { +        XHCIEPContext *epctx = t->xhci->slots[t->slotid-1].eps[t->epid-1]; +        if (epctx) { +            epctx->retry = NULL; +            timer_del(epctx->kick_timer); +        } +        t->running_retry = 0; +        killed = 1; +    } +    if (t->trbs) { +        g_free(t->trbs); +    } + +    t->trbs = NULL; +    t->trb_count = t->trb_alloced = 0; + +    return killed; +} + +static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid, +                               unsigned int epid, TRBCCode report) +{ +    XHCISlot *slot; +    XHCIEPContext *epctx; +    int i, xferi, 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]; + +    xferi = epctx->next_xfer; +    for (i = 0; i < TD_QUEUE; i++) { +        killed += xhci_ep_nuke_one_xfer(&epctx->transfers[xferi], report); +        if (killed) { +            report = 0; /* Only report once */ +        } +        epctx->transfers[xferi].packet.ep = NULL; +        xferi = (xferi + 1) % TD_QUEUE; +    } + +    ep = xhci_epid_to_usbep(xhci, slotid, epid); +    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; +    int i; + +    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); +    } + +    for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) { +        usb_packet_cleanup(&epctx->transfers[i].packet); +    } + +    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->xhci; +    int i; + +    xfer->int_req = false; +    pci_dma_sglist_init(&xfer->sgl, PCI_DEVICE(xhci), xfer->trb_count); +    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->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_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->slotid; +            event.epid = xfer->epid; +            event.length = (trb->status & 0x1ffff) - chunk; +            event.flags = 0; +            event.ptr = trb->addr; +            if (xfer->status == CC_SUCCESS) { +                event.ccode = shortpkt ? CC_SHORT_PACKET : CC_SUCCESS; +            } else { +                event.ccode = xfer->status; +            } +            if (TRB_TYPE(*trb) == TR_EVDATA) { +                event.ptr = trb->parameter; +                event.flags |= TRB_EV_ED; +                event.length = edtla & 0xffffff; +                DPRINTF("xhci_xfer_data: EDTLA=%d\n", event.length); +                edtla = 0; +            } +            xhci_event(xhci, &event, 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) +{ +    XHCIState *xhci = xfer->xhci; +    XHCISlot *slot = &xhci->slots[xfer->slotid-1]; +    XHCIEPContext *epctx = slot->eps[xfer->epid-1]; +    uint32_t err; +    XHCIStreamContext *sctx; + +    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_submit(XHCIState *xhci, XHCITransfer *xfer, +                       XHCIEPContext *epctx); + +static int xhci_setup_packet(XHCITransfer *xfer) +{ +    XHCIState *xhci = xfer->xhci; +    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(xhci, xfer->slotid, xfer->epid); +        if (!ep) { +            DPRINTF("xhci: slot %d has no device\n", +                    xfer->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); +    usb_packet_map(&xfer->packet, &xfer->sgl); +    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_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->slotid, xfer->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_complete_packet(xfer); +    if (!xfer->running_async && !xfer->running_retry) { +        xhci_kick_ep(xhci, xfer->slotid, xfer->epid, 0); +    } +    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", xfer->slotid, xfer->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_complete_packet(xfer); +    if (!xfer->running_async && !xfer->running_retry) { +        xhci_kick_ep(xhci, xfer->slotid, xfer->epid, xfer->streamid); +    } +    return 0; +} + +static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx) +{ +    trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->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) +{ +    XHCIStreamContext *stctx; +    XHCIEPContext *epctx; +    XHCIRing *ring; +    USBEndpoint *ep = NULL; +    uint64_t mfindex; +    int length; +    int i; + +    trace_usb_xhci_ep_kick(slotid, epid, streamid); +    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 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->slots[slotid - 1].uport || +        !xhci->slots[slotid - 1].uport->dev || +        !xhci->slots[slotid - 1].uport->dev->attached) { +        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_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) { +                return; +            } +            xhci_complete_packet(xfer); +        } +        assert(!xfer->running_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); +    } +    assert(ring->dequeue != 0); + +    while (1) { +        XHCITransfer *xfer = &epctx->transfers[epctx->next_xfer]; +        if (xfer->running_async || xfer->running_retry) { +            break; +        } +        length = xhci_ring_chain_length(xhci, ring); +        if (length < 0) { +            break; +        } else if (length == 0) { +            break; +        } +        if (xfer->trbs && xfer->trb_alloced < length) { +            xfer->trb_count = 0; +            xfer->trb_alloced = 0; +            g_free(xfer->trbs); +            xfer->trbs = NULL; +        } +        if (!xfer->trbs) { +            xfer->trbs = g_malloc(sizeof(XHCITRB) * length); +            xfer->trb_alloced = length; +        } +        xfer->trb_count = length; + +        for (i = 0; i < length; i++) { +            assert(xhci_ring_fetch(xhci, ring, &xfer->trbs[i], NULL)); +        } +        xfer->streamid = streamid; + +        if (epid == 1) { +            if (xhci_fire_ctl_transfer(xhci, xfer) >= 0) { +                epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; +            } else { +                DPRINTF("xhci: error firing CTL transfer\n"); +            } +        } else { +            if (xhci_fire_transfer(xhci, xfer, epctx) >= 0) { +                epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; +            } else { +                if (!xfer->timed_xfer) { +                    DPRINTF("xhci: error firing data transfer\n"); +                } +            } +        } + +        if (epctx->state == EP_HALTED) { +            break; +        } +        if (xfer->running_retry) { +            DPRINTF("xhci: xfer nacked, stopping schedule\n"); +            epctx->retry = xfer; +            break; +        } +    } + +    ep = xhci_epid_to_usbep(xhci, slotid, epid); +    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; +    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_pci_dma(PCI_DEVICE(xhci), 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; + +    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; +        usb_device_reset(dev); +        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); +    } + +    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; +        slot_ctx[2] &= ~0xFF00000; /* interrupter target */ +        slot_ctx[2] |= islot_ctx[2] & 0xFF000000; + +        DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", +                slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); + +        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% */ +    pci_dma_write(PCI_DEVICE(xhci), ctx, bw_ctx, sizeof(bw_ctx)); + +    return CC_SUCCESS; +} + +static uint32_t rotl(uint32_t v, unsigned count) +{ +    count &= 31; +    return (v << count) | (v >> (32 - count)); +} + + +static uint32_t xhci_nec_challenge(uint32_t hi, uint32_t lo) +{ +    uint32_t val; +    val = rotl(lo - 0x49434878, 32 - ((hi>>8) & 0x1F)); +    val += rotl(lo + 0x49434878, hi & 0x1F); +    val -= rotl(hi ^ 0x49434878, (lo >> 16) & 0x1F); +    return ~val; +} + +static void xhci_via_challenge(XHCIState *xhci, uint64_t addr) +{ +    PCIDevice *pci_dev = PCI_DEVICE(xhci); +    uint32_t buf[8]; +    uint32_t obuf[8]; +    dma_addr_t paddr = xhci_mask64(addr); + +    pci_dma_read(pci_dev, paddr, &buf, 32); + +    memcpy(obuf, buf, sizeof(obuf)); + +    if ((buf[0] & 0xff) == 2) { +        obuf[0] = 0x49932000 + 0x54dc200 * buf[2] + 0x7429b578 * buf[3]; +        obuf[0] |=  (buf[2] * buf[3]) & 0xff; +        obuf[1] = 0x0132bb37 + 0xe89 * buf[2] + 0xf09 * buf[3]; +        obuf[2] = 0x0066c2e9 + 0x2091 * buf[2] + 0x19bd * buf[3]; +        obuf[3] = 0xd5281342 + 0x2cc9691 * buf[2] + 0x2367662 * buf[3]; +        obuf[4] = 0x0123c75c + 0x1595 * buf[2] + 0x19ec * buf[3]; +        obuf[5] = 0x00f695de + 0x26fd * buf[2] + 0x3e9 * buf[3]; +        obuf[6] = obuf[2] ^ obuf[3] ^ 0x29472956; +        obuf[7] = obuf[2] ^ obuf[3] ^ 0x65866593; +    } + +    pci_dma_write(pci_dev, paddr, &obuf, 32); +} + +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; + +    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_VENDOR_VIA_CHALLENGE_RESPONSE: +            xhci_via_challenge(xhci, trb.parameter); +            break; +        case CR_VENDOR_NEC_FIRMWARE_REVISION: +            event.type = 48; /* NEC reply */ +            event.length = 0x3025; +            break; +        case CR_VENDOR_NEC_CHALLENGE_RESPONSE: +        { +            uint32_t chi = trb.parameter >> 32; +            uint32_t clo = trb.parameter; +            uint32_t val = xhci_nec_challenge(chi, clo); +            event.length = val & 0xFFFF; +            event.epid = val >> 16; +            slotid = val >> 24; +            event.type = 48; /* NEC reply */ +        } +        break; +        default: +            trace_usb_xhci_unimplemented("command", type); +            event.ccode = CC_TRB_ERROR; +            break; +        } +        event.slotid = slotid; +        xhci_event(xhci, &event, 0); +    } +} + +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; + +    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].msix_used = 0; + +        xhci->intr[i].er_ep_idx = 0; +        xhci->intr[i].er_pcs = 1; +        xhci->intr[i].er_full = 0; +        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 = ptr; +    DeviceState *d = DEVICE(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(d); +        } +        xhci_intx_update(xhci); +        break; + +    case 0x04: /* USBSTS */ +        /* these bits are write-1-to-clear */ +        xhci->usbsts &= ~(val & (USBSTS_HSE|USBSTS_EINT|USBSTS_PCD|USBSTS_SRE)); +        xhci_intx_update(xhci); +        break; + +    case 0x14: /* DNCTRL */ +        xhci->dnctrl = val & 0xffff; +        break; +    case 0x18: /* CRCR low */ +        xhci->crcr_low = (val & 0xffffffcf) | (xhci->crcr_low & CRCR_CRR); +        break; +    case 0x1c: /* CRCR high */ +        xhci->crcr_high = val; +        if (xhci->crcr_low & (CRCR_CA|CRCR_CS) && (xhci->crcr_low & CRCR_CRR)) { +            XHCIEvent event = {ER_COMMAND_COMPLETE, CC_COMMAND_RING_STOPPED}; +            xhci->crcr_low &= ~CRCR_CRR; +            xhci_event(xhci, &event, 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; +    int v = (reg - 0x20) / 0x20; +    XHCIInterrupter *intr = &xhci->intr[v]; +    trace_usb_xhci_runtime_write(reg, val); + +    if (reg < 0x20) { +        trace_usb_xhci_unimplemented("runtime write", reg); +        return; +    } + +    switch (reg & 0x1f) { +    case 0x00: /* IMAN */ +        if (val & IMAN_IP) { +            intr->iman &= ~IMAN_IP; +        } +        intr->iman &= ~IMAN_IE; +        intr->iman |= val & IMAN_IE; +        if (v == 0) { +            xhci_intx_update(xhci); +        } +        xhci_msix_update(xhci, v); +        break; +    case 0x04: /* IMOD */ +        intr->imod = val; +        break; +    case 0x08: /* ERSTSZ */ +        intr->erstsz = val & 0xffff; +        break; +    case 0x10: /* ERSTBA low */ +        /* XXX NEC driver bug: it doesn't align this to 64 bytes +        intr->erstba_low = val & 0xffffffc0; */ +        intr->erstba_low = val & 0xfffffff0; +        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); +        break; +    case 0x1c: /* ERDP high */ +        intr->erdp_high = val; +        xhci_events_update(xhci, v); +        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 > 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 = 4, +    .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 = 4, +    .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); + +    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_complete_packet(xfer); +    xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid, xfer->streamid); +} + +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(XHCIState *xhci, +                                       unsigned int slotid, unsigned int epid) +{ +    assert(slotid >= 1 && slotid <= xhci->numslots); + +    if (!xhci->slots[slotid - 1].uport) { +        return NULL; +    } + +    return usb_ep_get(xhci->slots[slotid - 1].uport->dev, +                      (epid & 1) ? USB_TOKEN_IN : USB_TOKEN_OUT, 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) +{ +    DeviceState *dev = DEVICE(xhci); +    XHCIPort *port; +    int i, usbports, speedmask; + +    xhci->usbsts = USBSTS_HCH; + +    if (xhci->numports_2 > MAXPORTS_2) { +        xhci->numports_2 = MAXPORTS_2; +    } +    if (xhci->numports_3 > MAXPORTS_3) { +        xhci->numports_3 = 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, dev); + +    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; +            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; +            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(struct PCIDevice *dev, Error **errp) +{ +    int i, ret; + +    XHCIState *xhci = XHCI(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 */ + +    usb_xhci_init(xhci); + +    if (xhci->numintrs > MAXINTRS) { +        xhci->numintrs = MAXINTRS; +    } +    while (xhci->numintrs & (xhci->numintrs - 1)) {   /* ! power of 2 */ +        xhci->numintrs++; +    } +    if (xhci->numintrs < 1) { +        xhci->numintrs = 1; +    } +    if (xhci->numslots > MAXSLOTS) { +        xhci->numslots = 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; +    } + +    xhci->mfwrap_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_mfwrap_timer, xhci); + +    memory_region_init(&xhci->mem, OBJECT(xhci), "xhci", LEN_REGS); +    memory_region_init_io(&xhci->mem_cap, OBJECT(xhci), &xhci_cap_ops, xhci, +                          "capabilities", LEN_CAP); +    memory_region_init_io(&xhci->mem_oper, OBJECT(xhci), &xhci_oper_ops, xhci, +                          "operational", 0x400); +    memory_region_init_io(&xhci->mem_runtime, OBJECT(xhci), &xhci_runtime_ops, xhci, +                          "runtime", LEN_RUNTIME); +    memory_region_init_io(&xhci->mem_doorbell, OBJECT(xhci), &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(xhci), &xhci_port_ops, port, +                              port->name, 0x10); +        memory_region_add_subregion(&xhci->mem, offset, &port->mem); +    } + +    pci_register_bar(dev, 0, +                     PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64, +                     &xhci->mem); + +    if (pci_bus_is_express(dev->bus) || +        xhci_get_flag(xhci, XHCI_FLAG_FORCE_PCIE_ENDCAP)) { +        ret = pcie_endpoint_cap_init(dev, 0xa0); +        assert(ret >= 0); +    } + +    if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI)) { +        msi_init(dev, 0x70, xhci->numintrs, true, false); +    } +    if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI_X)) { +        msix_init(dev, xhci->numintrs, +                  &xhci->mem, 0, OFF_MSIX_TABLE, +                  &xhci->mem, 0, OFF_MSIX_PBA, +                  0x90); +    } +} + +static void usb_xhci_exit(PCIDevice *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_del(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); +    } + +    /* destroy msix memory region */ +    if (dev->msix_table && dev->msix_pba +        && dev->msix_entry_used) { +        memory_region_del_subregion(&xhci->mem, &dev->msix_table_mmio); +        memory_region_del_subregion(&xhci->mem, &dev->msix_pba_mmio); +    } + +    usb_bus_release(&xhci->bus); +} + +static int usb_xhci_post_load(void *opaque, int version_id) +{ +    XHCIState *xhci = opaque; +    PCIDevice *pci_dev = PCI_DEVICE(xhci); +    XHCISlot *slot; +    XHCIEPContext *epctx; +    dma_addr_t dcbaap, pctx; +    uint32_t slot_ctx[4]; +    uint32_t ep_ctx[5]; +    int slotid, epid, state, intr; + +    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_pci_dma(pci_dev, 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)); +            } +        } +    } + +    for (intr = 0; intr < xhci->numintrs; intr++) { +        if (xhci->intr[intr].msix_used) { +            msix_vector_use(pci_dev, intr); +        } else { +            msix_vector_unuse(pci_dev, intr); +        } +    } + +    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) +{ +    struct XHCIInterrupter *intr = opaque; +    return intr->er_full; +} + +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,         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() +    } +}; + +static const VMStateDescription vmstate_xhci = { +    .name = "xhci", +    .version_id = 1, +    .post_load = usb_xhci_post_load, +    .fields = (VMStateField[]) { +        VMSTATE_PCIE_DEVICE(parent_obj, XHCIState), +        VMSTATE_MSIX(parent_obj, XHCIState), + +        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("msi",      XHCIState, flags, XHCI_FLAG_USE_MSI, true), +    DEFINE_PROP_BIT("msix",     XHCIState, flags, XHCI_FLAG_USE_MSI_X, true), +    DEFINE_PROP_BIT("superspeed-ports-first", +                    XHCIState, flags, XHCI_FLAG_SS_FIRST, true), +    DEFINE_PROP_BIT("force-pcie-endcap", XHCIState, flags, +                    XHCI_FLAG_FORCE_PCIE_ENDCAP, false), +    DEFINE_PROP_BIT("streams", XHCIState, flags, +                    XHCI_FLAG_ENABLE_STREAMS, true), +    DEFINE_PROP_UINT32("intrs", XHCIState, numintrs, MAXINTRS), +    DEFINE_PROP_UINT32("slots", XHCIState, numslots, MAXSLOTS), +    DEFINE_PROP_UINT32("p2",    XHCIState, numports_2, 4), +    DEFINE_PROP_UINT32("p3",    XHCIState, numports_3, 4), +    DEFINE_PROP_END_OF_LIST(), +}; + +static void xhci_class_init(ObjectClass *klass, void *data) +{ +    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); +    DeviceClass *dc = DEVICE_CLASS(klass); + +    dc->vmsd    = &vmstate_xhci; +    dc->props   = xhci_properties; +    dc->reset   = xhci_reset; +    set_bit(DEVICE_CATEGORY_USB, dc->categories); +    k->realize      = usb_xhci_realize; +    k->exit         = usb_xhci_exit; +    k->vendor_id    = PCI_VENDOR_ID_NEC; +    k->device_id    = PCI_DEVICE_ID_NEC_UPD720200; +    k->class_id     = PCI_CLASS_SERIAL_USB; +    k->revision     = 0x03; +    k->is_express   = 1; +} + +static const TypeInfo xhci_info = { +    .name          = TYPE_XHCI, +    .parent        = TYPE_PCI_DEVICE, +    .instance_size = sizeof(XHCIState), +    .class_init    = xhci_class_init, +}; + +static void xhci_register_types(void) +{ +    type_register_static(&xhci_info); +} + +type_init(xhci_register_types) diff --git a/hw/usb/host-legacy.c b/hw/usb/host-legacy.c new file mode 100644 index 00000000..422ed9a6 --- /dev/null +++ b/hw/usb/host-legacy.c @@ -0,0 +1,143 @@ +/* + * Linux host USB redirector + * + * Copyright (c) 2005 Fabrice Bellard + * + * Copyright (c) 2008 Max Krasnyansky + *      Support for host device auto connect & disconnect + *      Major rewrite to support fully async operation + * + * Copyright 2008 TJ <linux@tjworld.net> + *      Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition + *      to the legacy /proc/bus/usb USB device discovery and handling + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "hw/usb.h" +#include "hw/usb/host.h" + +/* + * Autoconnect filter + * Format: + *    auto:bus:dev[:vid:pid] + *    auto:bus.dev[:vid:pid] + * + *    bus  - bus number    (dec, * means any) + *    dev  - device number (dec, * means any) + *    vid  - vendor id     (hex, * means any) + *    pid  - product id    (hex, * means any) + * + *    See 'lsusb' output. + */ +static int parse_filter(const char *spec, struct USBAutoFilter *f) +{ +    enum { BUS, DEV, VID, PID, DONE }; +    const char *p = spec; +    int i; + +    f->bus_num    = 0; +    f->addr       = 0; +    f->vendor_id  = 0; +    f->product_id = 0; + +    for (i = BUS; i < DONE; i++) { +        p = strpbrk(p, ":."); +        if (!p) { +            break; +        } +        p++; + +        if (*p == '*') { +            continue; +        } +        switch (i) { +        case BUS: +            f->bus_num = strtol(p, NULL, 10); +            break; +        case DEV: +            f->addr    = strtol(p, NULL, 10); +            break; +        case VID: +            f->vendor_id  = strtol(p, NULL, 16); +            break; +        case PID: +            f->product_id = strtol(p, NULL, 16); +            break; +        } +    } + +    if (i < DEV) { +        fprintf(stderr, "husb: invalid auto filter spec %s\n", spec); +        return -1; +    } + +    return 0; +} + +USBDevice *usb_host_device_open(USBBus *bus, const char *devname) +{ +    struct USBAutoFilter filter; +    USBDevice *dev; +    char *p; + +    dev = usb_create(bus, "usb-host"); + +    if (strstr(devname, "auto:")) { +        if (parse_filter(devname, &filter) < 0) { +            goto fail; +        } +    } else { +        p = strchr(devname, '.'); +        if (p) { +            filter.bus_num    = strtoul(devname, NULL, 0); +            filter.addr       = strtoul(p + 1, NULL, 0); +            filter.vendor_id  = 0; +            filter.product_id = 0; +        } else { +            p = strchr(devname, ':'); +            if (p) { +                filter.bus_num    = 0; +                filter.addr       = 0; +                filter.vendor_id  = strtoul(devname, NULL, 16); +                filter.product_id = strtoul(p + 1, NULL, 16); +            } else { +                goto fail; +            } +        } +    } + +    qdev_prop_set_uint32(&dev->qdev, "hostbus",   filter.bus_num); +    qdev_prop_set_uint32(&dev->qdev, "hostaddr",  filter.addr); +    qdev_prop_set_uint32(&dev->qdev, "vendorid",  filter.vendor_id); +    qdev_prop_set_uint32(&dev->qdev, "productid", filter.product_id); +    return dev; + +fail: +    object_unparent(OBJECT(dev)); +    return NULL; +} + +static void usb_host_register_types(void) +{ +    usb_legacy_register("usb-host", "host", usb_host_device_open); +} + +type_init(usb_host_register_types) diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c new file mode 100644 index 00000000..11429f5e --- /dev/null +++ b/hw/usb/host-libusb.c @@ -0,0 +1,1685 @@ +/* + * 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 <poll.h> +#include <libusb.h> + +#include "qemu-common.h" +#include "monitor/monitor.h" +#include "qemu/error-report.h" +#include "sysemu/sysemu.h" +#include "trace.h" + +#include "hw/usb.h" + +/* ------------------------------------------------------------------------ */ + +#define TYPE_USB_HOST_DEVICE "usb-host" +#define USB_HOST_DEVICE(obj) \ +     OBJECT_CHECK(USBHostDevice, (obj), TYPE_USB_HOST_DEVICE) + +typedef struct USBHostDevice USBHostDevice; +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; +    int32_t                          bootindex; +    uint32_t                         iso_urb_count; +    uint32_t                         iso_urb_frames; +    uint32_t                         options; +    uint32_t                         loglevel; + +    /* state */ +    QTAILQ_ENTRY(USBHostDevice)      next; +    int                              seen, errcount; +    int                              bus_num; +    int                              addr; +    char                             port[16]; + +    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; +    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 */ + +#if LIBUSBX_API_VERSION >= 0x01000103 +# define HAVE_STREAMS 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", +}; + +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, +}; + +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; + +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); +} + +static int usb_host_init(void) +{ +    const struct libusb_pollfd **poll; +    int i, rc; + +    if (ctx) { +        return 0; +    } +    rc = libusb_init(&ctx); +    if (rc != 0) { +        return -1; +    } +    libusb_set_debug(ctx, loglevel); + +    libusb_set_pollfd_notifiers(ctx, usb_host_add_fd, +                                usb_host_del_fd, +                                ctx); +    poll = libusb_get_pollfds(ctx); +    if (poll) { +        for (i = 0; poll[i] != NULL; i++) { +            usb_host_add_fd(poll[i]->fd, poll[i]->events, ctx); +        } +    } +    free(poll); +    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 LIBUSBX_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); +    return r; +} + +static void usb_host_req_free(USBHostRequest *r) +{ +    if (r->host) { +        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 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) { +        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; +        } +    } +    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 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; +    } + +    QTAILQ_REMOVE(&r->host->requests, r, next); +    r->host = NULL; + +    if (inflight) { +        libusb_cancel_transfer(r->xfer); +    } +} + +/* ------------------------------------------------------------------------ */ + +static void 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); +    } 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]; +                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) { +                            libusb_free_ss_endpoint_companion_descriptor +                                (endp_ss_comp); +                            compat_full = false; +                            compat_high = false; +                        } +#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; +    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++) { +        assert(udev->altsetting[i] < conf->interface[i].num_altsetting); +        intf = &conf->interface[i].altsetting[udev->altsetting[i]]; +        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) +{ +    USBDevice *udev = USB_DEVICE(s); +    int bus_num = libusb_get_bus_number(dev); +    int addr    = libusb_get_device_address(dev); +    int rc; +    Error *local_err = NULL; + +    trace_usb_host_open_started(bus_num, addr); + +    if (s->dh != NULL) { +        goto fail; +    } +    rc = libusb_open(dev, &s->dh); +    if (rc != 0) { +        goto fail; +    } + +    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); + +    udev->speed     = speed_map[libusb_get_device_speed(dev)]; +    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; + +    QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) { +        usb_host_req_abort(r); +    } +} + +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; + +    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_release_interfaces(s); +        usb_host_attach_kernel(s); +    } +} + +static void usb_host_realize(USBDevice *udev, Error **errp) +{ +    USBHostDevice *s = USB_HOST_DEVICE(udev); + +    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->exit.notify = usb_host_exit_notifier; +    qemu_add_exit_notifier(&s->exit); + +    QTAILQ_INSERT_TAIL(&hostdevs, s, next); +    usb_host_auto_check(NULL); +} + +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, NULL); +} + +static void usb_host_handle_destroy(USBDevice *udev) +{ +    USBHostDevice *s = USB_HOST_DEVICE(udev); + +    qemu_remove_exit_notifier(&s->exit); +    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 < conf->bNumInterfaces; i++) { +        rc = libusb_kernel_driver_active(s->dh, i); +        usb_host_libusb_error("libusb_kernel_driver_active", rc); +        if (rc != 1) { +            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 < conf->bNumInterfaces; 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; + +    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; +    } + +    for (i = 0; i < conf->bNumInterfaces; i++) { +        trace_usb_host_claim_interface(s->bus_num, s->addr, configuration, i); +        rc = libusb_claim_interface(s->dh, i); +        usb_host_libusb_error("libusb_claim_interface", rc); +        if (rc != 0) { +            return USB_RET_STALL; +        } +        s->ifs[i].claimed = true; +    } + +    udev->ninterfaces   = conf->bNumInterfaces; +    udev->configuration = configuration; + +    libusb_free_config_descriptor(conf); +    return USB_RET_SUCCESS; +} + +static void usb_host_release_interfaces(USBHostDevice *s) +{ +    USBDevice *udev = USB_DEVICE(s); +    int i, rc; + +    for (i = 0; i < udev->ninterfaces; 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; + +    trace_usb_host_set_config(s->bus_num, s->addr, config); + +    usb_host_release_interfaces(s); +    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->speed == USB_SPEED_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; + +    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.  Attemping to contine where we + * left off is impossible. + * + * What we are going to to to 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); +    } +    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); +    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), +    DEFINE_PROP_UINT32("isobufs",  USBHostDevice, iso_urb_count,    4), +    DEFINE_PROP_UINT32("isobsize", USBHostDevice, iso_urb_frames,   32), +    DEFINE_PROP_UINT32("loglevel",  USBHostDevice, loglevel, +                       LIBUSB_LOG_LEVEL_WARNING), +    DEFINE_PROP_BIT("pipeline",    USBHostDevice, options, +                    USB_HOST_OPT_PIPELINE, 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->handle_destroy = usb_host_handle_destroy; +    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; +    dc->props = 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, +}; + +static void usb_host_register_types(void) +{ +    type_register_static(&usb_host_dev_info); +} + +type_init(usb_host_register_types) + +/* ------------------------------------------------------------------------ */ + +static QEMUTimer *usb_auto_timer; +static VMChangeStateEntry *usb_vmstate; + +static void usb_host_vm_state(void *unused, int 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) { +                    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-stub.c b/hw/usb/host-stub.c new file mode 100644 index 00000000..2eaaa834 --- /dev/null +++ b/hw/usb/host-stub.c @@ -0,0 +1,47 @@ +/* + * Stub host USB redirector + * + * Copyright (c) 2005 Fabrice Bellard + * + * Copyright (c) 2008 Max Krasnyansky + *      Support for host device auto connect & disconnect + *      Major rewrite to support fully async operation + * + * Copyright 2008 TJ <linux@tjworld.net> + *      Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition + *      to the legacy /proc/bus/usb USB device discovery and handling + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "ui/console.h" +#include "hw/usb.h" +#include "monitor/monitor.h" + +void hmp_info_usbhost(Monitor *mon, const QDict *qdict) +{ +    monitor_printf(mon, "USB host devices not supported\n"); +} + +/* XXX: modify configure to compile the right host driver */ +USBDevice *usb_host_device_open(USBBus *bus, const char *devname) +{ +    return NULL; +} diff --git a/hw/usb/host.h b/hw/usb/host.h new file mode 100644 index 00000000..048ff3b4 --- /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/libhw.c b/hw/usb/libhw.c new file mode 100644 index 00000000..8df11c46 --- /dev/null +++ b/hw/usb/libhw.c @@ -0,0 +1,70 @@ +/* + * QEMU USB emulation, libhw bits. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "hw/hw.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/quirks-ftdi-ids.h b/hw/usb/quirks-ftdi-ids.h new file mode 100644 index 00000000..57c12ef6 --- /dev/null +++ b/hw/usb/quirks-ftdi-ids.h @@ -0,0 +1,1255 @@ +/* + * 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 tranceiver 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 Tranceiver */ +#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 + +/* + * Milkymist One JTAG/Serial + */ +#define QIHARDWARE_VID			0x20B7 +#define MILKYMISTONE_JTAGSERIAL_PID	0x0713 + +/* + * 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 00000000..8dbdb46f --- /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 00000000..a761a960 --- /dev/null +++ b/hw/usb/quirks.c @@ -0,0 +1,53 @@ +/* + * 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 "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].vendor_id != -1; i++) { +        if (ids[i].vendor_id  == vendor_id && +            ids[i].product_id == product_id && +            (ids[i].interface_class == -1 || +             (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 00000000..8dc60655 --- /dev/null +++ b/hw/usb/quirks.h @@ -0,0 +1,910 @@ +/* + * 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. + */ + +/* 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 { +    int vendor_id; +    int product_id; +    int interface_class; +    int interface_subclass; +    int interface_protocol; +}; + +#define USB_DEVICE(vendor, product) \ +    .vendor_id = vendor, .product_id = product, .interface_class = -1, + +#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 + +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) }, + +    { USB_DEVICE(-1, -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(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_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) }, + +    { USB_DEVICE(-1, -1) } /* Terminating Entry */ +}; + +#undef USB_DEVICE +#undef USB_DEVICE_AND_INTERFACE_INFO diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c new file mode 100644 index 00000000..25df25fd --- /dev/null +++ b/hw/usb/redirect.c @@ -0,0 +1,2519 @@ +/* + * 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-common.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" +#include "qapi/qmp/qerror.h" +#include "qemu/error-report.h" +#include "qemu/iov.h" +#include "sysemu/char.h" + +#include <dirent.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <usbredirparser.h> +#include <usbredirfilter.h> + +#include "hw/usb.h" + +#define MAX_ENDPOINTS 32 +#define 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 */ +    CharDriverState *cs; +    uint8_t debug; +    char *filter_str; +    int32_t bootindex; +    /* 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; +}; + +#define TYPE_USB_REDIR "usb-redir" +#define USB_REDIRECT(obj) OBJECT_CHECK(USBRedirDevice, (obj), 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) { \ +            error_report("usb-redir warning: " __VA_ARGS__); \ +        } \ +    } while (0) +#define INFO(...) \ +    do { \ +        if (dev->debug >= usbredirparser_info) { \ +            error_report("usb-redir: " __VA_ARGS__); \ +        } \ +    } while (0) +#define DPRINTF(...) \ +    do { \ +        if (dev->debug >= usbredirparser_debug) { \ +            error_report("usb-redir: " __VA_ARGS__); \ +        } \ +    } while (0) +#define DPRINTF2(...) \ +    do { \ +        if (dev->debug >= usbredirparser_debug_data) { \ +            error_report("usb-redir: " __VA_ARGS__); \ +        } \ +    } while (0) + +static void usbredir_log(void *priv, int level, const char *msg) +{ +    USBRedirDevice *dev = priv; + +    if (dev->debug < level) { +        return; +    } + +    error_report("%s", msg); +} + +static void usbredir_log_data(USBRedirDevice *dev, const char *desc, +    const uint8_t *data, int len) +{ +    int i, j, n; + +    if (dev->debug < usbredirparser_debug_data) { +        return; +    } + +    for (i = 0; i < len; i += j) { +        char buf[128]; + +        n = sprintf(buf, "%s", desc); +        for (j = 0; j < 8 && i + j < len; j++) { +            n += sprintf(buf + n, " %02X", data[i + j]); +        } +        error_report("%s", buf); +    } +} + +/* + * usbredirparser io functions + */ + +static int usbredir_read(void *priv, uint8_t *data, int count) +{ +    USBRedirDevice *dev = priv; + +    if (dev->read_buf_size < count) { +        count = dev->read_buf_size; +    } + +    memcpy(data, dev->read_buf, count); + +    dev->read_buf_size -= count; +    if (dev->read_buf_size) { +        dev->read_buf += count; +    } else { +        dev->read_buf = NULL; +    } + +    return count; +} + +static gboolean usbredir_write_unblocked(GIOChannel *chan, 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 (!dev->cs->be_open) { +        return 0; +    } + +    /* Don't send new data to the chardev until our state is fully synced */ +    if (!runstate_check(RUN_STATE_RUNNING)) { +        return 0; +    } + +    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; +        } +    } +    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_malloc0(sizeof(struct PacketIdQueueEntry)); +    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 void 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(data); +            return; +        } +        dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; +    } + +    bufp = g_malloc(sizeof(struct buf_packet)); +    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++; +} + +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 = (dev->endpoint[EP2I(ep)].bufpq_target_size + +                             start_iso.pkts_per_urb - 1) / +                            start_iso.pkts_per_urb; +        /* Output endpoints pre-fill only 1/2 of the packets, keeping the rest +           as overflow buffer. Also see the usbredir protocol documentation */ +        if (!(ep & USB_DIR_IN)) { +            start_iso.no_urbs *= 2; +        } +        if (start_iso.no_urbs > 16) { +            start_iso.no_urbs = 16; +        } + +        /* No id, we look at the ep when receiving a status back */ +        usbredirparser_send_start_iso_stream(dev->parser, 0, &start_iso); +        usbredirparser_do_write(dev->parser); +        DPRINTF("iso stream started pkts/sec %d pkts/urb %d urbs %d ep %02X\n", +                pkts_per_sec, start_iso.pkts_per_urb, start_iso.no_urbs, ep); +        dev->endpoint[EP2I(ep)].iso_started = 1; +        dev->endpoint[EP2I(ep)].bufpq_prefilled = 0; +        dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; +    } + +    if (ep & USB_DIR_IN) { +        struct buf_packet *isop; + +        if (dev->endpoint[EP2I(ep)].iso_started && +                !dev->endpoint[EP2I(ep)].bufpq_prefilled) { +            if (dev->endpoint[EP2I(ep)].bufpq_size < +                    dev->endpoint[EP2I(ep)].bufpq_target_size) { +                return; +            } +            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 +            }; +            uint8_t buf[p->iov.size]; +            /* No id, we look at the ep when receiving a status back */ +            usb_packet_copy(p, buf, p->iov.size); +            usbredirparser_send_iso_packet(dev->parser, 0, &iso_packet, +                                           buf, p->iov.size); +            usbredirparser_do_write(dev->parser); +        } +        status = dev->endpoint[EP2I(ep)].iso_error; +        dev->endpoint[EP2I(ep)].iso_error = 0; +        DPRINTF2("iso-token-out ep %02X status %d len %zd\n", ep, status, +                 p->iov.size); +        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) { +        usbredirparser_send_bulk_packet(dev->parser, p->id, +                                        &bulk_packet, NULL, 0); +    } else { +        uint8_t buf[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; +    int status, len; + +    if (!dev->endpoint[EP2I(ep)].interrupt_started && +            !dev->endpoint[EP2I(ep)].interrupt_error) { +        struct usb_redir_start_interrupt_receiving_header start_int = { +            .endpoint = ep, +        }; +        /* No id, we look at the ep when receiving a status back */ +        usbredirparser_send_start_interrupt_receiving(dev->parser, 0, +                                                      &start_int); +        usbredirparser_do_write(dev->parser); +        DPRINTF("interrupt recv started ep %02X\n", ep); +        dev->endpoint[EP2I(ep)].interrupt_started = 1; +        /* We don't really want to drop interrupt packets ever, but +           having some upper limit to how much we buffer is good. */ +        dev->endpoint[EP2I(ep)].bufpq_target_size = 1000; +        dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; +    } + +    intp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq); +    if (intp == NULL) { +        DPRINTF2("interrupt-token-in ep %02X, no intp\n", ep); +        /* Check interrupt_error for stream errors */ +        status = dev->endpoint[EP2I(ep)].interrupt_error; +        dev->endpoint[EP2I(ep)].interrupt_error = 0; +        if (status) { +            usbredir_handle_status(dev, p, status); +        } else { +            p->status = USB_RET_NAK; +        } +        return; +    } +    DPRINTF("interrupt-token-in ep %02X status %d len %d\n", ep, +            intp->status, intp->len); + +    status = intp->status; +    len = intp->len; +    if (len > p->iov.size) { +        ERROR("received int data is larger then packet ep %02X\n", ep); +        len = p->iov.size; +        status = usb_redir_babble; +    } +    usb_packet_copy(p, intp->data, len); +    bufp_free(dev, intp, ep); +    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; +    uint8_t buf[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 +    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 1024 * 1024; +} + +static void usbredir_chardev_read(void *opaque, const uint8_t *buf, int size) +{ +    USBRedirDevice *dev = opaque; + +    /* No recursion allowed! */ +    assert(dev->read_buf == NULL); + +    dev->read_buf = buf; +    dev->read_buf_size = size; + +    usbredirparser_do_read(dev->parser); +    /* Send any acks, etc. which may be queued now */ +    usbredirparser_do_write(dev->parser); +} + +static void usbredir_chardev_event(void *opaque, int event) +{ +    USBRedirDevice *dev = opaque; + +    switch (event) { +    case CHR_EVENT_OPENED: +        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; +    } +} + +/* + * init + destroy + */ + +static void usbredir_vm_state_change(void *priv, int 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 (dev->cs == NULL) { +        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_add_handlers(dev->cs, usbredir_chardev_can_read, +                          usbredir_chardev_read, usbredir_chardev_event, dev); + +    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_handle_destroy(USBDevice *udev) +{ +    USBRedirDevice *dev = USB_REDIRECT(udev); + +    qemu_chr_delete(dev->cs); +    dev->cs = NULL; +    /* 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_del(dev->attach_timer); +    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); +} + +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; +    } +    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; + +    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; +        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; +        } + +        if (QTAILQ_EMPTY(&dev->endpoint[EP2I(ep)].bufpq)) { +            usb_wakeup(usb_ep_get(&dev->dev, USB_TOKEN_IN, ep & 0x0f), 0); +        } + +        /* bufp_alloc also adds the packet to the ep queue */ +        bufp_alloc(dev, data, data_len, interrupt_packet->status, ep, data); +    } 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) { +        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 */ +        bufp_alloc(dev, data + i, len, status, ep, free_on_destroy); +    } + +    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 void usbredir_pre_save(void *priv) +{ +    USBRedirDevice *dev = priv; + +    usbredir_fill_already_in_flight(dev); +} + +static int usbredir_post_load(void *priv, int version_id) +{ +    USBRedirDevice *dev = priv; + +    if (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 void usbredir_put_parser(QEMUFile *f, void *priv, size_t unused) +{ +    USBRedirDevice *dev = priv; +    uint8_t *data; +    int len; + +    if (dev->parser == NULL) { +        qemu_put_be32(f, 0); +        return; +    } + +    usbredirparser_serialize(dev->parser, &data, &len); +    qemu_oom_check(data); + +    qemu_put_be32(f, len); +    qemu_put_buffer(f, data, len); + +    free(data); +} + +static int usbredir_get_parser(QEMUFile *f, void *priv, size_t unused) +{ +    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 void usbredir_put_bufpq(QEMUFile *f, void *priv, size_t unused) +{ +    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); +} + +static int usbredir_get_bufpq(QEMUFile *f, void *priv, size_t unused) +{ +    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_malloc(sizeof(struct buf_packet)); +        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 void usbredir_put_packet_id_q(QEMUFile *f, void *priv, size_t unused) +{ +    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); +} + +static int usbredir_get_packet_id_q(QEMUFile *f, void *priv, size_t unused) +{ +    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_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->handle_destroy = usbredir_handle_destroy; +    uc->cancel_packet  = usbredir_cancel_packet; +    uc->handle_reset   = usbredir_handle_reset; +    uc->handle_data    = usbredir_handle_data; +    uc->handle_control = usbredir_handle_control; +    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; +    dc->props          = 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, NULL); +} + +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, +}; + +static void usbredir_register_types(void) +{ +    type_register_static(&usbredir_dev_info); +} + +type_init(usbredir_register_types)  | 
