diff options
Diffstat (limited to 'hw/usb')
39 files changed, 11550 insertions, 3578 deletions
diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs index 4225136d0f..f9695e7d8a 100644 --- a/hw/usb/Makefile.objs +++ b/hw/usb/Makefile.objs @@ -1,14 +1,36 @@ -hw-obj-$(CONFIG_USB_UHCI) += hcd-uhci.o -hw-obj-$(CONFIG_USB_OHCI) += hcd-ohci.o -hw-obj-$(CONFIG_USB_EHCI) += hcd-ehci.o -hw-obj-$(CONFIG_USB_XHCI) += hcd-xhci.o -hw-obj-y += libhw.o - -hw-obj-$(CONFIG_SMARTCARD) += dev-smartcard-reader.o -hw-obj-$(CONFIG_USB_REDIR) += redirect.o - -common-obj-y += core.o bus.o desc.o dev-hub.o -common-obj-y += host-$(HOST_USB).o dev-bluetooth.o -common-obj-y += dev-hid.o dev-storage.o dev-wacom.o -common-obj-y += dev-serial.o dev-network.o dev-audio.o -common-obj-y += dev-uas.o +# usb subsystem core +common-obj-y += core.o combined-packet.o bus.o desc.o +common-obj-y += libhw.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 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-y += dev-hub.o +common-obj-y += 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 + +# FIXME: make configurable too +CONFIG_USB_BLUETOOTH := y +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 +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 index b649360dd3..d1827be101 100644 --- a/hw/usb/bus.c +++ b/hw/usb/bus.c @@ -1,8 +1,8 @@ #include "hw/hw.h" #include "hw/usb.h" #include "hw/qdev.h" -#include "sysemu.h" -#include "monitor.h" +#include "sysemu/sysemu.h" +#include "monitor/monitor.h" #include "trace.h" static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent); @@ -140,24 +140,21 @@ void usb_device_handle_reset(USBDevice *dev) } } -int usb_device_handle_control(USBDevice *dev, USBPacket *p, int request, - int value, int index, int length, uint8_t *data) +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) { - return klass->handle_control(dev, p, request, value, index, length, - data); + klass->handle_control(dev, p, request, value, index, length, data); } - return -ENOSYS; } -int usb_device_handle_data(USBDevice *dev, USBPacket *p) +void usb_device_handle_data(USBDevice *dev, USBPacket *p) { USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); if (klass->handle_data) { - return klass->handle_data(dev, p); + klass->handle_data(dev, p); } - return -ENOSYS; } const char *usb_device_get_product_desc(USBDevice *dev) @@ -169,6 +166,9 @@ const char *usb_device_get_product_desc(USBDevice *dev) 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; } @@ -181,6 +181,22 @@ void usb_device_set_interface(USBDevice *dev, int interface, } } +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); + } +} + static int usb_qdev_init(DeviceState *qdev) { USBDevice *dev = USB_DEVICE(qdev); @@ -325,8 +341,10 @@ 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; } } @@ -399,19 +417,47 @@ void usb_release_port(USBDevice *dev) 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); + } + } +} + int usb_device_attach(USBDevice *dev) { USBBus *bus = usb_bus_from_device(dev); USBPort *port = dev->port; + char devspeed[32], portspeed[32]; assert(port != NULL); assert(!dev->attached); - trace_usb_port_attach(bus->busnr, port->path); + 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_report("Warning: speed mismatch trying to attach " - "usb device %s to bus %s", - dev->product_desc, bus->qbus.name); + error_report("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 -1; } @@ -526,7 +572,7 @@ static char *usb_get_fw_dev_path(DeviceState *qdev) return fw_path; } -void usb_info(Monitor *mon) +void usb_info(Monitor *mon, const QDict *qdict) { USBBus *bus; USBDevice *dev; @@ -585,6 +631,13 @@ USBDevice *usbdevice_create(const char *cmdline) 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) { if (*params) { error_report("usbdevice %s accepts no params", driver); @@ -605,7 +658,7 @@ static void usb_device_class_init(ObjectClass *klass, void *data) k->props = usb_props; } -static TypeInfo usb_device_type_info = { +static const TypeInfo usb_device_type_info = { .name = TYPE_USB_DEVICE, .parent = TYPE_DEVICE, .instance_size = sizeof(USBDevice), diff --git a/hw/usb/ccid-card-emulated.c b/hw/usb/ccid-card-emulated.c new file mode 100644 index 0000000000..deb6d4703b --- /dev/null +++ b/hw/usb/ccid-card-emulated.c @@ -0,0 +1,611 @@ +/* + * 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 "monitor/monitor.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; + int pipe[2]; + 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); + if (write(card->pipe[1], card, 1) != 1) { + DPRINTF(card, 1, "write to pipe failed\n"); + } +} + +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 pipe_read(void *opaque) +{ + EmulatedState *card = opaque; + EmulEvent *event, *next; + char dummy; + int len; + + do { + len = read(card->pipe[0], &dummy, sizeof(dummy)); + } while (len == sizeof(dummy)); + 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_pipe_signaling(EmulatedState *card) +{ + if (pipe(card->pipe) < 0) { + DPRINTF(card, 2, "pipe creation failed\n"); + return -1; + } + fcntl(card->pipe[0], F_SETFL, O_NONBLOCK); + fcntl(card->pipe[1], F_SETFL, O_NONBLOCK); + fcntl(card->pipe[0], F_SETOWN, getpid()); + qemu_set_fd_handler(card->pipe[0], pipe_read, NULL, card); + 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_pipe_signaling(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, event_thread, card, + QEMU_THREAD_JOINABLE); + qemu_thread_create(&card->apdu_thread_id, 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; + 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 0000000000..01c7e6f20d --- /dev/null +++ b/hw/usb/ccid-card-passthru.c @@ -0,0 +1,412 @@ +/* + * 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/sockets.h" +#include "monitor/monitor.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 = PASSTHRU_DEV_NAME, + .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; + 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 0000000000..9334da8acd --- /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 0000000000..13f6602ad2 --- /dev/null +++ b/hw/usb/combined-packet.c @@ -0,0 +1,186 @@ +/* + * 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)) { + 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 index 2da38e7fd0..05948ca9a4 100644 --- a/hw/usb/core.c +++ b/hw/usb/core.c @@ -25,7 +25,7 @@ */ #include "qemu-common.h" #include "hw/usb.h" -#include "iov.h" +#include "qemu/iov.h" #include "trace.h" void usb_attach(USBPort *port) @@ -71,7 +71,7 @@ void usb_device_reset(USBDevice *dev) usb_device_handle_reset(dev); } -void usb_wakeup(USBEndpoint *ep) +void usb_wakeup(USBEndpoint *ep, unsigned int stream) { USBDevice *dev = ep->dev; USBBus *bus = usb_bus_from_device(dev); @@ -80,7 +80,7 @@ void usb_wakeup(USBEndpoint *ep) dev->port->ops->wakeup(dev->port); } if (bus->ops->wakeup_endpoint) { - bus->ops->wakeup_endpoint(bus, ep); + bus->ops->wakeup_endpoint(bus, ep, stream); } } @@ -97,17 +97,17 @@ void usb_wakeup(USBEndpoint *ep) #define SETUP_STATE_ACK 3 #define SETUP_STATE_PARAM 4 -static int do_token_setup(USBDevice *s, USBPacket *p) +static void do_token_setup(USBDevice *s, USBPacket *p) { int request, value, index; - int ret = 0; if (p->iov.size != 8) { - return USB_RET_STALL; + p->status = USB_RET_STALL; + return; } usb_packet_copy(p, s->setup_buf, p->iov.size); - p->result = 0; + p->actual_length = 0; s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; s->setup_index = 0; @@ -116,24 +116,26 @@ static int do_token_setup(USBDevice *s, USBPacket *p) index = (s->setup_buf[5] << 8) | s->setup_buf[4]; if (s->setup_buf[0] & USB_DIR_IN) { - ret = usb_device_handle_control(s, p, request, value, index, - s->setup_len, s->data_buf); - if (ret == USB_RET_ASYNC) { - s->setup_state = SETUP_STATE_SETUP; - return USB_RET_ASYNC; + 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 (ret < 0) - return ret; - if (ret < s->setup_len) - s->setup_len = ret; + 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)); - return USB_RET_STALL; + p->status = USB_RET_STALL; + return; } if (s->setup_len == 0) s->setup_state = SETUP_STATE_ACK; @@ -141,13 +143,12 @@ static int do_token_setup(USBDevice *s, USBPacket *p) s->setup_state = SETUP_STATE_DATA; } - return ret; + p->actual_length = 8; } -static int do_token_in(USBDevice *s, USBPacket *p) +static void do_token_in(USBDevice *s, USBPacket *p) { int request, value, index; - int ret = 0; assert(p->ep->nr == 0); @@ -158,19 +159,15 @@ static int do_token_in(USBDevice *s, USBPacket *p) switch(s->setup_state) { case SETUP_STATE_ACK: if (!(s->setup_buf[0] & USB_DIR_IN)) { - ret = usb_device_handle_control(s, p, request, value, index, - s->setup_len, s->data_buf); - if (ret == USB_RET_ASYNC) { - return USB_RET_ASYNC; + 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; - if (ret > 0) - return 0; - return ret; + p->actual_length = 0; } - - /* return 0 byte */ - return 0; + break; case SETUP_STATE_DATA: if (s->setup_buf[0] & USB_DIR_IN) { @@ -180,20 +177,21 @@ static int do_token_in(USBDevice *s, USBPacket *p) } usb_packet_copy(p, s->data_buf + s->setup_index, len); s->setup_index += len; - if (s->setup_index >= s->setup_len) + if (s->setup_index >= s->setup_len) { s->setup_state = SETUP_STATE_ACK; - return len; + } + return; } - s->setup_state = SETUP_STATE_IDLE; - return USB_RET_STALL; + p->status = USB_RET_STALL; + break; default: - return USB_RET_STALL; + p->status = USB_RET_STALL; } } -static int do_token_out(USBDevice *s, USBPacket *p) +static void do_token_out(USBDevice *s, USBPacket *p) { assert(p->ep->nr == 0); @@ -205,7 +203,7 @@ static int do_token_out(USBDevice *s, USBPacket *p) } else { /* ignore additional output */ } - return 0; + break; case SETUP_STATE_DATA: if (!(s->setup_buf[0] & USB_DIR_IN)) { @@ -215,23 +213,23 @@ static int do_token_out(USBDevice *s, USBPacket *p) } usb_packet_copy(p, s->data_buf + s->setup_index, len); s->setup_index += len; - if (s->setup_index >= s->setup_len) + if (s->setup_index >= s->setup_len) { s->setup_state = SETUP_STATE_ACK; - return len; + } + return; } - s->setup_state = SETUP_STATE_IDLE; - return USB_RET_STALL; + p->status = USB_RET_STALL; + break; default: - return USB_RET_STALL; + p->status = USB_RET_STALL; } } -static int do_parameter(USBDevice *s, USBPacket *p) +static void do_parameter(USBDevice *s, USBPacket *p) { - int request, value, index; - int i, ret = 0; + int i, request, value, index; for (i = 0; i < 8; i++) { s->setup_buf[i] = p->parameter >> (i*8); @@ -249,27 +247,27 @@ static int do_parameter(USBDevice *s, USBPacket *p) fprintf(stderr, "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", s->setup_len, sizeof(s->data_buf)); - return USB_RET_STALL; + p->status = USB_RET_STALL; + return; } if (p->pid == USB_TOKEN_OUT) { usb_packet_copy(p, s->data_buf, s->setup_len); } - ret = usb_device_handle_control(s, p, request, value, index, - s->setup_len, s->data_buf); - if (ret < 0) { - return ret; + usb_device_handle_control(s, p, request, value, index, + s->setup_len, s->data_buf); + if (p->status == USB_RET_ASYNC) { + return; } - if (ret < s->setup_len) { - s->setup_len = ret; + 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); } - - return ret; } /* ctrl complete function for devices which use usb_generic_handle_packet and @@ -278,30 +276,30 @@ static int do_parameter(USBDevice *s, USBPacket *p) usb_packet_complete to complete their async control packets. */ void usb_generic_async_ctrl_complete(USBDevice *s, USBPacket *p) { - if (p->result < 0) { + if (p->status < 0) { s->setup_state = SETUP_STATE_IDLE; } switch (s->setup_state) { case SETUP_STATE_SETUP: - if (p->result < s->setup_len) { - s->setup_len = p->result; + if (p->actual_length < s->setup_len) { + s->setup_len = p->actual_length; } s->setup_state = SETUP_STATE_DATA; - p->result = 8; + p->actual_length = 8; break; case SETUP_STATE_ACK: s->setup_state = SETUP_STATE_IDLE; - p->result = 0; + p->actual_length = 0; break; case SETUP_STATE_PARAM: - if (p->result < s->setup_len) { - s->setup_len = p->result; + if (p->actual_length < s->setup_len) { + s->setup_len = p->actual_length; } if (p->pid == USB_TOKEN_IN) { - p->result = 0; + p->actual_length = 0; usb_packet_copy(p, s->data_buf, s->setup_len); } break; @@ -342,40 +340,57 @@ USBDevice *usb_find_device(USBPort *port, uint8_t addr) return usb_device_find_device(dev, addr); } -static int usb_process_one(USBPacket *p) +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) { - return do_parameter(dev, p); + do_parameter(dev, p); + return; } switch (p->pid) { case USB_TOKEN_SETUP: - return do_token_setup(dev, p); + do_token_setup(dev, p); + break; case USB_TOKEN_IN: - return do_token_in(dev, p); + do_token_in(dev, p); + break; case USB_TOKEN_OUT: - return do_token_out(dev, p); + do_token_out(dev, p); + break; default: - return USB_RET_STALL; + p->status = USB_RET_STALL; } } else { /* data pipe */ - return usb_device_handle_data(dev, p); + usb_device_handle_data(dev, p); } } -/* Hand over a packet to a device for processing. Return value +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. */ -int usb_handle_packet(USBDevice *dev, USBPacket *p) +void usb_handle_packet(USBDevice *dev, USBPacket *p) { - int ret; - if (dev == NULL) { - return USB_RET_NODEV; + p->status = USB_RET_NODEV; + return; } assert(dev == p->ep->dev); assert(dev->state == USB_STATE_DEFAULT); @@ -389,34 +404,41 @@ int usb_handle_packet(USBDevice *dev, USBPacket *p) } if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline) { - ret = usb_process_one(p); - if (ret == USB_RET_ASYNC) { + 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->ep->pipeline); - p->result = ret; - usb_packet_set_state(p, USB_PACKET_COMPLETE); + assert(!p->ep->pipeline || QTAILQ_EMPTY(&p->ep->queue)); + if (p->status != USB_RET_NAK) { + usb_packet_set_state(p, USB_PACKET_COMPLETE); + } } } else { - ret = USB_RET_ASYNC; - usb_packet_set_state(p, USB_PACKET_QUEUED); - QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue); + usb_queue_one(p); } - return ret; } -static void __usb_packet_complete(USBDevice *dev, USBPacket *p) +void usb_packet_complete_one(USBDevice *dev, USBPacket *p) { USBEndpoint *ep = p->ep; - assert(p->result != USB_RET_ASYNC && p->result != USB_RET_NAK); + assert(QTAILQ_FIRST(&ep->queue) == p); + assert(p->status != USB_RET_ASYNC && p->status != USB_RET_NAK); - if (p->result < 0) { + 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); @@ -430,25 +452,28 @@ static void __usb_packet_complete(USBDevice *dev, USBPacket *p) void usb_packet_complete(USBDevice *dev, USBPacket *p) { USBEndpoint *ep = p->ep; - int ret; usb_packet_check_state(p, USB_PACKET_ASYNC); - assert(QTAILQ_FIRST(&ep->queue) == p); - __usb_packet_complete(dev, p); + usb_packet_complete_one(dev, p); - while (!ep->halted && !QTAILQ_EMPTY(&ep->queue)) { + 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); - ret = usb_process_one(p); - if (ret == USB_RET_ASYNC) { + usb_process_one(p); + if (p->status == USB_RET_ASYNC) { usb_packet_set_state(p, USB_PACKET_ASYNC); break; } - p->result = ret; - __usb_packet_complete(ep->dev, p); + usb_packet_complete_one(ep->dev, p); } } @@ -520,15 +545,22 @@ void usb_packet_set_state(USBPacket *p, USBPacketState state) p->state = state; } -void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep, uint64_t id) +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->result = 0; + 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); } @@ -540,31 +572,40 @@ void usb_packet_addbuf(USBPacket *p, void *ptr, size_t len) void usb_packet_copy(USBPacket *p, void *ptr, size_t bytes) { - assert(p->result >= 0); - assert(p->result + bytes <= p->iov.size); + 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(p->iov.iov, p->iov.niov, p->result, ptr, bytes); + iov_to_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); break; case USB_TOKEN_IN: - iov_from_buf(p->iov.iov, p->iov.niov, p->result, ptr, bytes); + 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->result += bytes; + p->actual_length += bytes; } void usb_packet_skip(USBPacket *p, size_t bytes) { - assert(p->result >= 0); - assert(p->result + bytes <= p->iov.size); + 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(p->iov.iov, p->iov.niov, p->result, 0, bytes); + iov_memset(iov->iov, iov->niov, p->actual_length, 0, bytes); } - p->result += 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) @@ -724,3 +765,24 @@ void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled) struct USBEndpoint *uep = usb_ep_get(dev, pid, ep); uep->pipeline = enabled; } + +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.c b/hw/usb/desc.c index 0a9d3c9f60..fce303e9c8 100644 --- a/hw/usb/desc.c +++ b/hw/usb/desc.c @@ -76,7 +76,8 @@ int usb_desc_device_qualifier(const USBDescDevice *dev, return bLength; } -int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) +int usb_desc_config(const USBDescConfig *conf, int flags, + uint8_t *dest, size_t len) { uint8_t bLength = 0x09; uint16_t wTotalLength = 0; @@ -99,7 +100,7 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) /* handle grouped interfaces if any */ for (i = 0; i < conf->nif_groups; i++) { - rc = usb_desc_iface_group(&(conf->if_groups[i]), + rc = usb_desc_iface_group(&(conf->if_groups[i]), flags, dest + wTotalLength, len - wTotalLength); if (rc < 0) { @@ -110,7 +111,8 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) /* handle normal (ungrouped / no IAD) interfaces if any */ for (i = 0; i < conf->nif; i++) { - rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength); + rc = usb_desc_iface(conf->ifs + i, flags, + dest + wTotalLength, len - wTotalLength); if (rc < 0) { return rc; } @@ -122,8 +124,8 @@ int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) return wTotalLength; } -int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, - size_t len) +int usb_desc_iface_group(const USBDescIfaceAssoc *iad, int flags, + uint8_t *dest, size_t len) { int pos = 0; int i = 0; @@ -147,7 +149,7 @@ int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, /* handle associated interfaces in this group */ for (i = 0; i < iad->nif; i++) { - int rc = usb_desc_iface(&(iad->ifs[i]), dest + pos, len - pos); + int rc = usb_desc_iface(&(iad->ifs[i]), flags, dest + pos, len - pos); if (rc < 0) { return rc; } @@ -157,7 +159,8 @@ int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, return pos; } -int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) +int usb_desc_iface(const USBDescIface *iface, int flags, + uint8_t *dest, size_t len) { uint8_t bLength = 0x09; int i, rc, pos = 0; @@ -188,7 +191,7 @@ int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) } for (i = 0; i < iface->bNumEndpoints; i++) { - rc = usb_desc_endpoint(iface->eps + i, dest + pos, len - pos); + rc = usb_desc_endpoint(iface->eps + i, flags, dest + pos, len - pos); if (rc < 0) { return rc; } @@ -198,13 +201,15 @@ int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) return pos; } -int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len) +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) { + if (len < bLength + extralen + superlen) { return -1; } @@ -220,11 +225,26 @@ int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len) 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, ep->extra, extralen); + memcpy(dest + bLength + superlen, ep->extra, extralen); } - return bLength + extralen; + return bLength + extralen + superlen; } int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len) @@ -239,6 +259,111 @@ int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len) 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) @@ -359,6 +484,9 @@ static void usb_desc_setdefaults(USBDevice *dev) case USB_SPEED_HIGH: dev->device = desc->high; break; + case USB_SPEED_SUPER: + dev->device = desc->super; + break; } usb_desc_set_config(dev, 0); } @@ -376,6 +504,9 @@ void usb_desc_init(USBDevice *dev) if (desc->high) { dev->speedmask |= USB_SPEED_MASK_HIGH; } + if (desc->super) { + dev->speedmask |= USB_SPEED_MASK_SUPER; + } usb_desc_setdefaults(dev); } @@ -384,13 +515,13 @@ void usb_desc_attach(USBDevice *dev) const USBDesc *desc = usb_device_get_usb_desc(dev); assert(desc != NULL); - if (desc->high && (dev->port->speedmask & USB_SPEED_MASK_HIGH)) { + if (desc->super && (dev->port->speedmask & USB_SPEED_MASK_SUPER)) { + dev->speed = USB_SPEED_SUPER; + } else if (desc->high && (dev->port->speedmask & USB_SPEED_MASK_HIGH)) { dev->speed = USB_SPEED_HIGH; } else if (desc->full && (dev->port->speedmask & USB_SPEED_MASK_FULL)) { dev->speed = USB_SPEED_FULL; } else { - fprintf(stderr, "usb: port/device speed mismatch for \"%s\"\n", - usb_device_get_product_desc(dev)); return; } usb_desc_setdefaults(dev); @@ -494,14 +625,15 @@ int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len) return pos; } -int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len) +int usb_desc_get_descriptor(USBDevice *dev, USBPacket *p, + int value, uint8_t *dest, size_t len) { const USBDesc *desc = usb_device_get_usb_desc(dev); const USBDescDevice *other_dev; uint8_t buf[256]; uint8_t type = value >> 8; uint8_t index = value & 0xff; - int ret = -1; + int flags, ret = -1; if (dev->speed == USB_SPEED_HIGH) { other_dev = usb_device_get_usb_desc(dev)->full; @@ -509,6 +641,11 @@ int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len 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, buf, sizeof(buf)); @@ -516,7 +653,8 @@ int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len break; case USB_DT_CONFIG: if (index < dev->device->bNumConfigurations) { - ret = usb_desc_config(dev->device->confs + index, buf, sizeof(buf)); + ret = usb_desc_config(dev->device->confs + index, flags, + buf, sizeof(buf)); } trace_usb_desc_config(dev->addr, index, len, ret); break; @@ -524,7 +662,6 @@ int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len 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)); @@ -533,11 +670,16 @@ int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len break; case USB_DT_OTHER_SPEED_CONFIG: if (other_dev != NULL && index < other_dev->bNumConfigurations) { - ret = usb_desc_config(other_dev->confs + index, buf, sizeof(buf)); + 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 */ @@ -554,6 +696,8 @@ int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len ret = len; } memcpy(dest, buf, ret); + p->actual_length = ret; + ret = 0; } return ret; } @@ -573,7 +717,7 @@ int usb_desc_handle_control(USBDevice *dev, USBPacket *p, break; case DeviceRequest | USB_REQ_GET_DESCRIPTOR: - ret = usb_desc_get_descriptor(dev, value, data, length); + ret = usb_desc_get_descriptor(dev, p, value, data, length); break; case DeviceRequest | USB_REQ_GET_CONFIGURATION: @@ -582,7 +726,8 @@ int usb_desc_handle_control(USBDevice *dev, USBPacket *p, * the non zero value of bConfigurationValue. */ data[0] = dev->config ? dev->config->bConfigurationValue : 0; - ret = 1; + p->actual_length = 1; + ret = 0; break; case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: ret = usb_desc_set_config(dev, value); @@ -607,7 +752,8 @@ int usb_desc_handle_control(USBDevice *dev, USBPacket *p, data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP; } data[1] = 0x00; - ret = 2; + p->actual_length = 2; + ret = 0; break; } case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: @@ -630,7 +776,8 @@ int usb_desc_handle_control(USBDevice *dev, USBPacket *p, break; } data[0] = dev->altsetting[index]; - ret = 1; + p->actual_length = 1; + ret = 0; break; case InterfaceOutRequest | USB_REQ_SET_INTERFACE: ret = usb_desc_set_interface(dev, index, value); diff --git a/hw/usb/desc.h b/hw/usb/desc.h index 7cf5442945..ddd3e7485c 100644 --- a/hw/usb/desc.h +++ b/hw/usb/desc.h @@ -63,6 +63,37 @@ typedef struct USBDescriptor { 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; @@ -139,6 +170,11 @@ struct USBDescEndpoint { 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 { @@ -152,19 +188,25 @@ struct USBDesc { USBDescID id; const USBDescDevice *full; const USBDescDevice *high; + const USBDescDevice *super; const char* const *str; }; +#define USB_DESC_FLAG_SUPER (1 << 1) + /* generate usb packages from structs */ int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, uint8_t *dest, size_t len); int usb_desc_device_qualifier(const USBDescDevice *dev, uint8_t *dest, size_t len); -int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len); -int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, - size_t len); -int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len); -int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len); +int usb_desc_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); /* control message emulation helpers */ @@ -174,7 +216,8 @@ 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, int value, 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); diff --git a/hw/usb/dev-audio.c b/hw/usb/dev-audio.c index 79b75fb628..04933a985a 100644 --- a/hw/usb/dev-audio.c +++ b/hw/usb/dev-audio.c @@ -33,7 +33,6 @@ #include "hw/usb.h" #include "hw/usb/desc.h" #include "hw/hw.h" -#include "hw/audiodev.h" #include "audio/audio.h" #define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ @@ -217,7 +216,7 @@ static const USBDescIface desc_iface[] = { }; static const USBDescDevice desc_device = { - .bcdUSB = 0x0200, + .bcdUSB = 0x0100, .bMaxPacketSize0 = 64, .bNumConfigurations = 1, .confs = (USBDescConfig[]) { @@ -503,7 +502,7 @@ static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, return ret; } -static int usb_audio_handle_control(USBDevice *dev, USBPacket *p, +static void usb_audio_handle_control(USBDevice *dev, USBPacket *p, int request, int value, int index, int length, uint8_t *data) { @@ -518,7 +517,7 @@ static int usb_audio_handle_control(USBDevice *dev, USBPacket *p, ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { - return ret; + return; } switch (request) { @@ -534,6 +533,7 @@ static int usb_audio_handle_control(USBDevice *dev, USBPacket *p, } goto fail; } + p->actual_length = ret; break; case ClassInterfaceOutRequest | CR_SET_CUR: @@ -557,10 +557,9 @@ fail: "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n", request, value, index, length); } - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } static void usb_audio_set_interface(USBDevice *dev, int iface, @@ -583,50 +582,35 @@ static void usb_audio_handle_reset(USBDevice *dev) usb_audio_set_output_altset(s, ALTSET_OFF); } -static int usb_audio_handle_dataout(USBAudioState *s, USBPacket *p) +static void usb_audio_handle_dataout(USBAudioState *s, USBPacket *p) { - int rc; - if (s->out.altset == ALTSET_OFF) { - return USB_RET_STALL; + p->status = USB_RET_STALL; + return; } - rc = streambuf_put(&s->out.buf, p); - if (rc < p->iov.size && s->debug > 1) { + 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 - rc); + p->iov.size - p->actual_length); } - - return 0; } -static int usb_audio_handle_data(USBDevice *dev, USBPacket *p) +static void usb_audio_handle_data(USBDevice *dev, USBPacket *p) { USBAudioState *s = (USBAudioState *) dev; - int ret = 0; - switch (p->pid) { - case USB_TOKEN_OUT: - switch (p->ep->nr) { - case 1: - ret = usb_audio_handle_dataout(s, p); - break; - default: - goto fail; - } - break; - - default: -fail: - ret = USB_RET_STALL; - break; + if (p->pid == USB_TOKEN_OUT && p->ep->nr == 1) { + usb_audio_handle_dataout(s, p); + return; } - if (ret == USB_RET_STALL && s->debug) { + + 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); } - return ret; } static void usb_audio_handle_destroy(USBDevice *dev) @@ -699,7 +683,7 @@ static void usb_audio_class_init(ObjectClass *klass, void *data) k->set_interface = usb_audio_set_interface; } -static TypeInfo usb_audio_info = { +static const TypeInfo usb_audio_info = { .name = "usb-audio", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBAudioState), diff --git a/hw/usb/dev-bluetooth.c b/hw/usb/dev-bluetooth.c index 55bc19184b..68cc1d4fab 100644 --- a/hw/usb/dev-bluetooth.c +++ b/hw/usb/dev-bluetooth.c @@ -21,12 +21,13 @@ #include "qemu-common.h" #include "hw/usb.h" #include "hw/usb/desc.h" -#include "net.h" +#include "sysemu/bt.h" #include "hw/bt.h" struct USBBtState { USBDevice dev; struct HCIInfo *hci; + USBEndpoint *intr; int config; @@ -285,13 +286,12 @@ static void usb_bt_fifo_enqueue(struct usb_hci_in_fifo_s *fifo, fifo->fifo[off].len = len; } -static inline int usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo, +static inline void usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo, USBPacket *p) { int len; - if (likely(!fifo->len)) - return USB_RET_STALL; + assert(fifo->len != 0); len = MIN(p->iov.size, fifo->fifo[fifo->start].len); usb_packet_copy(p, fifo->fifo[fifo->start].data, len); @@ -310,8 +310,6 @@ static inline int usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo, fifo->dstart = 0; fifo->dsize = DFIFO_LEN_MASK + 1; } - - return len; } static inline void usb_bt_fifo_out_enqueue(struct USBBtState *s, @@ -363,7 +361,7 @@ static void usb_bt_handle_reset(USBDevice *dev) s->outsco.len = 0; } -static int usb_bt_handle_control(USBDevice *dev, USBPacket *p, +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; @@ -382,16 +380,15 @@ static int usb_bt_handle_control(USBDevice *dev, USBPacket *p, usb_bt_fifo_reset(&s->sco); break; } - return ret; + return; } - ret = 0; switch (request) { case InterfaceRequest | USB_REQ_GET_STATUS: case EndpointRequest | USB_REQ_GET_STATUS: data[0] = 0x00; data[1] = 0x00; - ret = 2; + p->actual_length = 2; break; case InterfaceOutRequest | USB_REQ_CLEAR_FEATURE: case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: @@ -407,16 +404,14 @@ static int usb_bt_handle_control(USBDevice *dev, USBPacket *p, break; default: fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } -static int usb_bt_handle_data(USBDevice *dev, USBPacket *p) +static void usb_bt_handle_data(USBDevice *dev, USBPacket *p) { struct USBBtState *s = (struct USBBtState *) dev->opaque; - int ret = 0; if (!s->config) goto fail; @@ -425,15 +420,27 @@ static int usb_bt_handle_data(USBDevice *dev, USBPacket *p) case USB_TOKEN_IN: switch (p->ep->nr) { case USB_EVT_EP: - ret = usb_bt_fifo_dequeue(&s->evt, p); + if (s->evt.len == 0) { + p->status = USB_RET_NAK; + break; + } + usb_bt_fifo_dequeue(&s->evt, p); break; case USB_ACL_EP: - ret = usb_bt_fifo_dequeue(&s->acl, p); + if (s->evt.len == 0) { + p->status = USB_RET_STALL; + break; + } + usb_bt_fifo_dequeue(&s->acl, p); break; case USB_SCO_EP: - ret = usb_bt_fifo_dequeue(&s->sco, p); + if (s->evt.len == 0) { + p->status = USB_RET_STALL; + break; + } + usb_bt_fifo_dequeue(&s->sco, p); break; default: @@ -460,11 +467,9 @@ static int usb_bt_handle_data(USBDevice *dev, USBPacket *p) default: fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - - return ret; } static void usb_bt_out_hci_packet_event(void *opaque, @@ -472,6 +477,9 @@ static void usb_bt_out_hci_packet_event(void *opaque, { struct USBBtState *s = (struct USBBtState *) opaque; + if (s->evt.len == 0) { + usb_wakeup(s->intr, 0); + } usb_bt_fifo_enqueue(&s->evt, data, len); } @@ -494,8 +502,12 @@ static void usb_bt_handle_destroy(USBDevice *dev) static int usb_bt_initfn(USBDevice *dev) { + struct USBBtState *s = DO_UPCAST(struct USBBtState, dev, dev); + usb_desc_create_serial(dev); usb_desc_init(dev); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, USB_EVT_EP); + return 0; } @@ -543,7 +555,7 @@ static void usb_bt_class_initfn(ObjectClass *klass, void *data) dc->vmsd = &vmstate_usb_bt; } -static TypeInfo bt_info = { +static const TypeInfo bt_info = { .name = "usb-bt-dongle", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(struct USBBtState), diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c index bbc48ca96c..13c8adb585 100644 --- a/hw/usb/dev-hid.c +++ b/hw/usb/dev-hid.c @@ -23,11 +23,11 @@ * THE SOFTWARE. */ #include "hw/hw.h" -#include "console.h" +#include "ui/console.h" #include "hw/usb.h" #include "hw/usb/desc.h" -#include "qemu-timer.h" -#include "hw/hid.h" +#include "qemu/timer.h" +#include "hw/input/hid.h" #ifdef CONFIG_MARU #include "../tizen/src/mloop_event.h" @@ -50,6 +50,7 @@ typedef struct USBHIDState { USBDevice dev; USBEndpoint *intr; HIDState hid; + uint32_t usb_version; } USBHIDState; enum { @@ -135,6 +136,36 @@ static const USBDescIface desc_iface_tablet = { }, }; +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, @@ -200,6 +231,23 @@ static const USBDescDevice desc_device_tablet = { }, }; +static const USBDescDevice desc_device_tablet2 = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_TABLET, + .bmAttributes = 0x80, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface_tablet2, + }, + }, +}; + static const USBDescDevice desc_device_keyboard = { .bcdUSB = 0x0100, .bMaxPacketSize0 = 8, @@ -243,6 +291,20 @@ static const USBDesc desc_tablet = { .str = desc_strings, }; +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, +}; + static const USBDesc desc_keyboard = { .id = { .idVendor = 0x0627, @@ -365,7 +427,7 @@ static void usb_hid_changed(HIDState *hs) { USBHIDState *us = container_of(hs, USBHIDState, hid); - usb_wakeup(us->intr); + usb_wakeup(us->intr, 0); } static void usb_hid_handle_reset(USBDevice *dev) @@ -375,7 +437,7 @@ static void usb_hid_handle_reset(USBDevice *dev) hid_reset(&us->hid); } -static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, +static void usb_hid_handle_control(USBDevice *dev, USBPacket *p, int request, int value, int index, int length, uint8_t *data) { USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); @@ -384,10 +446,9 @@ static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { - return ret; + return; } - ret = 0; switch (request) { /* hid specific requests */ case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: @@ -396,15 +457,15 @@ static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, if (hs->kind == HID_MOUSE) { memcpy(data, qemu_mouse_hid_report_descriptor, sizeof(qemu_mouse_hid_report_descriptor)); - ret = sizeof(qemu_mouse_hid_report_descriptor); + 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)); - ret = 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)); - ret = sizeof(qemu_keyboard_hid_report_descriptor); + p->actual_length = sizeof(qemu_keyboard_hid_report_descriptor); } break; default: @@ -413,14 +474,14 @@ static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, break; case GET_REPORT: if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { - ret = hid_pointer_poll(hs, data, length); + p->actual_length = hid_pointer_poll(hs, data, length); } else if (hs->kind == HID_KEYBOARD) { - ret = hid_keyboard_poll(hs, data, length); + p->actual_length = hid_keyboard_poll(hs, data, length); } break; case SET_REPORT: if (hs->kind == HID_KEYBOARD) { - ret = hid_keyboard_write(hs, data, length); + p->actual_length = hid_keyboard_write(hs, data, length); } else { goto fail; } @@ -429,61 +490,57 @@ static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { goto fail; } - ret = 1; data[0] = hs->protocol; + p->actual_length = 1; break; case SET_PROTOCOL: if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { goto fail; } - ret = 0; hs->protocol = value; break; case GET_IDLE: - ret = 1; data[0] = hs->idle; + p->actual_length = 1; break; case SET_IDLE: hs->idle = (uint8_t) (value >> 8); - hid_set_next_idle(hs, qemu_get_clock_ns(vm_clock)); + hid_set_next_idle(hs); if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { hid_pointer_activate(hs); } - ret = 0; break; default: fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } -static int usb_hid_handle_data(USBDevice *dev, USBPacket *p) +static void usb_hid_handle_data(USBDevice *dev, USBPacket *p) { USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); HIDState *hs = &us->hid; uint8_t buf[p->iov.size]; - int ret = 0; + int len = 0; switch (p->pid) { case USB_TOKEN_IN: if (p->ep->nr == 1) { - int64_t curtime = qemu_get_clock_ns(vm_clock); if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { hid_pointer_activate(hs); } - if (!hid_has_events(hs) && - (!hs->idle || hs->next_idle_clock - curtime > 0)) { - return USB_RET_NAK; + if (!hid_has_events(hs)) { + p->status = USB_RET_NAK; + return; } - hid_set_next_idle(hs, curtime); + hid_set_next_idle(hs); if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { - ret = hid_pointer_poll(hs, buf, p->iov.size); + len = hid_pointer_poll(hs, buf, p->iov.size); } else if (hs->kind == HID_KEYBOARD) { - ret = hid_keyboard_poll(hs, buf, p->iov.size); + len = hid_keyboard_poll(hs, buf, p->iov.size); } - usb_packet_copy(p, buf, ret); + usb_packet_copy(p, buf, len); } else { goto fail; } @@ -491,10 +548,9 @@ static int usb_hid_handle_data(USBDevice *dev, USBPacket *p) case USB_TOKEN_OUT: default: fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } static void usb_hid_handle_destroy(USBDevice *dev) @@ -522,6 +578,21 @@ static int usb_hid_initfn(USBDevice *dev, int kind) static int usb_tablet_initfn(USBDevice *dev) { + USBHIDState *us = DO_UPCAST(USBHIDState, dev, dev); + + switch (us->usb_version) { + case 1: + dev->usb_desc = &desc_tablet; + break; + case 2: + dev->usb_desc = &desc_tablet2; + break; + default: + error_report("Invalid usb version %d for usb-tabler (must be 1 or 2)", + us->usb_version); + return -1; + } + return usb_hid_initfn(dev, HID_TABLET); } @@ -579,8 +650,14 @@ static void usb_hid_class_initfn(ObjectClass *klass, void *data) 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 Property usb_tablet_properties[] = { + DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2), + DEFINE_PROP_END_OF_LIST(), +}; + static void usb_tablet_class_initfn(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -589,11 +666,11 @@ static void usb_tablet_class_initfn(ObjectClass *klass, void *data) usb_hid_class_initfn(klass, data); uc->init = usb_tablet_initfn; uc->product_desc = "QEMU USB Tablet"; - uc->usb_desc = &desc_tablet; dc->vmsd = &vmstate_usb_ptr; + dc->props = usb_tablet_properties; } -static TypeInfo usb_tablet_info = { +static const TypeInfo usb_tablet_info = { .name = "usb-tablet", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBHIDState), @@ -612,7 +689,7 @@ static void usb_mouse_class_initfn(ObjectClass *klass, void *data) dc->vmsd = &vmstate_usb_ptr; } -static TypeInfo usb_mouse_info = { +static const TypeInfo usb_mouse_info = { .name = "usb-mouse", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBHIDState), @@ -631,7 +708,7 @@ static void usb_keyboard_class_initfn(ObjectClass *klass, void *data) dc->vmsd = &vmstate_usb_kbd; } -static TypeInfo usb_keyboard_info = { +static const TypeInfo usb_keyboard_info = { .name = "usb-kbd", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBHIDState), diff --git a/hw/usb/dev-hub.c b/hw/usb/dev-hub.c index 8fd30df0e6..0b71abd028 100644 --- a/hw/usb/dev-hub.c +++ b/hw/usb/dev-hub.c @@ -25,6 +25,7 @@ #include "trace.h" #include "hw/usb.h" #include "hw/usb/desc.h" +#include "qemu/error-report.h" #define NUM_PORTS 8 @@ -32,6 +33,7 @@ typedef struct USBHubPort { USBPort port; uint16_t wPortStatus; uint16_t wPortChange; + uint16_t wPortChange_reported; } USBHubPort; typedef struct USBHubState { @@ -164,7 +166,7 @@ static void usb_hub_attach(USBPort *port1) } else { port->wPortStatus &= ~PORT_STAT_LOW_SPEED; } - usb_wakeup(s->intr); + usb_wakeup(s->intr, 0); } static void usb_hub_detach(USBPort *port1) @@ -173,7 +175,7 @@ static void usb_hub_detach(USBPort *port1) USBHubPort *port = &s->ports[port1->index]; trace_usb_hub_detach(s->dev.addr, port1->index + 1); - usb_wakeup(s->intr); + 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); @@ -184,6 +186,7 @@ static void usb_hub_detach(USBPort *port1) 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) @@ -201,7 +204,7 @@ static void usb_hub_wakeup(USBPort *port1) if (port->wPortStatus & PORT_STAT_SUSPEND) { port->wPortChange |= PORT_STAT_C_SUSPEND; - usb_wakeup(s->intr); + usb_wakeup(s->intr, 0); } } @@ -288,7 +291,7 @@ static const char *feature_name(int feature) return name[feature] ?: "?"; } -static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, +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; @@ -298,7 +301,7 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { - return ret; + return; } switch(request) { @@ -306,7 +309,6 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, if (value == 0 && index != 0x81) { /* clear ep halt */ goto fail; } - ret = 0; break; /* usb specific requests */ case GetHubStatus: @@ -314,7 +316,7 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, data[1] = 0; data[2] = 0; data[3] = 0; - ret = 4; + p->actual_length = 4; break; case GetPortStatus: { @@ -331,16 +333,14 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, data[1] = port->wPortStatus >> 8; data[2] = port->wPortChange; data[3] = port->wPortChange >> 8; - ret = 4; + p->actual_length = 4; } break; case SetHubFeature: case ClearHubFeature: - if (value == 0 || value == 1) { - } else { + if (value != 0 && value != 1) { goto fail; } - ret = 0; break; case SetPortFeature: { @@ -366,6 +366,7 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, port->wPortChange |= PORT_STAT_C_RESET; /* set enable bit */ port->wPortStatus |= PORT_STAT_ENABLE; + usb_wakeup(s->intr, 0); } break; case PORT_POWER: @@ -373,7 +374,6 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, default: goto fail; } - ret = 0; } break; case ClearPortFeature: @@ -413,7 +413,6 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, default: goto fail; } - ret = 0; } break; case GetHubDescriptor: @@ -437,22 +436,20 @@ static int usb_hub_handle_control(USBDevice *dev, USBPacket *p, var_hub_size++; } - ret = sizeof(qemu_hub_hub_descriptor) + var_hub_size; - data[0] = ret; + p->actual_length = sizeof(qemu_hub_hub_descriptor) + var_hub_size; + data[0] = p->actual_length; break; } default: fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } -static int usb_hub_handle_data(USBDevice *dev, USBPacket *p) +static void usb_hub_handle_data(USBDevice *dev, USBPacket *p) { USBHubState *s = (USBHubState *)dev; - int ret; switch(p->pid) { case USB_TOKEN_IN: @@ -465,22 +462,25 @@ static int usb_hub_handle_data(USBDevice *dev, USBPacket *p) if (p->iov.size == 1) { /* FreeBSD workaround */ n = 1; } else if (n > p->iov.size) { - return USB_RET_BABBLE; + p->status = USB_RET_BABBLE; + return; } status = 0; for(i = 0; i < NUM_PORTS; i++) { port = &s->ports[i]; - if (port->wPortChange) + if (port->wPortChange && + port->wPortChange_reported != port->wPortChange) { status |= (1 << (i + 1)); + } + port->wPortChange_reported = port->wPortChange; } if (status != 0) { for(i = 0; i < n; i++) { buf[i] = status >> (8 * i); } usb_packet_copy(p, buf, n); - ret = n; } else { - ret = USB_RET_NAK; /* usb11 11.13.1 */ + p->status = USB_RET_NAK; /* usb11 11.13.1 */ } } else { goto fail; @@ -489,10 +489,9 @@ static int usb_hub_handle_data(USBDevice *dev, USBPacket *p) case USB_TOKEN_OUT: default: fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } static void usb_hub_handle_destroy(USBDevice *dev) @@ -520,6 +519,11 @@ static int usb_hub_initfn(USBDevice *dev) USBHubPort *port; int i; + if (dev->port->hubcount == 5) { + error_report("usb hub chain too deep"); + return -1; + } + usb_desc_create_serial(dev); usb_desc_init(dev); s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); @@ -574,7 +578,7 @@ static void usb_hub_class_initfn(ObjectClass *klass, void *data) dc->vmsd = &vmstate_usb_hub; } -static TypeInfo hub_info = { +static const TypeInfo hub_info = { .name = "usb-hub", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBHubState), diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c index c84892c98d..5473ac2cd5 100644 --- a/hw/usb/dev-network.c +++ b/hw/usb/dev-network.c @@ -26,10 +26,12 @@ #include "qemu-common.h" #include "hw/usb.h" #include "hw/usb/desc.h" -#include "net.h" -#include "qemu-queue.h" -#include "sysemu.h" -#include "iov.h" +#include "net/net.h" +#include "qapi/qmp/qerror.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. @@ -639,6 +641,8 @@ typedef struct USBNetState { unsigned int in_ptr, in_len; uint8_t in_buf[2048]; + USBEndpoint *intr; + char usbstring_mac[13]; NICState *nic; NICConf conf; @@ -851,6 +855,10 @@ 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; @@ -1001,6 +1009,13 @@ static int rndis_keepalive_response(USBNetState *s, 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; @@ -1025,7 +1040,8 @@ static int rndis_parse(USBNetState *s, uint8_t *data, int length) case RNDIS_RESET_MSG: rndis_clear_responsequeue(s); - s->out_ptr = s->in_ptr = s->in_len = 0; + s->out_ptr = 0; + usb_net_reset_in_buf(s); return rndis_reset_response(s, (rndis_reset_msg_type *) data); case RNDIS_KEEPALIVE_MSG: @@ -1040,7 +1056,7 @@ static void usb_net_handle_reset(USBDevice *dev) { } -static int usb_net_handle_control(USBDevice *dev, USBPacket *p, +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; @@ -1048,10 +1064,9 @@ static int usb_net_handle_control(USBDevice *dev, USBPacket *p, ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { - return ret; + return; } - ret = 0; switch(request) { case ClassInterfaceOutRequest | USB_CDC_SEND_ENCAPSULATED_COMMAND: if (!is_rndis(s) || value || index != 0) { @@ -1070,22 +1085,25 @@ static int usb_net_handle_control(USBDevice *dev, USBPacket *p, } #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; } - ret = rndis_get_response(s, data); - if (!ret) { + p->actual_length = rndis_get_response(s, data); + if (p->actual_length == 0) { data[0] = 0; - ret = 1; + p->actual_length = 1; } #ifdef TRAFFIC_DEBUG { unsigned int i; fprintf(stderr, "GET_ENCAPSULATED_RESPONSE:"); - for (i = 0; i < ret; i++) { + for (i = 0; i < p->actual_length; i++) { if (!(i & 15)) fprintf(stderr, "\n%04x:", i); fprintf(stderr, " %02x", data[i]); @@ -1100,72 +1118,67 @@ static int usb_net_handle_control(USBDevice *dev, USBPacket *p, fprintf(stderr, "usbnet: failed control transaction: " "request 0x%x value 0x%x index 0x%x length 0x%x\n", request, value, index, length); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } -static int usb_net_handle_statusin(USBNetState *s, USBPacket *p) +static void usb_net_handle_statusin(USBNetState *s, USBPacket *p) { le32 buf[2]; - int ret = 8; if (p->iov.size < 8) { - return USB_RET_STALL; + 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) - ret = USB_RET_NAK; + 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, ret); - iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", ret); + p->iov.size, p->status); + iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->status); #endif - - return ret; } -static int usb_net_handle_datain(USBNetState *s, USBPacket *p) +static void usb_net_handle_datain(USBNetState *s, USBPacket *p) { - int ret = USB_RET_NAK; + int len; if (s->in_ptr > s->in_len) { - s->in_ptr = s->in_len = 0; - ret = USB_RET_NAK; - return ret; + usb_net_reset_in_buf(s); + p->status = USB_RET_NAK; + return; } if (!s->in_len) { - ret = USB_RET_NAK; - return ret; + p->status = USB_RET_NAK; + return; } - ret = s->in_len - s->in_ptr; - if (ret > p->iov.size) { - ret = p->iov.size; + 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], ret); - s->in_ptr += ret; + 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)) || !ret)) { + (is_rndis(s) || (s->in_len & (64 - 1)) || !len)) { /* no short packet necessary */ - s->in_ptr = s->in_len = 0; + usb_net_reset_in_buf(s); } #ifdef TRAFFIC_DEBUG - fprintf(stderr, "usbnet: data in len %zu return %d", p->iov.size, ret); - iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", ret); + 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 - - return ret; } -static int usb_net_handle_dataout(USBNetState *s, USBPacket *p) +static void usb_net_handle_dataout(USBNetState *s, USBPacket *p) { - int ret = p->iov.size; int sz = sizeof(s->out_buf) - s->out_ptr; struct rndis_packet_msg_type *msg = (struct rndis_packet_msg_type *) s->out_buf; @@ -1176,47 +1189,46 @@ static int usb_net_handle_dataout(USBNetState *s, USBPacket *p) iov_hexdump(p->iov.iov, p->iov.niov, stderr, "usbnet", p->iov.size); #endif - if (sz > ret) - sz = ret; + 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 (ret < 64) { - qemu_send_packet(&s->nic->nc, s->out_buf, s->out_ptr); + if (p->iov.size < 64) { + qemu_send_packet(qemu_get_queue(s->nic), s->out_buf, s->out_ptr); s->out_ptr = 0; } - return ret; + return; } len = le32_to_cpu(msg->MessageLength); - if (s->out_ptr < 8 || s->out_ptr < len) - return ret; + 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(&s->nic->nc, s->out_buf + offs, size); + 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); - - return ret; } -static int usb_net_handle_data(USBDevice *dev, USBPacket *p) +static void usb_net_handle_data(USBDevice *dev, USBPacket *p) { USBNetState *s = (USBNetState *) dev; - int ret = 0; switch(p->pid) { case USB_TOKEN_IN: switch (p->ep->nr) { case 1: - ret = usb_net_handle_statusin(s, p); + usb_net_handle_statusin(s, p); break; case 2: - ret = usb_net_handle_datain(s, p); + usb_net_handle_datain(s, p); break; default: @@ -1227,7 +1239,7 @@ static int usb_net_handle_data(USBDevice *dev, USBPacket *p) case USB_TOKEN_OUT: switch (p->ep->nr) { case 2: - ret = usb_net_handle_dataout(s, p); + usb_net_handle_dataout(s, p); break; default: @@ -1237,33 +1249,46 @@ static int usb_net_handle_data(USBDevice *dev, USBPacket *p) default: fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - if (ret == USB_RET_STALL) + + 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); - return ret; + } } static ssize_t usbnet_receive(NetClientState *nc, const uint8_t *buf, size_t size) { - USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; - struct rndis_packet_msg_type *msg; + USBNetState *s = qemu_get_nic_opaque(nc); + uint8_t *in_buf = s->in_buf; + size_t total_size = size; if (is_rndis(s)) { - msg = (struct rndis_packet_msg_type *) s->in_buf; if (s->rndis_state != RNDIS_DATA_INITIALIZED) { return -1; } - if (size + sizeof(struct rndis_packet_msg_type) > sizeof(s->in_buf)) - return -1; + 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(struct rndis_packet_msg_type)); - msg->DataOffset = cpu_to_le32(sizeof(struct rndis_packet_msg_type) - 8); + 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; @@ -1273,21 +1298,18 @@ static ssize_t usbnet_receive(NetClientState *nc, const uint8_t *buf, size_t siz * msg->VcHandle; * msg->Reserved; */ - memcpy(msg + 1, buf, size); - s->in_len = size + sizeof(struct rndis_packet_msg_type); - } else { - if (size > sizeof(s->in_buf)) - return -1; - memcpy(s->in_buf, buf, size); - s->in_len = size; + in_buf += sizeof(*msg); } + + memcpy(in_buf, buf, size); + s->in_len = total_size; s->in_ptr = 0; return size; } static int usbnet_can_receive(NetClientState *nc) { - USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + USBNetState *s = qemu_get_nic_opaque(nc); if (is_rndis(s) && s->rndis_state != RNDIS_DATA_INITIALIZED) { return 1; @@ -1298,7 +1320,7 @@ static int usbnet_can_receive(NetClientState *nc) static void usbnet_cleanup(NetClientState *nc) { - USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + USBNetState *s = qemu_get_nic_opaque(nc); s->nic = NULL; } @@ -1309,7 +1331,7 @@ static void usb_net_handle_destroy(USBDevice *dev) /* TODO: remove the nd_table[] entry */ rndis_clear_responsequeue(s); - qemu_del_net_client(&s->nic->nc); + qemu_del_nic(s->nic); } static NetClientInfo net_usbnet_info = { @@ -1335,11 +1357,12 @@ static int usb_net_initfn(USBDevice *dev) 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(&s->nic->nc, s->conf.macaddr.a); + 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, @@ -1411,7 +1434,7 @@ static void usb_net_class_initfn(ObjectClass *klass, void *data) dc->props = net_properties; } -static TypeInfo net_info = { +static const TypeInfo net_info = { .name = "usb-net", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBNetState), diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c index 8aa655286b..2fc8a3b136 100644 --- a/hw/usb/dev-serial.c +++ b/hw/usb/dev-serial.c @@ -9,10 +9,10 @@ */ #include "qemu-common.h" -#include "qemu-error.h" +#include "qemu/error-report.h" #include "hw/usb.h" #include "hw/usb/desc.h" -#include "qemu-char.h" +#include "sysemu/char.h" //#define DEBUG_Serial @@ -113,7 +113,7 @@ enum { static const USBDescStrings desc_strings = { [STR_MANUFACTURER] = "QEMU", [STR_PRODUCT_SERIAL] = "QEMU USB SERIAL", - [STR_PRODUCT_BRAILLE] = "QEMU USB BRAILLE", + [STR_PRODUCT_BRAILLE] = "QEMU USB BAUM BRAILLE", [STR_SERIALNUMBER] = "1", }; @@ -219,7 +219,7 @@ static uint8_t usb_get_modem_lines(USBSerialState *s) return ret; } -static int usb_serial_handle_control(USBDevice *dev, USBPacket *p, +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; @@ -228,13 +228,11 @@ static int usb_serial_handle_control(USBDevice *dev, USBPacket *p, DPRINTF("got control %x, value %x\n",request, value); ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { - return ret; + return; } - ret = 0; switch (request) { case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: - ret = 0; break; /* Class specific requests. */ @@ -323,7 +321,7 @@ static int usb_serial_handle_control(USBDevice *dev, USBPacket *p, case DeviceInVendor | FTDI_GET_MDM_ST: data[0] = usb_get_modem_lines(s) | 1; data[1] = 0; - ret = 2; + p->actual_length = 2; break; case DeviceOutVendor | FTDI_SET_EVENT_CHR: /* TODO: handle it */ @@ -338,25 +336,23 @@ static int usb_serial_handle_control(USBDevice *dev, USBPacket *p, break; case DeviceInVendor | FTDI_GET_LATENCY: data[0] = s->latency; - ret = 1; + p->actual_length = 1; break; default: fail: DPRINTF("got unsupported/bogus control %x, value %x\n", request, value); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } -static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) +static void usb_serial_handle_data(USBDevice *dev, USBPacket *p) { USBSerialState *s = (USBSerialState *)dev; - int i, ret = 0; uint8_t devep = p->ep->nr; struct iovec *iov; uint8_t header[2]; - int first_len, len; + int i, first_len, len; switch (p->pid) { case USB_TOKEN_OUT: @@ -366,6 +362,7 @@ static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) 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: @@ -374,7 +371,7 @@ static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) first_len = RECV_BUF - s->recv_ptr; len = p->iov.size; if (len <= 2) { - ret = USB_RET_NAK; + p->status = USB_RET_NAK; break; } header[0] = usb_get_modem_lines(s) | 1; @@ -384,7 +381,6 @@ static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) s->event_trigger &= ~FTDI_BI; header[1] = FTDI_BI; usb_packet_copy(p, header, 2); - ret = 2; break; } else { header[1] = 0; @@ -393,7 +389,7 @@ static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) if (len > s->recv_used) len = s->recv_used; if (!len) { - ret = USB_RET_NAK; + p->status = USB_RET_NAK; break; } if (first_len > len) @@ -404,29 +400,23 @@ static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) usb_packet_copy(p, s->recv_buf, len - first_len); s->recv_used -= len; s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; - ret = len + 2; break; default: DPRINTF("Bad token\n"); fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - - return ret; -} - -static void usb_serial_handle_destroy(USBDevice *dev) -{ - USBSerialState *s = (USBSerialState *)dev; - - qemu_chr_delete(s->cs); } static int usb_serial_can_read(void *opaque) { USBSerialState *s = opaque; + + if (!s->dev.attached) { + return 0; + } return RECV_BUF - s->recv_used; } @@ -469,8 +459,14 @@ static void usb_serial_event(void *opaque, int event) case CHR_EVENT_FOCUS: break; case CHR_EVENT_OPENED: - usb_serial_reset(s); - /* TODO: Reset USB port */ + if (!s->dev.attached) { + usb_device_attach(&s->dev); + } + break; + case CHR_EVENT_CLOSED: + if (s->dev.attached) { + usb_device_detach(&s->dev); + } break; } } @@ -481,6 +477,7 @@ static int usb_serial_initfn(USBDevice *dev) usb_desc_create_serial(dev); usb_desc_init(dev); + dev->auto_attach = 0; if (!s->cs) { error_report("Property chardev is required"); @@ -490,6 +487,10 @@ static int usb_serial_initfn(USBDevice *dev) 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); + } return 0; } @@ -587,12 +588,11 @@ static void usb_serial_class_initfn(ObjectClass *klass, void *data) uc->handle_reset = usb_serial_handle_reset; uc->handle_control = usb_serial_handle_control; uc->handle_data = usb_serial_handle_data; - uc->handle_destroy = usb_serial_handle_destroy; dc->vmsd = &vmstate_usb_serial; dc->props = serial_properties; } -static TypeInfo serial_info = { +static const TypeInfo serial_info = { .name = "usb-serial", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBSerialState), @@ -615,12 +615,11 @@ static void usb_braille_class_initfn(ObjectClass *klass, void *data) uc->handle_reset = usb_serial_handle_reset; uc->handle_control = usb_serial_handle_control; uc->handle_data = usb_serial_handle_data; - uc->handle_destroy = usb_serial_handle_destroy; dc->vmsd = &vmstate_usb_serial; dc->props = braille_properties; } -static TypeInfo braille_info = { +static const TypeInfo braille_info = { .name = "usb-braille", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBSerialState), diff --git a/hw/usb/dev-smartcard-reader.c b/hw/usb/dev-smartcard-reader.c index 1ea079176a..125cc2c221 100644 --- a/hw/usb/dev-smartcard-reader.c +++ b/hw/usb/dev-smartcard-reader.c @@ -35,12 +35,12 @@ */ #include "qemu-common.h" -#include "qemu-error.h" +#include "qemu/error-report.h" #include "hw/usb.h" #include "hw/usb/desc.h" -#include "monitor.h" +#include "monitor/monitor.h" -#include "hw/ccid.h" +#include "ccid.h" #define DPRINTF(s, lvl, fmt, ...) \ do { \ @@ -68,12 +68,6 @@ do { \ #define BULK_IN_BUF_SIZE 384 #define BULK_IN_PENDING_NUM 8 -#define InterfaceOutClass \ - ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)<<8) - -#define InterfaceInClass \ - ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)<<8) - #define CCID_MAX_PACKET_SIZE 64 #define CCID_CONTROL_ABORT 0x1 @@ -195,10 +189,34 @@ typedef struct QEMU_PACKED CCID_SlotStatus { 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; - uint8_t abProtocolDataStructure[0]; + CCID_ProtocolDataStructure abProtocolDataStructure; } CCID_Parameter; typedef struct QEMU_PACKED CCID_DataBlock { @@ -230,7 +248,7 @@ typedef struct QEMU_PACKED CCID_SetParameters { CCID_Header hdr; uint8_t bProtocolNum; uint16_t abRFU; - uint8_t abProtocolDataStructure[0]; + CCID_ProtocolDataStructure abProtocolDataStructure; } CCID_SetParameters; typedef struct CCID_Notify_Slot_Change { @@ -260,8 +278,6 @@ typedef struct CCIDBus { BusState qbus; } CCIDBus; -#define MAX_PROTOCOL_SIZE 7 - /* * powered - defaults to true, changed by PowerOn/PowerOff messages */ @@ -285,7 +301,7 @@ typedef struct USBCCIDState { uint8_t bError; uint8_t bmCommandStatus; uint8_t bProtocolNum; - uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; + CCID_ProtocolDataStructure abProtocolDataStructure; uint32_t ulProtocolDataStructureSize; uint32_t state_vmstate; uint32_t migration_target_ip; @@ -319,8 +335,8 @@ static const uint8_t qemu_ccid_descriptor[] = { */ 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ - 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ - 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ + 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; */ @@ -359,11 +375,11 @@ static const uint8_t qemu_ccid_descriptor[] = { * 20000 Short APDU level exchange with CCID * 40000 Short and Extended APDU level exchange with CCID * - * + 100000 USB Wake up signaling supported on card + * 100000 USB Wake up signaling supported on card * insertion and removal. Must set bit 5 in bmAttributes * in Configuration descriptor if 100000 is set. */ - 0xfe, 0x04, 0x11, 0x00, + 0xfe, 0x04, 0x01, 0x00, /* * u32 dwMaxCCIDMessageLength; For extended APDU in * [261 + 10 , 65544 + 10]. Otherwise the minimum is @@ -410,8 +426,8 @@ static const USBDescStrings desc_strings = { static const USBDescIface desc_iface0 = { .bInterfaceNumber = 0, .bNumEndpoints = 3, - .bInterfaceClass = 0x0b, - .bInterfaceSubClass = 0x00, + .bInterfaceClass = USB_CLASS_CSCID, + .bInterfaceSubClass = USB_SUBCLASS_UNDEFINED, .bInterfaceProtocol = 0x00, .iInterface = STR_INTERFACE, .ndesc = 1, @@ -471,6 +487,7 @@ static const USBDesc desc_ccid = { 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); } @@ -482,6 +499,7 @@ static void ccid_card_apdu_from_guest(CCIDCardState *card, uint32_t len) { CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + if (cc->apdu_from_guest) { cc->apdu_from_guest(card, apdu, len); } @@ -490,6 +508,7 @@ static void ccid_card_apdu_from_guest(CCIDCardState *card, static int ccid_card_exitfn(CCIDCardState *card) { CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + if (cc->exitfn) { return cc->exitfn(card); } @@ -499,6 +518,7 @@ static int ccid_card_exitfn(CCIDCardState *card) static int ccid_card_initfn(CCIDCardState *card) { CCIDCardClass *cc = CCID_CARD_GET_CLASS(card); + if (cc->initfn) { return cc->initfn(card); } @@ -635,39 +655,72 @@ static void ccid_handle_reset(USBDevice *dev) ccid_reset(s); } -static int ccid_handle_control(USBDevice *dev, USBPacket *p, int request, +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 = DO_UPCAST(USBCCIDState, dev, dev); - int ret = 0; + int ret; - DPRINTF(s, 1, "got control %x, value %x\n", request, value); + 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 ret; + return; } switch (request) { /* Class specific requests. */ - case InterfaceOutClass | CCID_CONTROL_ABORT: + case ClassInterfaceOutRequest | CCID_CONTROL_ABORT: DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; - case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + case ClassInterfaceRequest | CCID_CONTROL_GET_CLOCK_FREQUENCIES: DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; - case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: + case ClassInterfaceRequest | CCID_CONTROL_GET_DATA_RATES: DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; default: DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", request, value); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } static bool ccid_card_inserted(USBCCIDState *s) @@ -692,7 +745,7 @@ static uint8_t ccid_calc_status(USBCCIDState *s) * bmCommandStatus */ uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus << 6); - DPRINTF(s, D_VERBOSE, "status = %d\n", ret); + DPRINTF(s, D_VERBOSE, "%s: status = %d\n", __func__, ret); return ret; } @@ -734,7 +787,7 @@ static void ccid_write_parameters(USBCCIDState *s, CCID_Header *recv) h->b.bStatus = ccid_calc_status(s); h->b.bError = s->bError; h->bProtocolNum = s->bProtocolNum; - memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); + h->abProtocolDataStructure = s->abProtocolDataStructure; ccid_reset_error_status(s); } @@ -753,12 +806,18 @@ static void ccid_write_data_block(USBCCIDState *s, uint8_t slot, uint8_t seq, p->b.bStatus = ccid_calc_status(s); p->b.bError = s->bError; if (p->b.bError) { - DPRINTF(s, D_VERBOSE, "error %d", p->b.bError); + DPRINTF(s, D_VERBOSE, "error %d\n", p->b.bError); } memcpy(p->abData, data, len); ccid_reset_error_status(s); } +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) { @@ -766,64 +825,103 @@ static void ccid_write_data_block_answer(USBCCIDState *s, uint8_t slot; if (!ccid_has_pending_answers(s)) { - abort(); + 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 len = 0; - if ((ph->bProtocolNum & 3) == 0) { - len = 5; - } - if ((ph->bProtocolNum & 3) == 1) { - len = 7; - } - if (len == 0) { - s->bmCommandStatus = COMMAND_STATUS_FAILED; - s->bError = 7; /* Protocol invalid or not supported */ + 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 = ph->bProtocolNum; - memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); - s->ulProtocolDataStructureSize = len; - DPRINTF(s, 1, "%s: using len %d\n", __func__, len); + 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 uint8_t abDefaultProtocolDataStructure[7] = { - 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; +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) { - uint32_t len = sizeof(abDefaultProtocolDataStructure); - - s->bProtocolNum = 1; /* T=1 */ - s->ulProtocolDataStructureSize = len; - memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); -} - -static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) -{ - s->bmCommandStatus = COMMAND_STATUS_FAILED; - s->bError = error; + s->bProtocolNum = 0; /* T=0 */ + s->abProtocolDataStructure = defaultProtocolDataStructure; } /* NOTE: only a single slot is supported (SLOT_0) */ @@ -840,7 +938,7 @@ static void ccid_on_slot_change(USBCCIDState *s, bool full) s->bmSlotICCState |= SLOT_0_CHANGED_MASK; } s->notify_slot_change = true; - usb_wakeup(s->intr); + usb_wakeup(s->intr, 0); } static void ccid_write_data_block_error( @@ -870,18 +968,35 @@ static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) } } -/* - * Handle a single USB_TOKEN_OUT, return value returned to guest. - * Return value: - * 0 - all ok - * USB_RET_STALL - failed to handle packet - */ -static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) +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) { - return USB_RET_STALL; + 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); @@ -890,20 +1005,22 @@ static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) DPRINTF(s, D_VERBOSE, "usb-ccid: bulk_in: expecting more packets (%zd/%d)\n", p->iov.size, ccid_header->dwLength); - return 0; + 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\n", __func__, ccid_header->bMessageType); + 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, "PowerOn: %d\n", + DPRINTF(s, 1, "%s: PowerOn: %d\n", __func__, ((CCID_IccPowerOn *)(ccid_header))->bPowerSelect); s->powered = true; if (!ccid_card_inserted(s)) { @@ -913,7 +1030,6 @@ static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) ccid_write_data_block_atr(s, ccid_header); break; case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: - DPRINTF(s, 1, "PowerOff\n"); ccid_reset_error_status(s); s->powered = false; ccid_write_slot_status(s, ccid_header); @@ -935,6 +1051,10 @@ static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) 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", @@ -949,60 +1069,52 @@ static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) } } s->bulk_out_pos = 0; - return 0; } -static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, USBPacket *p) +static void ccid_bulk_in_copy_to_guest(USBCCIDState *s, USBPacket *p) { - int ret = 0; + int len = 0; - assert(p->iov.size > 0); ccid_bulk_in_get(s); if (s->current_bulk_in != NULL) { - ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, + 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, ret); - s->current_bulk_in->pos += ret; + 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 */ - ret = USB_RET_NAK; + p->status = USB_RET_NAK; } - if (ret > 0) { + if (len) { DPRINTF(s, D_MORE_INFO, "%s: %zd/%d req/act to guest (BULK_IN)\n", - __func__, p->iov.size, ret); + __func__, p->iov.size, len); } - if (ret != USB_RET_NAK && ret < p->iov.size) { + if (len < p->iov.size) { DPRINTF(s, 1, "%s: returning short (EREMOTEIO) %d < %zd\n", - __func__, ret, p->iov.size); + __func__, len, p->iov.size); } - return ret; } -static int ccid_handle_data(USBDevice *dev, USBPacket *p) +static void ccid_handle_data(USBDevice *dev, USBPacket *p) { USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); - int ret = 0; uint8_t buf[2]; switch (p->pid) { case USB_TOKEN_OUT: - ret = ccid_handle_bulk_out(s, p); + ccid_handle_bulk_out(s, p); break; case USB_TOKEN_IN: switch (p->ep->nr) { case CCID_BULK_IN_EP: - if (!p->iov.size) { - ret = USB_RET_NAK; - } else { - ret = ccid_bulk_in_copy_to_guest(s, p); - } + ccid_bulk_in_copy_to_guest(s, p); break; case CCID_INT_IN_EP: if (s->notify_slot_change) { @@ -1010,28 +1122,27 @@ static int ccid_handle_data(USBDevice *dev, USBPacket *p) buf[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; buf[1] = s->bmSlotICCState; usb_packet_copy(p, buf, 2); - ret = 2; s->notify_slot_change = false; s->bmSlotICCState &= ~SLOT_0_CHANGED_MASK; DPRINTF(s, D_INFO, "handle_data: int_in: notify_slot_change %X, " "requested len %zd\n", s->bmSlotICCState, p->iov.size); + } else { + p->status = USB_RET_NAK; } break; default: DPRINTF(s, 1, "Bad endpoint\n"); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } break; default: DPRINTF(s, 1, "Bad token\n"); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - - return ret; } static void ccid_handle_destroy(USBDevice *dev) @@ -1083,7 +1194,9 @@ void ccid_card_send_apdu_to_guest(CCIDCardState *card, s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; answer = ccid_peek_next_answer(s); if (answer == NULL) { - abort(); + 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); @@ -1216,6 +1329,7 @@ static int ccid_initfn(USBDevice *dev) s->bulk_out_pos = 0; ccid_reset_parameters(s); ccid_reset(s); + s->debug = parse_debug_env("QEMU_CCID_DEBUG", D_VERBOSE, s->debug); return 0; } @@ -1300,7 +1414,7 @@ static VMStateDescription ccid_vmstate = { VMSTATE_UINT8(bError, USBCCIDState), VMSTATE_UINT8(bmCommandStatus, USBCCIDState), VMSTATE_UINT8(bProtocolNum, USBCCIDState), - VMSTATE_BUFFER(abProtocolDataStructure, 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), @@ -1337,7 +1451,7 @@ static void ccid_class_initfn(ObjectClass *klass, void *data) dc->props = ccid_properties; } -static TypeInfo ccid_info = { +static const TypeInfo ccid_info = { .name = CCID_DEV_NAME, .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBCCIDState), @@ -1353,7 +1467,7 @@ static void ccid_card_class_init(ObjectClass *klass, void *data) k->props = ccid_props; } -static TypeInfo ccid_card_type_info = { +static const TypeInfo ccid_card_type_info = { .name = TYPE_CCID_CARD, .parent = TYPE_DEVICE, .instance_size = sizeof(CCIDCardState), diff --git a/hw/usb/dev-storage.c b/hw/usb/dev-storage.c index 0efd16658d..2532de8838 100644 --- a/hw/usb/dev-storage.c +++ b/hw/usb/dev-storage.c @@ -8,15 +8,15 @@ */ #include "qemu-common.h" -#include "qemu-option.h" -#include "qemu-config.h" +#include "qemu/option.h" +#include "qemu/config-file.h" #include "hw/usb.h" #include "hw/usb/desc.h" -#include "hw/scsi.h" -#include "console.h" -#include "monitor.h" -#include "sysemu.h" -#include "blockdev.h" +#include "hw/scsi/scsi.h" +#include "ui/console.h" +#include "monitor/monitor.h" +#include "sysemu/sysemu.h" +#include "sysemu/blockdev.h" #ifdef CONFIG_MARU #include "../tizen/src/mloop_event.h" @@ -58,12 +58,12 @@ typedef struct { struct usb_msd_csw csw; SCSIRequest *req; SCSIBus bus; + /* For async completion. */ + USBPacket *packet; + /* usb-storage only */ BlockConf conf; char *serial; - SCSIDevice *scsi_dev; uint32_t removable; - /* For async completion. */ - USBPacket *packet; } MSDState; struct usb_msd_cbw { @@ -82,6 +82,7 @@ enum { STR_SERIALNUMBER, STR_CONFIG_FULL, STR_CONFIG_HIGH, + STR_CONFIG_SUPER, }; static const USBDescStrings desc_strings = { @@ -90,6 +91,7 @@ static const USBDescStrings desc_strings = { [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 = { @@ -162,6 +164,43 @@ static const USBDescDevice desc_device_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 = 0xc0, + .nif = 1, + .ifs = &desc_iface_super, + }, + }, +}; + static const USBDesc desc = { .id = { .idVendor = 0x46f4, /* CRC16() of "QEMU" */ @@ -171,15 +210,16 @@ static const USBDesc desc = { .iProduct = STR_PRODUCT, .iSerialNumber = STR_SERIALNUMBER, }, - .full = &desc_device_full, - .high = &desc_device_high, - .str = desc_strings, + .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->result; + 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); @@ -227,7 +267,8 @@ static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len) if (p) { usb_msd_copy_data(s, p); p = s->packet; - if (p && p->result == p->iov.size) { + if (p && p->actual_length == p->iov.size) { + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ usb_msd_packet_complete(s); } } @@ -256,7 +297,7 @@ static void usb_msd_command_complete(SCSIRequest *req, uint32_t status, size_t r s->mode = USB_MSDM_CBW; } else { if (s->data_len) { - int len = (p->iov.size - p->result); + int len = (p->iov.size - p->actual_length); usb_packet_skip(p, len); s->data_len -= len; } @@ -264,6 +305,7 @@ static void usb_msd_command_complete(SCSIRequest *req, uint32_t status, size_t r 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; @@ -294,44 +336,53 @@ static void usb_msd_handle_reset(USBDevice *dev) assert(s->req == NULL); if (s->packet) { - s->packet->result = USB_RET_STALL; + s->packet->status = USB_RET_STALL; usb_msd_packet_complete(s); } s->mode = USB_MSDM_CBW; } -static int usb_msd_handle_control(USBDevice *dev, USBPacket *p, +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; - int ret; + SCSIDevice *scsi_dev; + int ret, maxlun; ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { - return ret; + return; } - ret = 0; switch (request) { case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: - ret = 0; break; /* Class specific requests. */ case ClassInterfaceOutRequest | MassStorageReset: /* Reset state ready for the next CBW. */ s->mode = USB_MSDM_CBW; - ret = 0; break; case ClassInterfaceRequest | GetMaxLun: - data[0] = 0; - ret = 1; + 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: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } static void usb_msd_cancel_io(USBDevice *dev, USBPacket *p) @@ -346,13 +397,14 @@ static void usb_msd_cancel_io(USBDevice *dev, USBPacket *p) } } -static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) +static void usb_msd_handle_data(USBDevice *dev, USBPacket *p) { MSDState *s = (MSDState *)dev; uint32_t tag; - int ret = 0; struct usb_msd_cbw cbw; uint8_t devep = p->ep->nr; + SCSIDevice *scsi_dev; + uint32_t len; switch (p->pid) { case USB_TOKEN_OUT: @@ -372,7 +424,8 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) goto fail; } DPRINTF("Command on LUN %d\n", cbw.lun); - if (cbw.lun != 0) { + scsi_dev = scsi_device_find(&s->bus, 0, 0, cbw.lun); + if (scsi_dev == NULL) { fprintf(stderr, "usb-msd: Bad LUN %d\n", cbw.lun); goto fail; } @@ -389,15 +442,14 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) 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(s->scsi_dev, tag, 0, cbw.cmd, NULL); + s->req = scsi_req_new(scsi_dev, tag, cbw.lun, cbw.cmd, NULL); #ifdef DEBUG_MSD scsi_req_print(s->req); #endif - scsi_req_enqueue(s->req); - if (s->req && s->req->cmd.xfer != SCSI_XFER_NONE) { + len = scsi_req_enqueue(s->req); + if (len) { scsi_req_continue(s->req); } - ret = p->result; break; case USB_MSDM_DATAOUT: @@ -410,7 +462,7 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) usb_msd_copy_data(s, p); } if (le32_to_cpu(s->csw.residue)) { - int len = p->iov.size - p->result; + int len = p->iov.size - p->actual_length; if (len) { usb_packet_skip(p, len); s->data_len -= len; @@ -419,12 +471,10 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) } } } - if (p->result < p->iov.size) { + if (p->actual_length < p->iov.size) { DPRINTF("Deferring packet %p [wait data-out]\n", p); s->packet = p; - ret = USB_RET_ASYNC; - } else { - ret = p->result; + p->status = USB_RET_ASYNC; } break; @@ -445,7 +495,7 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) } /* Waiting for SCSI write to complete. */ s->packet = p; - ret = USB_RET_ASYNC; + p->status = USB_RET_ASYNC; break; case USB_MSDM_CSW: @@ -457,11 +507,10 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) /* still in flight */ DPRINTF("Deferring packet %p [wait status]\n", p); s->packet = p; - ret = USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } else { usb_msd_send_status(s, p); s->mode = USB_MSDM_CBW; - ret = 13; } break; @@ -472,7 +521,7 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) usb_msd_copy_data(s, p); } if (le32_to_cpu(s->csw.residue)) { - int len = p->iov.size - p->result; + int len = p->iov.size - p->actual_length; if (len) { usb_packet_skip(p, len); s->data_len -= len; @@ -481,12 +530,10 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) } } } - if (p->result < p->iov.size) { + if (p->actual_length < p->iov.size) { DPRINTF("Deferring packet %p [wait data-in]\n", p); s->packet = p; - ret = USB_RET_ASYNC; - } else { - ret = p->result; + p->status = USB_RET_ASYNC; } break; @@ -499,11 +546,9 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) default: DPRINTF("Bad token\n"); fail: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - - return ret; } #ifdef CONFIG_MARU @@ -535,7 +580,7 @@ static void *usb_msd_load_request(QEMUFile *f, SCSIRequest *req) return NULL; } -static const struct SCSIBusInfo usb_msd_scsi_info = { +static const struct SCSIBusInfo usb_msd_scsi_info_storage = { .tcq = false, .max_target = 0, .max_lun = 0, @@ -546,10 +591,22 @@ static const struct SCSIBusInfo usb_msd_scsi_info = { .load_request = usb_msd_load_request, }; -static int usb_msd_initfn(USBDevice *dev) +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 int usb_msd_initfn_storage(USBDevice *dev) { MSDState *s = DO_UPCAST(MSDState, dev, dev); BlockDriverState *bs = s->conf.bs; + SCSIDevice *scsi_dev; if (!bs) { error_report("drive property not set"); @@ -577,10 +634,10 @@ static int usb_msd_initfn(USBDevice *dev) } usb_desc_init(dev); - scsi_bus_new(&s->bus, &s->dev.qdev, &usb_msd_scsi_info); - s->scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0, !!s->removable, - s->conf.bootindex); - if (!s->scsi_dev) { + scsi_bus_new(&s->bus, &s->dev.qdev, &usb_msd_scsi_info_storage, NULL); + scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0, !!s->removable, + s->conf.bootindex, s->serial); + if (!scsi_dev) { return -1; } s->bus.qbus.allow_hotplug = 0; @@ -598,6 +655,19 @@ static int usb_msd_initfn(USBDevice *dev) return 0; } +static int usb_msd_initfn_bot(USBDevice *dev) +{ + MSDState *s = DO_UPCAST(MSDState, dev, dev); + + usb_desc_create_serial(dev); + usb_desc_init(dev); + scsi_bus_new(&s->bus, &s->dev.qdev, &usb_msd_scsi_info_bot, NULL); + s->bus.qbus.allow_hotplug = 0; + usb_msd_handle_reset(dev); + + return 0; +} + static USBDevice *usb_msd_init(USBBus *bus, const char *filename) { static int nr=0; @@ -687,12 +757,11 @@ static Property msd_properties[] = { DEFINE_PROP_END_OF_LIST(), }; -static void usb_msd_class_initfn(ObjectClass *klass, void *data) +static void usb_msd_class_initfn_common(ObjectClass *klass) { DeviceClass *dc = DEVICE_CLASS(klass); USBDeviceClass *uc = USB_DEVICE_CLASS(klass); - uc->init = usb_msd_initfn; uc->product_desc = "QEMU USB MSD"; uc->usb_desc = &desc; uc->cancel_packet = usb_msd_cancel_io; @@ -705,19 +774,44 @@ static void usb_msd_class_initfn(ObjectClass *klass, void *data) #endif 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->init = usb_msd_initfn_storage; dc->props = msd_properties; + usb_msd_class_initfn_common(klass); } -static TypeInfo msd_info = { +static void usb_msd_class_initfn_bot(ObjectClass *klass, void *data) +{ + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_msd_initfn_bot; + usb_msd_class_initfn_common(klass); +} + +static const TypeInfo msd_info = { .name = "usb-storage", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(MSDState), - .class_init = usb_msd_class_initfn, + .class_init = usb_msd_class_initfn_storage, +}; + +static const TypeInfo bot_info = { + .name = "usb-bot", + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(MSDState), + .class_init = usb_msd_class_initfn_bot, }; static void usb_msd_register_types(void) { type_register_static(&msd_info); + type_register_static(&bot_info); usb_legacy_register("usb-storage", "disk", usb_msd_init); } diff --git a/hw/usb/dev-uas.c b/hw/usb/dev-uas.c index 5a0057a36b..6efab62544 100644 --- a/hw/usb/dev-uas.c +++ b/hw/usb/dev-uas.c @@ -10,14 +10,14 @@ */ #include "qemu-common.h" -#include "qemu-option.h" -#include "qemu-config.h" +#include "qemu/option.h" +#include "qemu/config-file.h" #include "trace.h" #include "hw/usb.h" #include "hw/usb/desc.h" -#include "hw/scsi.h" -#include "hw/scsi-defs.h" +#include "hw/scsi/scsi.h" +#include "block/scsi.h" /* --------------------------------------------------------------------- */ @@ -99,6 +99,9 @@ typedef struct { /* --------------------------------------------------------------------- */ +#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; @@ -106,12 +109,18 @@ typedef struct UASStatus UASStatus; struct UASDevice { USBDevice dev; SCSIBus bus; - UASRequest *datain; - UASRequest *dataout; - USBPacket *status; QEMUBH *status_bh; QTAILQ_HEAD(, UASStatus) results; QTAILQ_HEAD(, UASRequest) requests; + + /* usb 2.0 only */ + USBPacket *status2; + UASRequest *datain2; + UASRequest *dataout2; + + /* usb 3.0 only */ + USBPacket *data3[UAS_MAX_STREAMS]; + USBPacket *status3[UAS_MAX_STREAMS]; }; struct UASRequest { @@ -132,6 +141,7 @@ struct UASRequest { }; struct UASStatus { + uint32_t stream; uas_ui status; uint32_t length; QTAILQ_ENTRY(UASStatus) next; @@ -144,6 +154,7 @@ enum { STR_PRODUCT, STR_SERIALNUMBER, STR_CONFIG_HIGH, + STR_CONFIG_SUPER, }; static const USBDescStrings desc_strings = { @@ -151,6 +162,7 @@ static const USBDescStrings desc_strings = { [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 = { @@ -204,6 +216,64 @@ static const USBDescIface desc_iface_high = { } }; +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, @@ -220,6 +290,22 @@ static const USBDescDevice desc_device_high = { }, }; +static const USBDescDevice desc_device_super = { + .bcdUSB = 0x0300, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_SUPER, + .bmAttributes = 0xc0, + .nif = 1, + .ifs = &desc_iface_super, + }, + }, +}; + static const USBDesc desc = { .id = { .idVendor = 0x46f4, /* CRC16() of "QEMU" */ @@ -229,45 +315,68 @@ static const USBDesc desc = { .iProduct = STR_PRODUCT, .iSerialNumber = STR_SERIALNUMBER, }, - .high = &desc_device_high, - .str = desc_strings, + .high = &desc_device_high, + .super = &desc_device_super, + .str = desc_strings, }; /* --------------------------------------------------------------------- */ -static UASStatus *usb_uas_alloc_status(uint8_t id, uint16_t tag) +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_ui_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 = QTAILQ_FIRST(&uas->results); - USBPacket *p = uas->status; + UASStatus *st; + USBPacket *p; - assert(p != NULL); - assert(st != NULL); + 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; + } - uas->status = NULL; - usb_packet_copy(p, &st->status, st->length); - p->result = st->length; - QTAILQ_REMOVE(&uas->results, st, next); - g_free(st); + usb_packet_copy(p, &st->status, st->length); + QTAILQ_REMOVE(&uas->results, st, next); + g_free(st); - usb_packet_complete(&uas->dev, p); + 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 (uas->status) { + if (p) { /* * Just schedule bh make sure any in-flight data transaction * is finished before completing (sending) the status packet. @@ -276,14 +385,14 @@ static void usb_uas_queue_status(UASDevice *uas, UASStatus *st, int length) } else { USBEndpoint *ep = usb_ep_get(&uas->dev, USB_TOKEN_IN, UAS_PIPE_ID_STATUS); - usb_wakeup(ep); + usb_wakeup(ep, st->stream); } } static void usb_uas_queue_response(UASDevice *uas, uint16_t tag, uint8_t code, uint16_t add_info) { - UASStatus *st = usb_uas_alloc_status(UAS_UI_RESPONSE, tag); + 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; @@ -293,7 +402,7 @@ static void usb_uas_queue_response(UASDevice *uas, uint16_t tag, static void usb_uas_queue_sense(UASRequest *req, uint8_t status) { - UASStatus *st = usb_uas_alloc_status(UAS_UI_SENSE, req->tag); + 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); @@ -310,7 +419,8 @@ static void usb_uas_queue_sense(UASRequest *req, uint8_t status) static void usb_uas_queue_read_ready(UASRequest *req) { - UASStatus *st = usb_uas_alloc_status(UAS_UI_READ_READY, req->tag); + 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); @@ -318,7 +428,8 @@ static void usb_uas_queue_read_ready(UASRequest *req) static void usb_uas_queue_write_ready(UASRequest *req) { - UASStatus *st = usb_uas_alloc_status(UAS_UI_WRITE_READY, req->tag); + 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); @@ -349,6 +460,7 @@ static void usb_uas_complete_data_packet(UASRequest *req) 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); } @@ -357,16 +469,16 @@ 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->result); + req->data->iov.size - req->data->actual_length); trace_usb_uas_xfer_data(req->uas->dev.addr, req->tag, length, - req->data->result, req->data->iov.size, + 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->result == req->data->iov.size) { + 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) { @@ -380,18 +492,22 @@ 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->datain == NULL) { - uas->datain = req; + 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->dataout == NULL) { - uas->dataout = req; + 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; @@ -416,11 +532,11 @@ static void usb_uas_scsi_free_request(SCSIBus *bus, void *priv) UASRequest *req = priv; UASDevice *uas = req->uas; - if (req == uas->datain) { - uas->datain = NULL; + if (req == uas->datain2) { + uas->datain2 = NULL; } - if (req == uas->dataout) { - uas->dataout = NULL; + if (req == uas->dataout2) { + uas->dataout2 = NULL; } QTAILQ_REMOVE(&uas->requests, req, next); g_free(req); @@ -504,29 +620,42 @@ static void usb_uas_handle_reset(USBDevice *dev) } } -static int usb_uas_handle_control(USBDevice *dev, USBPacket *p, +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 ret; + return; } fprintf(stderr, "%s: unhandled control request\n", __func__); - return USB_RET_STALL; + p->status = USB_RET_STALL; } static void usb_uas_cancel_io(USBDevice *dev, USBPacket *p) { UASDevice *uas = DO_UPCAST(UASDevice, dev, dev); UASRequest *req, *nreq; + int i; - if (uas->status == p) { - uas->status = NULL; + 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; @@ -554,9 +683,18 @@ static void usb_uas_command(UASDevice *uas, uas_ui *ui) 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), ui->command.cdb, req); +#if 1 + scsi_req_print(req->req); +#endif len = scsi_req_enqueue(req->req); if (len) { req->data_size = len; @@ -576,7 +714,6 @@ bad_target: */ usb_uas_queue_response(uas, req->tag, UAS_RC_INVALID_INFO_UNIT, 0); g_free(req); - return; } static void usb_uas_task(UASDevice *uas, uas_ui *ui) @@ -640,16 +777,15 @@ bad_target: incorrect_lun: usb_uas_queue_response(uas, tag, UAS_RC_INCORRECT_LUN, 0); - return; } -static int usb_uas_handle_data(USBDevice *dev, USBPacket *p) +static void usb_uas_handle_data(USBDevice *dev, USBPacket *p) { UASDevice *uas = DO_UPCAST(UASDevice, dev, dev); uas_ui ui; UASStatus *st; UASRequest *req; - int length, ret = 0; + int length; switch (p->ep->nr) { case UAS_PIPE_ID_COMMAND: @@ -658,59 +794,80 @@ static int usb_uas_handle_data(USBDevice *dev, USBPacket *p) switch (ui.hdr.id) { case UAS_UI_COMMAND: usb_uas_command(uas, &ui); - ret = length; break; case UAS_UI_TASK_MGMT: usb_uas_task(uas, &ui); - ret = length; break; default: fprintf(stderr, "%s: unknown command ui: id 0x%x\n", __func__, ui.hdr.id); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } break; case UAS_PIPE_ID_STATUS: - st = QTAILQ_FIRST(&uas->results); - if (st == NULL) { - assert(uas->status == NULL); - uas->status = p; - ret = USB_RET_ASYNC; - break; + 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); - ret = st->length; QTAILQ_REMOVE(&uas->results, st, next); g_free(st); break; case UAS_PIPE_ID_DATA_IN: case UAS_PIPE_ID_DATA_OUT: - req = (p->ep->nr == UAS_PIPE_ID_DATA_IN) ? uas->datain : uas->dataout; + 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) { - fprintf(stderr, "%s: no inflight request\n", __func__); - ret = USB_RET_STALL; - break; + if (p->stream) { + assert(uas->data3[p->stream] == NULL); + uas->data3[p->stream] = p; + p->status = USB_RET_ASYNC; + break; + } else { + fprintf(stderr, "%s: no inflight request\n", __func__); + p->status = USB_RET_STALL; + break; + } } scsi_req_ref(req->req); req->data = p; usb_uas_copy_data(req); - if (p->result == p->iov.size || req->complete) { + if (p->actual_length == p->iov.size || req->complete) { req->data = NULL; - ret = p->result; } else { req->data_async = true; - ret = USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } scsi_req_unref(req->req); usb_uas_start_next_transfer(uas); break; default: fprintf(stderr, "%s: invalid endpoint %d\n", __func__, p->ep->nr); - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } static void usb_uas_handle_destroy(USBDevice *dev) @@ -731,7 +888,7 @@ static int usb_uas_init(USBDevice *dev) QTAILQ_INIT(&uas->requests); uas->status_bh = qemu_bh_new(usb_uas_send_status_bh, uas); - scsi_bus_new(&uas->bus, &uas->dev.qdev, &usb_uas_scsi_info); + scsi_bus_new(&uas->bus, &uas->dev.qdev, &usb_uas_scsi_info, NULL); return 0; } @@ -763,7 +920,7 @@ static void usb_uas_class_initfn(ObjectClass *klass, void *data) dc->vmsd = &vmstate_usb_uas; } -static TypeInfo uas_info = { +static const TypeInfo uas_info = { .name = "usb-uas", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(UASDevice), diff --git a/hw/usb/dev-wacom.c b/hw/usb/dev-wacom.c index ed9a5ee358..3be5cdefda 100644 --- a/hw/usb/dev-wacom.c +++ b/hw/usb/dev-wacom.c @@ -26,7 +26,7 @@ * THE SOFTWARE. */ #include "hw/hw.h" -#include "console.h" +#include "ui/console.h" #include "hw/usb.h" #include "hw/usb/desc.h" @@ -43,6 +43,7 @@ typedef struct USBWacomState { USBDevice dev; + USBEndpoint *intr; QEMUPutMouseEntry *eh_entry; int dx, dy, dz, buttons_state; int x, y; @@ -137,6 +138,7 @@ static void usb_mouse_event(void *opaque, s->dz += dz1; s->buttons_state = buttons_state; s->changed = 1; + usb_wakeup(s->intr, 0); } static void usb_wacom_event(void *opaque, @@ -150,6 +152,7 @@ static void usb_wacom_event(void *opaque, 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) @@ -250,7 +253,7 @@ static void usb_wacom_handle_reset(USBDevice *dev) s->mode = WACOM_MODE_HID; } -static int usb_wacom_handle_control(USBDevice *dev, USBPacket *p, +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; @@ -258,10 +261,9 @@ static int usb_wacom_handle_control(USBDevice *dev, USBPacket *p, ret = usb_desc_handle_control(dev, p, request, value, index, length, data); if (ret >= 0) { - return ret; + return; } - ret = 0; switch (request) { case WACOM_SET_REPORT: if (s->mouse_grabbed) { @@ -269,61 +271,58 @@ static int usb_wacom_handle_control(USBDevice *dev, USBPacket *p, s->mouse_grabbed = 0; } s->mode = data[0]; - ret = 0; break; case WACOM_GET_REPORT: data[0] = 0; data[1] = s->mode; - ret = 2; + p->actual_length = 2; break; /* USB HID requests */ case HID_GET_REPORT: if (s->mode == WACOM_MODE_HID) - ret = usb_mouse_poll(s, data, length); + p->actual_length = usb_mouse_poll(s, data, length); else if (s->mode == WACOM_MODE_WACOM) - ret = usb_wacom_poll(s, data, length); + p->actual_length = usb_wacom_poll(s, data, length); break; case HID_GET_IDLE: - ret = 1; data[0] = s->idle; + p->actual_length = 1; break; case HID_SET_IDLE: s->idle = (uint8_t) (value >> 8); - ret = 0; break; default: - ret = USB_RET_STALL; + p->status = USB_RET_STALL; break; } - return ret; } -static int usb_wacom_handle_data(USBDevice *dev, USBPacket *p) +static void usb_wacom_handle_data(USBDevice *dev, USBPacket *p) { USBWacomState *s = (USBWacomState *) dev; uint8_t buf[p->iov.size]; - int ret = 0; + int len = 0; switch (p->pid) { case USB_TOKEN_IN: if (p->ep->nr == 1) { - if (!(s->changed || s->idle)) - return USB_RET_NAK; + if (!(s->changed || s->idle)) { + p->status = USB_RET_NAK; + return; + } s->changed = 0; if (s->mode == WACOM_MODE_HID) - ret = usb_mouse_poll(s, buf, p->iov.size); + len = usb_mouse_poll(s, buf, p->iov.size); else if (s->mode == WACOM_MODE_WACOM) - ret = usb_wacom_poll(s, buf, p->iov.size); - usb_packet_copy(p, buf, ret); + len = usb_wacom_poll(s, buf, p->iov.size); + usb_packet_copy(p, buf, len); break; } /* Fall through. */ case USB_TOKEN_OUT: default: - ret = USB_RET_STALL; - break; + p->status = USB_RET_STALL; } - return ret; } static void usb_wacom_handle_destroy(USBDevice *dev) @@ -341,6 +340,7 @@ static int usb_wacom_initfn(USBDevice *dev) USBWacomState *s = DO_UPCAST(USBWacomState, dev, dev); usb_desc_create_serial(dev); usb_desc_init(dev); + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); s->changed = 1; return 0; } @@ -366,7 +366,7 @@ static void usb_wacom_class_init(ObjectClass *klass, void *data) dc->vmsd = &vmstate_usb_wacom; } -static TypeInfo wacom_info = { +static const TypeInfo wacom_info = { .name = "usb-wacom-tablet", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBWacomState), diff --git a/hw/usb/hcd-ehci-pci.c b/hw/usb/hcd-ehci-pci.c new file mode 100644 index 0000000000..0eb78269f7 --- /dev/null +++ b/hw/usb/hcd-ehci-pci.c @@ -0,0 +1,228 @@ +/* + * 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; +} EHCIPCIInfo; + +static int usb_ehci_pci_initfn(PCIDevice *dev) +{ + 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->caps[0x09] = 0x68; /* EECP */ + + s->irq = dev->irq[3]; + s->dma = pci_dma_context(dev); + + s->capsbase = 0x00; + s->opregbase = 0x20; + + usb_ehci_initfn(s, DEVICE(dev)); + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem); + + return 0; +} + +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.dma = busmaster ? pci_dma_context(dev) : NULL; +} + +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->init = usb_ehci_pci_initfn; + k->class_id = PCI_CLASS_SERIAL_USB; + k->config_write = usb_ehci_pci_write_config; + k->no_hotplug = 1; + dc->vmsd = &vmstate_ehci_pci; + dc->props = ehci_pci_properties; +} + +static const TypeInfo ehci_pci_type_info = { + .name = TYPE_PCI_EHCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(EHCIPCIState), + .abstract = true, + .class_init = ehci_class_init, +}; + +static void ehci_data_class_init(ObjectClass *klass, void *data) +{ + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + EHCIPCIInfo *i = data; + + k->vendor_id = i->vendor_id; + k->device_id = i->device_id; + k->revision = i->revision; +} + +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, + },{ + .name = "ich9-usb-ehci2", /* 00:1a.7 */ + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI2, + .revision = 0x03, + } +}; + +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 0000000000..b68a66a63b --- /dev/null +++ b/hw/usb/hcd-ehci-sysbus.c @@ -0,0 +1,105 @@ +/* + * 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 int usb_ehci_sysbus_initfn(SysBusDevice *dev) +{ + EHCISysBusState *i = SYS_BUS_EHCI(dev); + SysBusEHCIClass *sec = SYS_BUS_EHCI_GET_CLASS(dev); + EHCIState *s = &i->ehci; + + s->capsbase = sec->capsbase; + s->opregbase = sec->opregbase; + s->dma = &dma_context_memory; + + usb_ehci_initfn(s, DEVICE(dev)); + sysbus_init_irq(dev, &s->irq); + sysbus_init_mmio(dev, &s->mem); + return 0; +} + +static void ehci_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = usb_ehci_sysbus_initfn; + dc->vmsd = &vmstate_ehci_sysbus; + dc->props = ehci_sysbus_properties; +} + +static const TypeInfo ehci_type_info = { + .name = TYPE_SYS_BUS_EHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(EHCISysBusState), + .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); + + 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); + + sec->capsbase = 0x0; + sec->opregbase = 0x10; +} + +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_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_init(ehci_sysbus_register_types) diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index 017342b56a..0d3799d443 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c @@ -2,6 +2,11 @@ * 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, @@ -22,40 +27,18 @@ * along with this program; if not, see <http://www.gnu.org/licenses/>. */ -#include "hw/hw.h" -#include "qemu-timer.h" -#include "hw/usb.h" -#include "hw/pci.h" -#include "monitor.h" -#include "trace.h" -#include "dma.h" - -#define EHCI_DEBUG 0 - -#if EHCI_DEBUG -#define DPRINTF printf -#else -#define DPRINTF(...) -#endif - -/* internal processing - reset HC to try and recover */ -#define USB_RET_PROCERR (-99) - -#define MMIO_SIZE 0x1000 +#include "hw/usb/hcd-ehci.h" /* Capability Registers Base Address - section 2.2 */ -#define CAPREGBASE 0x0000 -#define CAPLENGTH CAPREGBASE + 0x0000 // 1-byte, 0x0001 reserved -#define HCIVERSION CAPREGBASE + 0x0002 // 2-bytes, i/f version # -#define HCSPARAMS CAPREGBASE + 0x0004 // 4-bytes, structural params -#define HCCPARAMS CAPREGBASE + 0x0008 // 4-bytes, capability params +#define CAPLENGTH 0x0000 /* 1-byte, 0x0001 reserved */ +#define HCIVERSION 0x0002 /* 2-bytes, i/f version # */ +#define HCSPARAMS 0x0004 /* 4-bytes, structural params */ +#define HCCPARAMS 0x0008 /* 4-bytes, capability params */ #define EECP HCCPARAMS + 1 -#define HCSPPORTROUTE1 CAPREGBASE + 0x000c -#define HCSPPORTROUTE2 CAPREGBASE + 0x0010 +#define HCSPPORTROUTE1 0x000c +#define HCSPPORTROUTE2 0x0010 -#define OPREGBASE 0x0020 // Operational Registers Base Address - -#define USBCMD OPREGBASE + 0x0000 +#define USBCMD 0x0000 #define USBCMD_RUNSTOP (1 << 0) // run / Stop #define USBCMD_HCRESET (1 << 1) // HC Reset #define USBCMD_FLS (3 << 2) // Frame List Size @@ -69,7 +52,7 @@ #define USBCMD_ITC (0x7f << 16) // Int Threshold Control #define USBCMD_ITC_SH 16 // Int Threshold Control Shift -#define USBSTS OPREGBASE + 0x0004 +#define USBSTS 0x0004 #define USBSTS_RO_MASK 0x0000003f #define USBSTS_INT (1 << 0) // USB Interrupt #define USBSTS_ERRINT (1 << 1) // Error Interrupt @@ -86,20 +69,17 @@ * Interrupt enable bits correspond to the interrupt active bits in USBSTS * so no need to redefine here. */ -#define USBINTR OPREGBASE + 0x0008 +#define USBINTR 0x0008 #define USBINTR_MASK 0x0000003f -#define FRINDEX OPREGBASE + 0x000c -#define CTRLDSSEGMENT OPREGBASE + 0x0010 -#define PERIODICLISTBASE OPREGBASE + 0x0014 -#define ASYNCLISTADDR OPREGBASE + 0x0018 +#define FRINDEX 0x000c +#define CTRLDSSEGMENT 0x0010 +#define PERIODICLISTBASE 0x0014 +#define ASYNCLISTADDR 0x0018 #define ASYNCLISTADDR_MASK 0xffffffe0 -#define CONFIGFLAG OPREGBASE + 0x0040 +#define CONFIGFLAG 0x0040 -#define PORTSC (OPREGBASE + 0x0044) -#define PORTSC_BEGIN PORTSC -#define PORTSC_END (PORTSC + 4 * NB_PORTS) /* * Bits that are reserved or are read-only are masked out of values * written to us by software @@ -129,11 +109,13 @@ #define FRAME_TIMER_FREQ 1000 #define FRAME_TIMER_NS (1000000000 / FRAME_TIMER_FREQ) +#define UFRAME_TIMER_NS (FRAME_TIMER_NS / 8) #define NB_MAXINTRATE 8 // Max rate at which controller issues ints -#define NB_PORTS 6 // Number of downstream ports #define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction #define MAX_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 */ @@ -167,274 +149,6 @@ typedef enum { #define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor #define NLPTR_TYPE_FSTN 3 // frame span traversal node - -/* EHCI spec version 1.0 Section 3.3 - */ -typedef struct EHCIitd { - uint32_t next; - - uint32_t transact[8]; -#define ITD_XACT_ACTIVE (1 << 31) -#define ITD_XACT_DBERROR (1 << 30) -#define ITD_XACT_BABBLE (1 << 29) -#define ITD_XACT_XACTERR (1 << 28) -#define ITD_XACT_LENGTH_MASK 0x0fff0000 -#define ITD_XACT_LENGTH_SH 16 -#define ITD_XACT_IOC (1 << 15) -#define ITD_XACT_PGSEL_MASK 0x00007000 -#define ITD_XACT_PGSEL_SH 12 -#define ITD_XACT_OFFSET_MASK 0x00000fff - - uint32_t bufptr[7]; -#define ITD_BUFPTR_MASK 0xfffff000 -#define ITD_BUFPTR_SH 12 -#define ITD_BUFPTR_EP_MASK 0x00000f00 -#define ITD_BUFPTR_EP_SH 8 -#define ITD_BUFPTR_DEVADDR_MASK 0x0000007f -#define ITD_BUFPTR_DEVADDR_SH 0 -#define ITD_BUFPTR_DIRECTION (1 << 11) -#define ITD_BUFPTR_MAXPKT_MASK 0x000007ff -#define ITD_BUFPTR_MAXPKT_SH 0 -#define ITD_BUFPTR_MULT_MASK 0x00000003 -#define ITD_BUFPTR_MULT_SH 0 -} EHCIitd; - -/* EHCI spec version 1.0 Section 3.4 - */ -typedef struct EHCIsitd { - uint32_t next; // Standard next link pointer - uint32_t epchar; -#define SITD_EPCHAR_IO (1 << 31) -#define SITD_EPCHAR_PORTNUM_MASK 0x7f000000 -#define SITD_EPCHAR_PORTNUM_SH 24 -#define SITD_EPCHAR_HUBADD_MASK 0x007f0000 -#define SITD_EPCHAR_HUBADDR_SH 16 -#define SITD_EPCHAR_EPNUM_MASK 0x00000f00 -#define SITD_EPCHAR_EPNUM_SH 8 -#define SITD_EPCHAR_DEVADDR_MASK 0x0000007f - - uint32_t uframe; -#define SITD_UFRAME_CMASK_MASK 0x0000ff00 -#define SITD_UFRAME_CMASK_SH 8 -#define SITD_UFRAME_SMASK_MASK 0x000000ff - - uint32_t results; -#define SITD_RESULTS_IOC (1 << 31) -#define SITD_RESULTS_PGSEL (1 << 30) -#define SITD_RESULTS_TBYTES_MASK 0x03ff0000 -#define SITD_RESULTS_TYBYTES_SH 16 -#define SITD_RESULTS_CPROGMASK_MASK 0x0000ff00 -#define SITD_RESULTS_CPROGMASK_SH 8 -#define SITD_RESULTS_ACTIVE (1 << 7) -#define SITD_RESULTS_ERR (1 << 6) -#define SITD_RESULTS_DBERR (1 << 5) -#define SITD_RESULTS_BABBLE (1 << 4) -#define SITD_RESULTS_XACTERR (1 << 3) -#define SITD_RESULTS_MISSEDUF (1 << 2) -#define SITD_RESULTS_SPLITXSTATE (1 << 1) - - uint32_t bufptr[2]; -#define SITD_BUFPTR_MASK 0xfffff000 -#define SITD_BUFPTR_CURROFF_MASK 0x00000fff -#define SITD_BUFPTR_TPOS_MASK 0x00000018 -#define SITD_BUFPTR_TPOS_SH 3 -#define SITD_BUFPTR_TCNT_MASK 0x00000007 - - uint32_t backptr; // Standard next link pointer -} EHCIsitd; - -/* EHCI spec version 1.0 Section 3.5 - */ -typedef struct EHCIqtd { - uint32_t next; // Standard next link pointer - uint32_t altnext; // Standard next link pointer - uint32_t token; -#define QTD_TOKEN_DTOGGLE (1 << 31) -#define QTD_TOKEN_TBYTES_MASK 0x7fff0000 -#define QTD_TOKEN_TBYTES_SH 16 -#define QTD_TOKEN_IOC (1 << 15) -#define QTD_TOKEN_CPAGE_MASK 0x00007000 -#define QTD_TOKEN_CPAGE_SH 12 -#define QTD_TOKEN_CERR_MASK 0x00000c00 -#define QTD_TOKEN_CERR_SH 10 -#define QTD_TOKEN_PID_MASK 0x00000300 -#define QTD_TOKEN_PID_SH 8 -#define QTD_TOKEN_ACTIVE (1 << 7) -#define QTD_TOKEN_HALT (1 << 6) -#define QTD_TOKEN_DBERR (1 << 5) -#define QTD_TOKEN_BABBLE (1 << 4) -#define QTD_TOKEN_XACTERR (1 << 3) -#define QTD_TOKEN_MISSEDUF (1 << 2) -#define QTD_TOKEN_SPLITXSTATE (1 << 1) -#define QTD_TOKEN_PING (1 << 0) - - uint32_t bufptr[5]; // Standard buffer pointer -#define QTD_BUFPTR_MASK 0xfffff000 -#define QTD_BUFPTR_SH 12 -} EHCIqtd; - -/* EHCI spec version 1.0 Section 3.6 - */ -typedef struct EHCIqh { - uint32_t next; // Standard next link pointer - - /* endpoint characteristics */ - uint32_t epchar; -#define QH_EPCHAR_RL_MASK 0xf0000000 -#define QH_EPCHAR_RL_SH 28 -#define QH_EPCHAR_C (1 << 27) -#define QH_EPCHAR_MPLEN_MASK 0x07FF0000 -#define QH_EPCHAR_MPLEN_SH 16 -#define QH_EPCHAR_H (1 << 15) -#define QH_EPCHAR_DTC (1 << 14) -#define QH_EPCHAR_EPS_MASK 0x00003000 -#define QH_EPCHAR_EPS_SH 12 -#define EHCI_QH_EPS_FULL 0 -#define EHCI_QH_EPS_LOW 1 -#define EHCI_QH_EPS_HIGH 2 -#define EHCI_QH_EPS_RESERVED 3 - -#define QH_EPCHAR_EP_MASK 0x00000f00 -#define QH_EPCHAR_EP_SH 8 -#define QH_EPCHAR_I (1 << 7) -#define QH_EPCHAR_DEVADDR_MASK 0x0000007f -#define QH_EPCHAR_DEVADDR_SH 0 - - /* endpoint capabilities */ - uint32_t epcap; -#define QH_EPCAP_MULT_MASK 0xc0000000 -#define QH_EPCAP_MULT_SH 30 -#define QH_EPCAP_PORTNUM_MASK 0x3f800000 -#define QH_EPCAP_PORTNUM_SH 23 -#define QH_EPCAP_HUBADDR_MASK 0x007f0000 -#define QH_EPCAP_HUBADDR_SH 16 -#define QH_EPCAP_CMASK_MASK 0x0000ff00 -#define QH_EPCAP_CMASK_SH 8 -#define QH_EPCAP_SMASK_MASK 0x000000ff -#define QH_EPCAP_SMASK_SH 0 - - uint32_t current_qtd; // Standard next link pointer - uint32_t next_qtd; // Standard next link pointer - uint32_t altnext_qtd; -#define QH_ALTNEXT_NAKCNT_MASK 0x0000001e -#define QH_ALTNEXT_NAKCNT_SH 1 - - uint32_t token; // Same as QTD token - uint32_t bufptr[5]; // Standard buffer pointer -#define BUFPTR_CPROGMASK_MASK 0x000000ff -#define BUFPTR_FRAMETAG_MASK 0x0000001f -#define BUFPTR_SBYTES_MASK 0x00000fe0 -#define BUFPTR_SBYTES_SH 5 -} EHCIqh; - -/* EHCI spec version 1.0 Section 3.7 - */ -typedef struct EHCIfstn { - uint32_t next; // Standard next link pointer - uint32_t backptr; // Standard next link pointer -} EHCIfstn; - -typedef struct EHCIPacket EHCIPacket; -typedef struct EHCIQueue EHCIQueue; -typedef struct EHCIState EHCIState; - -enum async_state { - EHCI_ASYNC_NONE = 0, - 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; - uint32_t tbytes; - enum async_state async; - int usb_status; -}; - -struct EHCIQueue { - EHCIState *ehci; - QTAILQ_ENTRY(EHCIQueue) next; - uint32_t seen; - uint64_t ts; - int async; - int revalidate; - - /* 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 */ - USBDevice *dev; - QTAILQ_HEAD(, EHCIPacket) packets; -}; - -typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead; - -struct EHCIState { - PCIDevice dev; - USBBus bus; - qemu_irq irq; - MemoryRegion mem; - int companion_count; - - /* properties */ - uint32_t maxframes; - - /* - * EHCI spec version 1.0 Section 2.3 - * Host Controller Operational Registers - */ - union { - uint8_t mmio[MMIO_SIZE]; - struct { - uint8_t cap[OPREGBASE]; - uint32_t usbcmd; - uint32_t usbsts; - uint32_t usbintr; - uint32_t frindex; - uint32_t ctrldssegment; - uint32_t periodiclistbase; - uint32_t asynclistaddr; - uint32_t notused[9]; - uint32_t configflag; - uint32_t portsc[NB_PORTS]; - }; - }; - - /* - * Internal states, shadow registers, etc - */ - 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; -}; - #define SET_LAST_RUN_CLOCK(s) \ (s)->last_run_ns = qemu_get_clock_ns(vm_clock); @@ -466,25 +180,21 @@ static const char *ehci_state_names[] = { }; static const char *ehci_mmio_names[] = { - [CAPLENGTH] = "CAPLENGTH", - [HCIVERSION] = "HCIVERSION", - [HCSPARAMS] = "HCSPARAMS", - [HCCPARAMS] = "HCCPARAMS", [USBCMD] = "USBCMD", [USBSTS] = "USBSTS", [USBINTR] = "USBINTR", [FRINDEX] = "FRINDEX", [PERIODICLISTBASE] = "P-LIST BASE", [ASYNCLISTADDR] = "A-LIST ADDR", - [PORTSC_BEGIN] = "PORTSC #0", - [PORTSC_BEGIN + 4] = "PORTSC #1", - [PORTSC_BEGIN + 8] = "PORTSC #2", - [PORTSC_BEGIN + 12] = "PORTSC #3", - [PORTSC_BEGIN + 16] = "PORTSC #4", - [PORTSC_BEGIN + 20] = "PORTSC #5", [CONFIGFLAG] = "CONFIGFLAG", }; +static 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) { @@ -499,7 +209,7 @@ static const char *state2str(uint32_t state) return nr2str(ehci_state_names, ARRAY_SIZE(ehci_state_names), state); } -static const char *addr2str(target_phys_addr_t addr) +static const char *addr2str(hwaddr addr) { return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr); } @@ -658,7 +368,7 @@ static int ehci_get_fetch_addr(EHCIState *s, int async) return async ? s->a_fetch_addr : s->p_fetch_addr; } -static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh) +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, @@ -676,7 +386,7 @@ static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh) (bool)(qh->epchar & QH_EPCHAR_I)); } -static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd) +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); @@ -693,7 +403,7 @@ static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd) (bool)(qtd->token & QTD_TOKEN_XACTERR)); } -static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd) +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), @@ -702,13 +412,19 @@ static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd) get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR)); } -static void ehci_trace_sitd(EHCIState *s, target_phys_addr_t addr, +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; @@ -724,6 +440,136 @@ 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->dma) { + 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->dma, 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->dma) { + 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->dma, 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) @@ -740,10 +586,26 @@ static EHCIPacket *ehci_alloc_packet(EHCIQueue *q) 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); @@ -766,27 +628,60 @@ static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async) return q; } -static void ehci_cancel_queue(EHCIQueue *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) { - return; + 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 void ehci_free_queue(EHCIQueue *q) +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"); - ehci_cancel_queue(q); + cancelled = ehci_cancel_queue(q); + if (warn && cancelled > 0) { + ehci_trace_guest_bug(q->ehci, warn); + } QTAILQ_REMOVE(head, q, next); g_free(q); } @@ -805,20 +700,10 @@ static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr, return NULL; } -static void ehci_queues_tag_unused_async(EHCIState *ehci) -{ - EHCIQueue *q; - - QTAILQ_FOREACH(q, &ehci->aqueues, next) { - if (!q->seen) { - q->revalidate = 1; - } - } -} - 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; @@ -831,7 +716,19 @@ static void ehci_queues_rip_unused(EHCIState *ehci, int async) if (ehci->last_run_ns < q->ts + maxage) { continue; } - ehci_free_queue(q); + 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); + } } } @@ -844,17 +741,18 @@ static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async) if (q->dev != dev) { continue; } - ehci_free_queue(q); + 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); + ehci_free_queue(q, warn); } } @@ -879,7 +777,6 @@ static void ehci_attach(USBPort *port) *portsc |= PORTSC_CSC; ehci_raise_irq(s, USBSTS_PCD); - ehci_commit_irq(s); } static void ehci_detach(USBPort *port) @@ -909,7 +806,6 @@ static void ehci_detach(USBPort *port) *portsc |= PORTSC_CSC; ehci_raise_irq(s, USBSTS_PCD); - ehci_commit_irq(s); } static void ehci_child_detach(USBPort *port, USBDevice *child) @@ -979,11 +875,25 @@ static int ehci_register_companion(USBBus *bus, USBPort *ports[], } s->companion_count++; - s->mmio[0x05] = (s->companion_count << 4) | portcount; + s->caps[0x05] = (s->companion_count << 4) | portcount; return 0; } +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; @@ -1024,7 +934,8 @@ static void ehci_reset(void *opaque) } } - memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE); + 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; @@ -1051,50 +962,43 @@ static void ehci_reset(void *opaque) qemu_bh_cancel(s->async_bh); } -static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr) +static uint64_t ehci_caps_read(void *ptr, hwaddr addr, + unsigned size) { EHCIState *s = ptr; - uint32_t val; - - val = s->mmio[addr]; - - return val; + return s->caps[addr]; } -static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr) +static uint64_t ehci_opreg_read(void *ptr, hwaddr addr, + unsigned size) { EHCIState *s = ptr; uint32_t val; - val = s->mmio[addr] | (s->mmio[addr+1] << 8); + 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 uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr) +static uint64_t ehci_port_read(void *ptr, hwaddr addr, + unsigned size) { EHCIState *s = ptr; uint32_t val; - val = s->mmio[addr] | (s->mmio[addr+1] << 8) | - (s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24); - - trace_usb_ehci_mmio_readl(addr, addr2str(addr), val); + val = s->portsc[addr >> 2]; + trace_usb_ehci_portsc_read(addr + PORTSC_BEGIN, addr >> 2, val); return val; } -static void ehci_mem_writeb(void *ptr, target_phys_addr_t addr, uint32_t val) -{ - fprintf(stderr, "EHCI doesn't handle byte writes to MMIO\n"); - exit(1); -} - -static void ehci_mem_writew(void *ptr, target_phys_addr_t addr, uint32_t val) -{ - fprintf(stderr, "EHCI doesn't handle 16-bit writes to MMIO\n"); - exit(1); -} - static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner) { USBDevice *dev = s->ports[port].dev; @@ -1123,11 +1027,17 @@ static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner) } } -static void handle_port_status_write(EHCIState *s, int port, uint32_t val) +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 + PORTSC_BEGIN, addr >> 2, val); + /* Clear rwc bits */ *portsc &= ~(val & PORTSC_RWC_MASK); /* The guest may clear, but not set the PED bit */ @@ -1159,39 +1069,20 @@ static void handle_port_status_write(EHCIState *s, int port, uint32_t val) *portsc &= ~PORTSC_RO_MASK; *portsc |= val; + trace_usb_ehci_portsc_change(addr + PORTSC_BEGIN, addr >> 2, *portsc, old); } -static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) +static void ehci_opreg_write(void *ptr, hwaddr addr, + uint64_t val, unsigned size) { EHCIState *s = ptr; - uint32_t *mmio = (uint32_t *)(&s->mmio[addr]); + uint32_t *mmio = s->opreg + (addr >> 2); uint32_t old = *mmio; int i; - trace_usb_ehci_mmio_writel(addr, addr2str(addr), val); - - /* Only aligned reads are allowed on OHCI */ - if (addr & 3) { - fprintf(stderr, "usb-ehci: Mis-aligned write to addr 0x" - TARGET_FMT_plx "\n", addr); - return; - } - - if (addr >= PORTSC && addr < PORTSC + 4 * NB_PORTS) { - handle_port_status_write(s, (addr-PORTSC)/4, val); - trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old); - return; - } - - if (addr < OPREGBASE) { - fprintf(stderr, "usb-ehci: write attempt to read-only register" - TARGET_FMT_plx "\n", addr); - return; - } - + trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val); - /* Do any register specific pre-write processing here. */ - switch(addr) { + switch (addr) { case USBCMD: if (val & USBCMD_HCRESET) { ehci_reset(s); @@ -1202,7 +1093,7 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) /* not supporting dynamic frame list size at the moment */ if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) { fprintf(stderr, "attempt to set frame list size -- value %d\n", - val & USBCMD_FLS); + (int)val & USBCMD_FLS); val &= ~USBCMD_FLS; } @@ -1213,6 +1104,7 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) */ s->async_stepdown = 0; qemu_bh_schedule(s->async_bh); + trace_usb_ehci_doorbell_ring(); } if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) != @@ -1223,7 +1115,7 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */ ehci_update_halt(s); s->async_stepdown = 0; - qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock)); + qemu_bh_schedule(s->async_bh); } break; @@ -1236,10 +1128,14 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) case USBINTR: val &= USBINTR_MASK; + if (ehci_enabled(s) && (USBSTS_FLR & val)) { + qemu_bh_schedule(s->async_bh); + } break; case FRINDEX: - val &= 0x00003ff8; /* frindex is 14bits and always a multiple of 8 */ + val &= 0x00003fff; /* frindex is 14bits */ + s->usbsts_frindex = val; break; case CONFIGFLAG: @@ -1268,38 +1164,8 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) } *mmio = val; - trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old); -} - - -// TODO : Put in common header file, duplication from usb-ohci.c - -/* Get an array of dwords from main memory */ -static inline int get_dwords(EHCIState *ehci, uint32_t addr, - uint32_t *buf, int num) -{ - int i; - - for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { - pci_dma_read(&ehci->dev, addr, buf, sizeof(*buf)); - *buf = le32_to_cpu(*buf); - } - - return 1; -} - -/* Put an array of dwords in to main memory */ -static inline int put_dwords(EHCIState *ehci, uint32_t addr, - uint32_t *buf, int num) -{ - int i; - - for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { - uint32_t tmp = cpu_to_le32(*buf); - pci_dma_write(&ehci->dev, addr, &tmp, sizeof(tmp)); - } - - return 1; + trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr), + *mmio, old); } /* @@ -1379,12 +1245,12 @@ static int ehci_init_transfer(EHCIPacket *p) 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; - pci_dma_sglist_init(&p->sgl, &p->queue->ehci->dev, 5); + qemu_sglist_init(&p->sgl, 5, p->queue->ehci->dma); while (bytes > 0) { if (cpage > 4) { fprintf(stderr, "cpage out of range (%d)\n", cpage); - return USB_RET_PROCERR; + return -1; } page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK; @@ -1402,16 +1268,16 @@ static int ehci_init_transfer(EHCIPacket *p) return 0; } -static void ehci_finish_transfer(EHCIQueue *q, int status) +static void ehci_finish_transfer(EHCIQueue *q, int len) { uint32_t cpage, offset; - if (status > 0) { + if (len > 0) { /* update cpage & offset */ cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE); offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK; - offset += status; + offset += len; cpage += offset >> QTD_BUFPTR_SH; offset &= ~QTD_BUFPTR_MASK; @@ -1434,139 +1300,152 @@ static void ehci_async_complete_packet(USBPort *port, USBPacket *packet) } p = container_of(packet, EHCIPacket, packet); - trace_usb_ehci_packet_action(p->queue, p, "wakeup"); 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; - p->usb_status = packet->result; - if (p->queue->async) { - qemu_bh_schedule(p->queue->ehci->async_bh); + 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_INFLIGHT); - p->async = EHCI_ASYNC_NONE; + assert(p->async == EHCI_ASYNC_INITIALIZED || + p->async == EHCI_ASYNC_FINISHED); - DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n", - q->qhaddr, q->qh.next, q->qtdaddr, q->usb_status); + 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); - if (p->usb_status < 0) { - switch (p->usb_status) { - case USB_RET_IOERROR: - case USB_RET_NODEV: - q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR); - set_field(&q->qh.token, 0, QTD_TOKEN_CERR); - ehci_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->usb_status); - assert(0); - break; - } - } else if ((p->usb_status > p->tbytes) && (p->pid == USB_TOKEN_IN)) { - p->usb_status = USB_RET_BABBLE; + 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); - } else { - // TODO check 4.12 for splits + break; + default: + /* should not be triggerable */ + fprintf(stderr, "USB invalid response %d\n", p->packet.status); + assert(0); + break; + } - if (p->tbytes && p->pid == USB_TOKEN_IN) { - p->tbytes -= p->usb_status; - } else { - p->tbytes = 0; + /* 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; + } } - - DPRINTF("updating tbytes to %d\n", p->tbytes); - set_field(&q->qh.token, p->tbytes, QTD_TOKEN_TBYTES); + } else { + tbytes = 0; } - ehci_finish_transfer(q, p->usb_status); + 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 - +/* 4.10.3 returns "again" */ static int ehci_execute(EHCIPacket *p, const char *action) { USBEndpoint *ep; - int ret; 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 USB_RET_PROCERR; - } - - p->tbytes = (p->qtd.token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH; - if (p->tbytes > BUFF_SIZE) { - fprintf(stderr, "Request for more bytes than allowed\n"); - return USB_RET_PROCERR; + return -1; } - p->pid = (p->qtd.token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH; - switch (p->pid) { - case 0: - p->pid = USB_TOKEN_OUT; - break; - case 1: - p->pid = USB_TOKEN_IN; - break; - case 2: - p->pid = USB_TOKEN_SETUP; - break; - default: - fprintf(stderr, "bad token\n"); - break; + 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_init_transfer(p) != 0) { - return USB_RET_PROCERR; + 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); - usb_packet_setup(&p->packet, p->pid, ep, p->qtdaddr); - usb_packet_map(&p->packet, &p->sgl); + 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); - ret = usb_handle_packet(p->queue->dev, &p->packet); - DPRINTF("submit: qh %x next %x qtd %x pid %x len %zd " - "(total %d) endp %x ret %d\n", - q->qhaddr, q->qh.next, q->qtdaddr, q->pid, - q->packet.iov.size, q->tbytes, endp, ret); + 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 (ret > BUFF_SIZE) { + if (p->packet.actual_length > BUFF_SIZE) { fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n"); - return USB_RET_PROCERR; + return -1; } - return ret; + return 1; } /* 4.7.2 @@ -1578,10 +1457,11 @@ static int ehci_process_itd(EHCIState *ehci, { USBDevice *dev; USBEndpoint *ep; - int ret; uint32_t i, len, pid, dir, devaddr, endp; uint32_t pg, off, ptr1, ptr2, max, mult; + ehci->periodic_sched_active = PERIODIC_ACTIVE; + dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION); devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR); endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP); @@ -1601,10 +1481,10 @@ static int ehci_process_itd(EHCIState *ehci, } if (len > BUFF_SIZE) { - return USB_RET_PROCERR; + return -1; } - pci_dma_sglist_init(&ehci->isgl, &ehci->dev, 2); + qemu_sglist_init(&ehci->isgl, 2, ehci->dma); if (off + len > 4096) { /* transfer crosses page border */ uint32_t len2 = off + len - 4096; @@ -1620,48 +1500,48 @@ static int ehci_process_itd(EHCIState *ehci, 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, addr); + usb_packet_setup(&ehci->ipacket, pid, ep, 0, addr, false, + (itd->transact[i] & ITD_XACT_IOC) != 0); usb_packet_map(&ehci->ipacket, &ehci->isgl); - ret = usb_handle_packet(dev, &ehci->ipacket); - assert(ret != USB_RET_ASYNC); + usb_handle_packet(dev, &ehci->ipacket); usb_packet_unmap(&ehci->ipacket, &ehci->isgl); } else { DPRINTF("ISOCH: attempt to addess non-iso endpoint\n"); - ret = USB_RET_NAK; + ehci->ipacket.status = USB_RET_NAK; + ehci->ipacket.actual_length = 0; } qemu_sglist_destroy(&ehci->isgl); - if (ret < 0) { - switch (ret) { - default: - fprintf(stderr, "Unexpected iso usb result: %d\n", ret); - /* Fall through */ - case USB_RET_IOERROR: - case USB_RET_NODEV: - /* 3.3.2: XACTERR is only allowed on IN transactions */ - if (dir) { - itd->transact[i] |= ITD_XACT_XACTERR; - ehci_raise_irq(ehci, USBSTS_ERRINT); - } - break; - case USB_RET_BABBLE: - itd->transact[i] |= ITD_XACT_BABBLE; + 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_NAK: - /* no data for us, so do a zero-length transfer */ - ret = 0; - break; } + 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 (ret >= 0) { - if (!dir) { - /* OUT */ - set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH); - } else { - /* IN */ - set_field(&itd->transact[i], ret, ITD_XACT_LENGTH); - } + if (!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); @@ -1692,8 +1572,10 @@ static int ehci_state_waitlisthead(EHCIState *ehci, int async) /* Find the head of the list (4.9.1.1) */ for(i = 0; i < MAX_QH; i++) { - get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh, - sizeof(EHCIqh) >> 2); + 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) { @@ -1770,8 +1652,7 @@ out: static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) { - EHCIPacket *p; - uint32_t entry, devaddr; + uint32_t entry; EHCIQueue *q; EHCIqh qh; @@ -1780,7 +1661,6 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) if (NULL == q) { q = ehci_alloc_queue(ehci, entry, async); } - p = QTAILQ_FIRST(&q->packets); q->seen++; if (q->seen > 1) { @@ -1790,37 +1670,32 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) goto out; } - get_dwords(ehci, NLPTR_GET(q->qhaddr), - (uint32_t *) &qh, sizeof(EHCIqh) >> 2); - if (q->revalidate && (q->qh.epchar != qh.epchar || - q->qh.epcap != qh.epcap || - q->qh.current_qtd != qh.current_qtd)) { - ehci_free_queue(q); - q = ehci_alloc_queue(ehci, entry, async); - q->seen++; - p = NULL; + if (get_dwords(ehci, NLPTR_GET(q->qhaddr), + (uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) { + q = NULL; + goto out; } - q->qh = qh; - q->revalidate = 0; - ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh); + ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh); - devaddr = get_field(q->qh.epchar, QH_EPCHAR_DEVADDR); - if (q->dev != NULL && q->dev->addr != devaddr) { - if (!QTAILQ_EMPTY(&q->packets)) { - /* should not happen (guest bug) */ - ehci_cancel_queue(q); + /* + * 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->dev = NULL; } - if (q->dev == NULL) { - q->dev = ehci_find_device(q->ehci, devaddr); + 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 (p && p->async == EHCI_ASYNC_FINISHED) { - /* I/O finished -- continue processing queue */ - trace_usb_ehci_packet_action(p->queue, p, "complete"); - ehci_set_state(ehci, async, EST_EXECUTING); - goto out; + 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)) { @@ -1873,8 +1748,10 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async) assert(!async); entry = ehci_get_fetch_addr(ehci, async); - get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd, - sizeof(EHCIitd) >> 2); + 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) { @@ -1897,8 +1774,10 @@ static int ehci_state_fetchsitd(EHCIState *ehci, int async) assert(!async); entry = ehci_get_fetch_addr(ehci, async); - get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd, - sizeof(EHCIsitd) >> 2); + 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)) { @@ -1957,20 +1836,21 @@ static int ehci_state_fetchqtd(EHCIQueue *q) { EHCIqtd qtd; EHCIPacket *p; - int again = 0; + int again = 1; - get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd, - sizeof(EHCIqtd) >> 2); + 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 (p->qtdaddr != q->qtdaddr || - (!NLPTR_TBIT(p->qtd.next) && (p->qtd.next != qtd.next)) || - (!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd.altnext)) || - p->qtd.bufptr[0] != qtd.bufptr[0]) { - /* guest bug: guest updated active QH or qTD underneath us */ + 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; @@ -1979,35 +1859,30 @@ static int ehci_state_fetchqtd(EHCIQueue *q) } if (!(qtd.token & QTD_TOKEN_ACTIVE)) { - if (p != NULL) { - /* transfer canceled by guest (clear active) */ - ehci_cancel_queue(q); - p = NULL; - } ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); - again = 1; } else if (p != NULL) { switch (p->async) { case EHCI_ASYNC_NONE: - /* Previously nacked packet (likely interrupt ep) */ - ehci_set_state(q->ehci, q->async, EST_EXECUTE); - break; + 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: - /* Unfinyshed async handled packet, go horizontal */ + /* 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: - /* Should never happen, as this case is caught by fetchqh */ + /* Complete executing of the packet */ ehci_set_state(q->ehci, q->async, EST_EXECUTING); break; } - again = 1; } else { p = ehci_alloc_packet(q); p->qtdaddr = q->qtdaddr; p->qtd = qtd; ehci_set_state(q->ehci, q->async, EST_EXECUTE); - again = 1; } return again; @@ -2028,33 +1903,52 @@ static int ehci_state_horizqh(EHCIQueue *q) return again; } -static void ehci_fill_queue(EHCIPacket *p) +/* 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.altnext) == 0) { - break; - } if (NLPTR_TBIT(qtd.next) != 0) { break; } qtdaddr = qtd.next; - get_dwords(q->ehci, NLPTR_GET(qtdaddr), - (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2); + /* + * 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; - p->usb_status = ehci_execute(p, "queue"); - assert(p->usb_status == USB_RET_ASYNC); + 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) @@ -2071,33 +1965,32 @@ static int ehci_state_execute(EHCIQueue *q) // TODO verify enough time remains in the uframe as in 4.4.1.1 // TODO write back ptr to async list when done or out of time - // TODO Windows does not seem to ever set the MULT field - if (!q->async) { - int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT); - if (!transactCtr) { - ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); - again = 1; - goto out; - } + /* 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); } - p->usb_status = ehci_execute(p, "process"); - if (p->usb_status == USB_RET_PROCERR) { - again = -1; + again = ehci_execute(p, "process"); + if (again == -1) { goto out; } - if (p->usb_status == USB_RET_ASYNC) { + 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); - again = 1; - ehci_fill_queue(p); + if (q->async) { + again = ehci_fill_queue(p); + } else { + again = 1; + } goto out; } @@ -2117,17 +2010,13 @@ static int ehci_state_executing(EHCIQueue *q) ehci_execute_complete(q); - // 4.10.3 - if (!q->async) { - int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT); - transactCtr--; - set_field(&q->qh.epcap, transactCtr, QH_EPCAP_MULT); - // 4.10.3, bottom of page 82, should exit this state when transaction - // counter decrements to 0 + /* 4.10.3 */ + if (!q->async && q->transact_ctr > 0) { + q->transact_ctr--; } /* 4.10.5 */ - if (p->usb_status == USB_RET_NAK) { + 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); @@ -2163,19 +2052,6 @@ static int ehci_state_writeback(EHCIQueue *q) * bit is clear. */ if (q->qh.token & QTD_TOKEN_HALT) { - /* - * We should not do any further processing on a halted queue! - * This is esp. important for bulk endpoints with pipelining enabled - * (redirection to a real USB device), where we must cancel all the - * transfers after this one so that: - * 1) If they've completed already, they are not processed further - * causing more stalls, originating from the same failed transfer - * 2) If still in flight, they are cancelled before the guest does - * a clear stall, otherwise the guest and device can loose sync! - */ - while ((p = QTAILQ_FIRST(&q->packets)) != NULL) { - ehci_free_packet(p); - } ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); again = 1; } else { @@ -2223,18 +2099,22 @@ static void ehci_advance_state(EHCIState *ehci, int 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; @@ -2252,6 +2132,9 @@ static void ehci_advance_state(EHCIState *ehci, int async) case EST_WRITEBACK: assert(q != NULL); again = ehci_state_writeback(q); + if (!async) { + ehci->periodic_sched_active = PERIODIC_ACTIVE; + } break; default: @@ -2310,8 +2193,8 @@ static void ehci_advance_async_state(EHCIState *ehci) */ if (ehci->usbcmd & USBCMD_IAAD) { /* Remove all unseen qhs from the async qhs queue */ - ehci_queues_tag_unused_async(ehci); - DPRINTF("ASYNC: doorbell request acknowledged\n"); + ehci_queues_rip_unseen(ehci, async); + trace_usb_ehci_doorbell_ack(); ehci->usbcmd &= ~USBCMD_IAAD; ehci_raise_irq(ehci, USBSTS_IAA); } @@ -2355,8 +2238,9 @@ static void ehci_advance_periodic_state(EHCIState *ehci) } list |= ((ehci->frindex & 0x1ff8) >> 1); - pci_dma_read(&ehci->dev, list, &entry, sizeof entry); - entry = le32_to_cpu(entry); + if (get_dwords(ehci, list, &entry, 1) < 0) { + break; + } DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n", ehci->frindex / 8, list, entry); @@ -2374,16 +2258,16 @@ static void ehci_advance_periodic_state(EHCIState *ehci) } } -static void ehci_update_frindex(EHCIState *ehci, int frames) +static void ehci_update_frindex(EHCIState *ehci, int uframes) { int i; - if (!ehci_enabled(ehci)) { + if (!ehci_enabled(ehci) && ehci->pstate == EST_INACTIVE) { return; } - for (i = 0; i < frames; i++) { - ehci->frindex += 8; + for (i = 0; i < uframes; i++) { + ehci->frindex++; if (ehci->frindex == 0x00002000) { ehci_raise_irq(ehci, USBSTS_FLR); @@ -2392,7 +2276,7 @@ static void ehci_update_frindex(EHCIState *ehci, int frames) if (ehci->frindex == 0x00004000) { ehci_raise_irq(ehci, USBSTS_FLR); ehci->frindex = 0; - if (ehci->usbsts_frindex > 0x00004000) { + if (ehci->usbsts_frindex >= 0x00004000) { ehci->usbsts_frindex -= 0x00004000; } else { ehci->usbsts_frindex = 0; @@ -2407,36 +2291,57 @@ static void ehci_frame_timer(void *opaque) int need_timer = 0; int64_t expire_time, t_now; uint64_t ns_elapsed; - int frames, skipped_frames; + int uframes, skipped_uframes; int i; t_now = qemu_get_clock_ns(vm_clock); ns_elapsed = t_now - ehci->last_run_ns; - frames = ns_elapsed / FRAME_TIMER_NS; + uframes = ns_elapsed / UFRAME_TIMER_NS; if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) { need_timer++; - ehci->async_stepdown = 0; - if (frames > ehci->maxframes) { - skipped_frames = frames - ehci->maxframes; - ehci_update_frindex(ehci, skipped_frames); - ehci->last_run_ns += FRAME_TIMER_NS * skipped_frames; - frames -= skipped_frames; - DPRINTF("WARNING - EHCI skipped %d frames\n", skipped_frames); + 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 < frames; i++) { + 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); - ehci_advance_periodic_state(ehci); - ehci->last_run_ns += FRAME_TIMER_NS; + if ((ehci->frindex & 7) == 0) { + ehci_advance_periodic_state(ehci); + } + ehci->last_run_ns += UFRAME_TIMER_NS; } } else { - if (ehci->async_stepdown < ehci->maxframes / 2) { - ehci->async_stepdown++; - } - ehci_update_frindex(ehci, frames); - ehci->last_run_ns += FRAME_TIMER_NS * frames; + 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 @@ -2453,28 +2358,48 @@ static void ehci_frame_timer(void *opaque) ehci->async_stepdown = 0; } + if (ehci_enabled(ehci) && (ehci->usbintr & USBSTS_FLR)) { + need_timer++; + } + if (need_timer) { - expire_time = t_now + (get_ticks_per_sec() + /* 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); + } qemu_mod_timer(ehci->frame_timer, expire_time); } } -static void ehci_async_bh(void *opaque) -{ - EHCIState *ehci = opaque; - ehci_advance_async_state(ehci); -} +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_mem_ops = { - .old_mmio = { - .read = { ehci_mem_readb, ehci_mem_readw, ehci_mem_readl }, - .write = { ehci_mem_writeb, ehci_mem_writew, ehci_mem_writel }, - }, +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 int usb_ehci_initfn(PCIDevice *dev); +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, @@ -2486,8 +2411,20 @@ static USBPortOps ehci_port_ops = { static USBBusOps ehci_bus_ops = { .register_companion = ehci_register_companion, + .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; @@ -2508,13 +2445,39 @@ static int usb_ehci_post_load(void *opaque, int version_id) return 0; } -static const VMStateDescription vmstate_ehci = { - .name = "ehci", +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[]) { - VMSTATE_PCI_DEVICE(dev, EHCIState), /* mmio registers */ VMSTATE_UINT32(usbcmd, EHCIState), VMSTATE_UINT32(usbsts, EHCIState), @@ -2545,105 +2508,24 @@ static const VMStateDescription vmstate_ehci = { } }; -static Property ehci_properties[] = { - DEFINE_PROP_UINT32("maxframes", EHCIState, maxframes, 128), - DEFINE_PROP_END_OF_LIST(), -}; - -static void ehci_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_ehci_initfn; - k->vendor_id = PCI_VENDOR_ID_INTEL; - k->device_id = PCI_DEVICE_ID_INTEL_82801D; /* ich4 */ - k->revision = 0x10; - k->class_id = PCI_CLASS_SERIAL_USB; - dc->vmsd = &vmstate_ehci; - dc->props = ehci_properties; -} - -static TypeInfo ehci_info = { - .name = "usb-ehci", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(EHCIState), - .class_init = ehci_class_init, -}; - -static void ich9_ehci_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_ehci_initfn; - k->vendor_id = PCI_VENDOR_ID_INTEL; - k->device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI1; - k->revision = 0x03; - k->class_id = PCI_CLASS_SERIAL_USB; - dc->vmsd = &vmstate_ehci; - dc->props = ehci_properties; -} - -static TypeInfo ich9_ehci_info = { - .name = "ich9-usb-ehci1", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(EHCIState), - .class_init = ich9_ehci_class_init, -}; - -static int usb_ehci_initfn(PCIDevice *dev) +void usb_ehci_initfn(EHCIState *s, DeviceState *dev) { - EHCIState *s = DO_UPCAST(EHCIState, dev, dev); - uint8_t *pci_conf = s->dev.config; int i; - pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20); - - /* capabilities pointer */ - pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x00); - //pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x50); - - pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); /* interrupt pin D */ - pci_set_byte(&pci_conf[PCI_MIN_GNT], 0); - pci_set_byte(&pci_conf[PCI_MAX_LAT], 0); - - // pci_conf[0x50] = 0x01; // power management caps - - pci_set_byte(&pci_conf[USB_SBRN], USB_RELEASE_2); // release number (2.1.4) - pci_set_byte(&pci_conf[0x61], 0x20); // frame length adjustment (2.1.5) - pci_set_word(&pci_conf[0x62], 0x00); // port wake up capability (2.1.6) - - pci_conf[0x64] = 0x00; - pci_conf[0x65] = 0x00; - pci_conf[0x66] = 0x00; - pci_conf[0x67] = 0x00; - pci_conf[0x68] = 0x01; - pci_conf[0x69] = 0x00; - pci_conf[0x6a] = 0x00; - pci_conf[0x6b] = 0x00; // USBLEGSUP - pci_conf[0x6c] = 0x00; - pci_conf[0x6d] = 0x00; - pci_conf[0x6e] = 0x00; - pci_conf[0x6f] = 0xc0; // USBLEFCTLSTS - - // 2.2 host controller interface version - s->mmio[0x00] = (uint8_t) OPREGBASE; - s->mmio[0x01] = 0x00; - s->mmio[0x02] = 0x00; - s->mmio[0x03] = 0x01; // HC version - s->mmio[0x04] = NB_PORTS; // Number of downstream ports - s->mmio[0x05] = 0x00; // No companion ports at present - s->mmio[0x06] = 0x00; - s->mmio[0x07] = 0x00; - s->mmio[0x08] = 0x80; // We can cache whole frame, not 64-bit capable - s->mmio[0x09] = 0x68; // EECP - s->mmio[0x0a] = 0x00; - s->mmio[0x0b] = 0x00; - - s->irq = s->dev.irq[3]; - - usb_bus_new(&s->bus, &ehci_bus_ops, &s->dev.qdev); + /* 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] = NB_PORTS; /* 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; + + usb_bus_new(&s->bus, &ehci_bus_ops, dev); for(i = 0; i < NB_PORTS; i++) { usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops, USB_SPEED_MASK_HIGH); @@ -2651,27 +2533,28 @@ static int usb_ehci_initfn(PCIDevice *dev) } s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s); - s->async_bh = qemu_bh_new(ehci_async_bh, s); + s->async_bh = qemu_bh_new(ehci_frame_timer, s); QTAILQ_INIT(&s->aqueues); QTAILQ_INIT(&s->pqueues); usb_packet_init(&s->ipacket); qemu_register_reset(ehci_reset, s); + qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s); - memory_region_init_io(&s->mem, &ehci_mem_ops, s, "ehci", MMIO_SIZE); - pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem); - - return 0; -} + memory_region_init(&s->mem, "ehci", MMIO_SIZE); + memory_region_init_io(&s->mem_caps, &ehci_mmio_caps_ops, s, + "capabilities", CAPA_SIZE); + memory_region_init_io(&s->mem_opreg, &ehci_mmio_opreg_ops, s, + "operational", PORTSC_BEGIN); + memory_region_init_io(&s->mem_ports, &ehci_mmio_port_ops, s, + "ports", PORTSC_END - PORTSC_BEGIN); -static void ehci_register_types(void) -{ - type_register_static(&ehci_info); - type_register_static(&ich9_ehci_info); + 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 + PORTSC_BEGIN, + &s->mem_ports); } -type_init(ehci_register_types) - /* * vim: expandtab ts=4 */ diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h new file mode 100644 index 0000000000..e95bb7ec46 --- /dev/null +++ b/hw/usb/hcd-ehci.h @@ -0,0 +1,366 @@ +/* + * 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 "monitor/monitor.h" +#include "trace.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 PORTSC 0x0044 +#define PORTSC_BEGIN PORTSC +#define PORTSC_END (PORTSC + 4 * NB_PORTS) + +#define NB_PORTS 6 /* 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; + qemu_irq irq; + MemoryRegion mem; + DMAContext *dma; + MemoryRegion mem_caps; + MemoryRegion mem_opreg; + MemoryRegion mem_ports; + int companion_count; + uint16_t capsbase; + uint16_t opregbase; + + /* 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[PORTSC_BEGIN/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; +}; + +extern const VMStateDescription vmstate_ehci; + +void usb_ehci_initfn(EHCIState *s, DeviceState *dev); + +#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 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; +} SysBusEHCIClass; + +#endif diff --git a/hw/usb/hcd-musb.c b/hw/usb/hcd-musb.c index 0bb5c7b19e..7968e17c34 100644 --- a/hw/usb/hcd-musb.c +++ b/hw/usb/hcd-musb.c @@ -21,7 +21,7 @@ * Only host-mode and non-DMA accesses are currently supported. */ #include "qemu-common.h" -#include "qemu-timer.h" +#include "qemu/timer.h" #include "hw/usb.h" #include "hw/irq.h" #include "hw/hw.h" @@ -607,7 +607,6 @@ static void musb_packet(MUSBState *s, MUSBEndPoint *ep, { USBDevice *dev; USBEndpoint *uep; - int ret; int idx = epnum && dir; int ttype; @@ -626,20 +625,25 @@ static void musb_packet(MUSBState *s, MUSBEndPoint *ep, /* A wild guess on the FADDR semantics... */ dev = usb_find_device(&s->port, ep->faddr[idx]); uep = usb_ep_get(dev, pid, ep->type[idx] & 0xf); - usb_packet_setup(&ep->packey[dir].p, pid, uep, - (dev->addr << 16) | (uep->nr << 8) | pid); + usb_packet_setup(&ep->packey[dir].p, pid, uep, 0, + (dev->addr << 16) | (uep->nr << 8) | pid, false, true); usb_packet_addbuf(&ep->packey[dir].p, ep->buf[idx], len); ep->packey[dir].ep = ep; ep->packey[dir].dir = dir; - ret = usb_handle_packet(dev, &ep->packey[dir].p); + usb_handle_packet(dev, &ep->packey[dir].p); - if (ret == USB_RET_ASYNC) { + if (ep->packey[dir].p.status == USB_RET_ASYNC) { + usb_device_flush_ep_queue(dev, uep); ep->status[dir] = len; return; } - ep->status[dir] = ret; + 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); } @@ -753,7 +757,6 @@ static void musb_rx_packet_complete(USBPacket *packey, void *opaque) if (ep->status[1] == USB_RET_STALL) { ep->status[1] = 0; - packey->result = 0; ep->csr[1] |= MGC_M_RXCSR_H_RXSTALL; if (!epnum) @@ -792,14 +795,12 @@ static void musb_rx_packet_complete(USBPacket *packey, void *opaque) /* TODO: check len for over/underruns of an OUT packet? */ /* TODO: perhaps make use of e->ext_size[1] here. */ - packey->result = ep->status[1]; - if (!(ep->csr[1] & (MGC_M_RXCSR_H_RXSTALL | MGC_M_RXCSR_DATAERROR))) { ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY; if (!epnum) ep->csr[0] |= MGC_M_CSR0_RXPKTRDY; - ep->rxcount = packey->result; /* XXX: MIN(packey->len, ep->maxp[1]); */ + ep->rxcount = ep->status[1]; /* XXX: MIN(packey->len, ep->maxp[1]); */ /* In DMA mode: assert DMA request for this EP */ } @@ -1236,7 +1237,7 @@ static void musb_ep_writeh(void *opaque, int ep, int addr, uint16_t value) } /* Generic control */ -static uint32_t musb_readb(void *opaque, target_phys_addr_t addr) +static uint32_t musb_readb(void *opaque, hwaddr addr) { MUSBState *s = (MUSBState *) opaque; int ep, i; @@ -1298,7 +1299,7 @@ static uint32_t musb_readb(void *opaque, target_phys_addr_t addr) }; } -static void musb_writeb(void *opaque, target_phys_addr_t addr, uint32_t value) +static void musb_writeb(void *opaque, hwaddr addr, uint32_t value) { MUSBState *s = (MUSBState *) opaque; int ep; @@ -1385,7 +1386,7 @@ static void musb_writeb(void *opaque, target_phys_addr_t addr, uint32_t value) }; } -static uint32_t musb_readh(void *opaque, target_phys_addr_t addr) +static uint32_t musb_readh(void *opaque, hwaddr addr) { MUSBState *s = (MUSBState *) opaque; int ep, i; @@ -1439,7 +1440,7 @@ static uint32_t musb_readh(void *opaque, target_phys_addr_t addr) }; } -static void musb_writeh(void *opaque, target_phys_addr_t addr, uint32_t value) +static void musb_writeh(void *opaque, hwaddr addr, uint32_t value) { MUSBState *s = (MUSBState *) opaque; int ep; @@ -1495,7 +1496,7 @@ static void musb_writeh(void *opaque, target_phys_addr_t addr, uint32_t value) }; } -static uint32_t musb_readw(void *opaque, target_phys_addr_t addr) +static uint32_t musb_readw(void *opaque, hwaddr addr) { MUSBState *s = (MUSBState *) opaque; int ep; @@ -1513,7 +1514,7 @@ static uint32_t musb_readw(void *opaque, target_phys_addr_t addr) }; } -static void musb_writew(void *opaque, target_phys_addr_t addr, uint32_t value) +static void musb_writew(void *opaque, hwaddr addr, uint32_t value) { MUSBState *s = (MUSBState *) opaque; int ep; diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c index c36184ae4f..51241cda78 100644 --- a/hw/usb/hcd-ohci.c +++ b/hw/usb/hcd-ohci.c @@ -27,9 +27,9 @@ */ #include "hw/hw.h" -#include "qemu-timer.h" +#include "qemu/timer.h" #include "hw/usb.h" -#include "hw/pci.h" +#include "hw/pci/pci.h" #include "hw/sysbus.h" #include "hw/qdev-dma.h" @@ -430,6 +430,23 @@ static USBDevice *ohci_find_device(OHCIState *ohci, uint8_t addr) 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) { @@ -478,6 +495,7 @@ static void ohci_reset(void *opaque) usb_cancel_packet(&ohci->usb_packet); ohci->async_td = 0; } + ohci_stop_endpoints(ohci); DPRINTF("usb-ohci: Reset %s\n", ohci->name); } @@ -807,18 +825,24 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, DMA_DIRECTION_TO_DEVICE); } - if (completion) { - ret = ohci->usb_packet.result; - } else { + 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, addr); + usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, false, int_req); usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, len); - ret = usb_handle_packet(dev, &ohci->usb_packet); - if (ret == USB_RET_ASYNC) { + 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; + } #ifdef DEBUG_ISOCH printf("so 0x%.8x eo 0x%.8x\nsa 0x%.8x ea 0x%.8x\ndir %s len %zu ret %d\n", @@ -994,7 +1018,6 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) } #endif if (completion) { - ret = ohci->usb_packet.result; ohci->async_td = 0; ohci->async_complete = 0; } else { @@ -1011,17 +1034,25 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) } 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, addr); + 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); - ret = usb_handle_packet(dev, &ohci->usb_packet); + usb_handle_packet(dev, &ohci->usb_packet); #ifdef DEBUG_PACKET - DPRINTF("ret=%d\n", ret); + DPRINTF("status=%d\n", ohci->usb_packet.status); #endif - if (ret == USB_RET_ASYNC) { + 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) { ohci_copy_td(ohci, &td, ohci->usb_buf, ret, @@ -1134,6 +1165,8 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head, int completion) 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; } @@ -1214,10 +1247,12 @@ static void ohci_frame_boundary(void *opaque) } /* Cancel all pending packets if either of the lists has been disabled. */ - if (ohci->async_td && - ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) { - usb_cancel_packet(&ohci->usb_packet); - ohci->async_td = 0; + 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); @@ -1470,12 +1505,10 @@ static void ohci_port_set_status(OHCIState *ohci, int portnum, uint32_t val) if (old_state != port->ctrl) ohci_set_interrupt(ohci, OHCI_INTR_RHSC); - - return; } static uint64_t ohci_mem_read(void *opaque, - target_phys_addr_t addr, + hwaddr addr, unsigned size) { OHCIState *ohci = opaque; @@ -1598,7 +1631,7 @@ static uint64_t ohci_mem_read(void *opaque, } static void ohci_mem_write(void *opaque, - target_phys_addr_t addr, + hwaddr addr, uint64_t val, unsigned size) { @@ -1703,6 +1736,7 @@ static void ohci_mem_write(void *opaque, /* PXA27x specific registers */ case 24: /* HcStatus */ ohci->hstatus &= ~(val & ohci->hmask); + break; case 25: /* HcHReset */ ohci->hreset = val & ~OHCI_HRESET_FSBIR; @@ -1848,7 +1882,7 @@ static int ohci_init_pxa(SysBusDevice *dev) /* Cannot fail as we pass NULL for masterbus */ usb_ohci_init(&s->ohci, &dev->qdev, s->num_ports, s->dma_offset, NULL, 0, - NULL); + &dma_context_memory); sysbus_init_irq(dev, &s->ohci.irq); sysbus_init_mmio(dev, &s->ohci.mem); @@ -1871,11 +1905,12 @@ static void ohci_pci_class_init(ObjectClass *klass, void *data) k->vendor_id = PCI_VENDOR_ID_APPLE; k->device_id = PCI_DEVICE_ID_APPLE_IPID_USB; k->class_id = PCI_CLASS_SERIAL_USB; + k->no_hotplug = 1; dc->desc = "Apple USB Controller"; dc->props = ohci_pci_properties; } -static TypeInfo ohci_pci_info = { +static const TypeInfo ohci_pci_info = { .name = "pci-ohci", .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(OHCIPCIState), @@ -1898,7 +1933,7 @@ static void ohci_sysbus_class_init(ObjectClass *klass, void *data) dc->props = ohci_sysbus_properties; } -static TypeInfo ohci_sysbus_info = { +static const TypeInfo ohci_sysbus_info = { .name = "sysbus-ohci", .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(OHCISysBusState), diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c index b0db92145a..c85b2038a2 100644 --- a/hw/usb/hcd-uhci.c +++ b/hw/usb/hcd-uhci.c @@ -27,10 +27,10 @@ */ #include "hw/hw.h" #include "hw/usb.h" -#include "hw/pci.h" -#include "qemu-timer.h" -#include "iov.h" -#include "dma.h" +#include "hw/pci/pci.h" +#include "qemu/timer.h" +#include "qemu/iov.h" +#include "sysemu/dma.h" #include "trace.h" //#define DEBUG @@ -75,6 +75,11 @@ #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 { @@ -88,6 +93,23 @@ enum { 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; + int (*initfn)(PCIDevice *dev); + bool unplug; +}; + +struct UHCIPCIDeviceClass { + PCIDeviceClass parent_class; + UHCIInfo info; +}; /* * Pending async transaction. @@ -97,19 +119,21 @@ typedef struct UHCIQueue UHCIQueue; struct UHCIAsync { USBPacket packet; - QEMUSGList sgl; + 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; - uint8_t isoc; + uint32_t td_addr; uint8_t done; }; struct UHCIQueue { + uint32_t qh_addr; uint32_t token; UHCIState *uhci; + USBEndpoint *ep; QTAILQ_ENTRY(UHCIQueue) next; - QTAILQ_HEAD(, UHCIAsync) asyncs; + QTAILQ_HEAD(asyncs_head, UHCIAsync) asyncs; int8_t valid; }; @@ -134,6 +158,7 @@ struct UHCIState { 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. */ @@ -147,6 +172,7 @@ struct UHCIState { /* Properties */ char *masterbus; uint32_t firstport; + uint32_t maxframes; }; typedef struct UHCI_TD { @@ -161,59 +187,96 @@ typedef struct UHCI_QH { uint32_t el_link; } UHCI_QH; +static void uhci_async_cancel(UHCIAsync *async); +static void uhci_queue_fill(UHCIQueue *q, UHCI_TD *td); + static inline int32_t uhci_queue_token(UHCI_TD *td) { - /* covers ep, dev, pid -> identifies the endpoint */ - return td->token & 0x7ffff; + 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_get(UHCIState *s, UHCI_TD *td) +static UHCIQueue *uhci_queue_new(UHCIState *s, uint32_t qh_addr, UHCI_TD *td, + USBEndpoint *ep) { - uint32_t token = uhci_queue_token(td); UHCIQueue *queue; - QTAILQ_FOREACH(queue, &s->queues, next) { - if (queue->token == token) { - return queue; - } - } - queue = g_new0(UHCIQueue, 1); queue->uhci = s; - queue->token = token; + 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) +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); + trace_usb_uhci_queue_del(queue->token, reason); QTAILQ_REMOVE(&s->queues, queue, next); g_free(queue); } -static UHCIAsync *uhci_async_alloc(UHCIQueue *queue, uint32_t addr) +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); + + return queue->qh_addr == qh_addr && + queue->token == uhci_queue_token(td) && + (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; + async->td_addr = td_addr; usb_packet_init(&async->packet); - pci_dma_sglist_init(&async->sgl, &queue->uhci->dev, 1); - trace_usb_uhci_packet_add(async->queue->token, async->td); + 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); + trace_usb_uhci_packet_del(async->queue->token, async->td_addr); usb_packet_cleanup(&async->packet); - qemu_sglist_destroy(&async->sgl); + if (async->buf != async->static_buf) { + g_free(async->buf); + } g_free(async); } @@ -221,19 +284,21 @@ 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); + 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); + trace_usb_uhci_packet_unlink_async(async->queue->token, async->td_addr); } static void uhci_async_cancel(UHCIAsync *async) { - trace_usb_uhci_packet_cancel(async->queue->token, async->td, async->done); + 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); @@ -258,34 +323,21 @@ static void uhci_async_validate_begin(UHCIState *s) static void uhci_async_validate_end(UHCIState *s) { UHCIQueue *queue, *n; - UHCIAsync *async; QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) { - if (queue->valid > 0) { - continue; - } - while (!QTAILQ_EMPTY(&queue->asyncs)) { - async = QTAILQ_FIRST(&queue->asyncs); - uhci_async_unlink(async); - uhci_async_cancel(async); + if (!queue->valid) { + uhci_queue_free(queue, "validate-end"); } - uhci_queue_free(queue); } } static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev) { - UHCIQueue *queue; - UHCIAsync *curr, *n; + UHCIQueue *queue, *n; - QTAILQ_FOREACH(queue, &s->queues, next) { - QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) { - if (!usb_packet_is_inflight(&curr->packet) || - curr->packet.ep->dev != dev) { - continue; - } - uhci_async_unlink(curr); - uhci_async_cancel(curr); + QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) { + if (queue->ep->dev == dev) { + uhci_queue_free(queue, "cancel-device"); } } } @@ -293,38 +345,24 @@ static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev) static void uhci_async_cancel_all(UHCIState *s) { UHCIQueue *queue, *nq; - UHCIAsync *curr, *n; QTAILQ_FOREACH_SAFE(queue, &s->queues, next, nq) { - QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) { - uhci_async_unlink(curr); - uhci_async_cancel(curr); - } - uhci_queue_free(queue); + uhci_queue_free(queue, "cancel-all"); } } -static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, UHCI_TD *td) +static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t td_addr) { - uint32_t token = uhci_queue_token(td); UHCIQueue *queue; UHCIAsync *async; QTAILQ_FOREACH(queue, &s->queues, next) { - if (queue->token == token) { - break; - } - } - if (queue == NULL) { - return NULL; - } - - QTAILQ_FOREACH(async, &queue->asyncs, next) { - if (async->td == addr) { - return async; + QTAILQ_FOREACH(async, &queue->asyncs, next) { + if (async->td_addr == td_addr) { + return async; + } } } - return NULL; } @@ -401,7 +439,7 @@ static int uhci_post_load(void *opaque, int version_id) static const VMStateDescription vmstate_uhci = { .name = "uhci", - .version_id = 2, + .version_id = 3, .minimum_version_id = 1, .minimum_version_id_old = 1, .post_load = uhci_post_load, @@ -419,44 +457,16 @@ static const VMStateDescription vmstate_uhci = { VMSTATE_UINT8(status2, UHCIState), VMSTATE_TIMER(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_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) -{ - UHCIState *s = opaque; - - addr &= 0x1f; - switch(addr) { - case 0x0c: - s->sof_timing = val; - break; - } -} - -static uint32_t uhci_ioport_readb(void *opaque, uint32_t addr) -{ - UHCIState *s = opaque; - uint32_t val; - - addr &= 0x1f; - switch(addr) { - case 0x0c: - val = s->sof_timing; - break; - default: - val = 0xff; - break; - } - return val; -} - -static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +static void uhci_port_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) { UHCIState *s = opaque; - addr &= 0x1f; trace_usb_uhci_mmio_writew(addr, val); switch(addr) { @@ -466,7 +476,7 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) trace_usb_uhci_schedule_start(); s->expire_time = qemu_get_clock_ns(vm_clock) + (get_ticks_per_sec() / FRAME_TIMER_FREQ); - qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock)); + qemu_mod_timer(s->frame_timer, s->expire_time); s->status &= ~UHCI_STS_HCHALTED; } else if (!(val & UHCI_CMD_RS)) { s->status |= UHCI_STS_HCHALTED; @@ -505,6 +515,17 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) 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; @@ -524,6 +545,10 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) } } 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); @@ -532,12 +557,11 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) } } -static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr) +static uint64_t uhci_port_read(void *opaque, hwaddr addr, unsigned size) { UHCIState *s = opaque; uint32_t val; - addr &= 0x1f; switch(addr) { case 0x00: val = s->cmd; @@ -551,6 +575,15 @@ static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr) 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; @@ -573,38 +606,6 @@ static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr) return val; } -static void uhci_ioport_writel(void *opaque, uint32_t addr, uint32_t val) -{ - UHCIState *s = opaque; - - addr &= 0x1f; - trace_usb_uhci_mmio_writel(addr, val); - - switch(addr) { - case 0x08: - s->fl_base_addr = val & ~0xfff; - break; - } -} - -static uint32_t uhci_ioport_readl(void *opaque, uint32_t addr) -{ - UHCIState *s = opaque; - uint32_t val; - - addr &= 0x1f; - switch(addr) { - case 0x08: - val = s->fl_base_addr; - break; - default: - val = 0xffffffff; - break; - } - trace_usb_uhci_mmio_readl(addr, val); - return val; -} - /* signal resume if controller suspended */ static void uhci_resume (void *opaque) { @@ -695,30 +696,75 @@ static USBDevice *uhci_find_device(UHCIState *s, uint8_t addr) return NULL; } -static void uhci_async_complete(USBPort *port, USBPacket *packet); -static void uhci_process_frame(UHCIState *s); +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; +} -/* return -1 if fatal error (frame must be stopped) - 0 if TD successful - 1 if TD unsuccessful or inactive -*/ static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_t *int_mask) { - int len = 0, max_len, err, ret; + int len = 0, max_len; uint8_t pid; max_len = ((td->token >> 21) + 1) & 0x7ff; pid = td->token & 0xff; - ret = async->packet.result; - if (td->ctrl & TD_CTRL_IOS) td->ctrl &= ~TD_CTRL_ACTIVE; - if (ret < 0) - goto out; + 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.result; + 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 @@ -729,157 +775,133 @@ static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_ *int_mask |= 0x01; if (pid == USB_TOKEN_IN) { - if (len > max_len) { - ret = USB_RET_BABBLE; - goto out; - } - + 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); + async->td_addr); return TD_RESULT_NEXT_QH; } } /* success */ - trace_usb_uhci_packet_complete_success(async->queue->token, async->td); + trace_usb_uhci_packet_complete_success(async->queue->token, + async->td_addr); return TD_RESULT_COMPLETE; +} -out: - /* - * We should not do any further processing on a queue with errors! - * This is esp. important for bulk endpoints with pipelining enabled - * (redirection to a real USB device), where we must cancel all the - * transfers after this one so that: - * 1) If they've completed already, they are not processed further - * causing more stalls, originating from the same failed transfer - * 2) If still in flight, they are cancelled before the guest does - * a clear stall, otherwise the guest and device can loose sync! - */ - while (!QTAILQ_EMPTY(&async->queue->asyncs)) { - UHCIAsync *as = QTAILQ_FIRST(&async->queue->asyncs); - uhci_async_unlink(as); - uhci_async_cancel(as); - } - - switch(ret) { - case USB_RET_STALL: - td->ctrl |= TD_CTRL_STALL; - td->ctrl &= ~TD_CTRL_ACTIVE; - s->status |= UHCI_STS_USBERR; - if (td->ctrl & TD_CTRL_IOC) { - *int_mask |= 0x01; - } - uhci_update_irq(s); - trace_usb_uhci_packet_complete_stall(async->queue->token, async->td); - return TD_RESULT_NEXT_QH; +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); - case USB_RET_BABBLE: - td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; - td->ctrl &= ~TD_CTRL_ACTIVE; - s->status |= UHCI_STS_USBERR; - if (td->ctrl & TD_CTRL_IOC) { - *int_mask |= 0x01; + 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; } - uhci_update_irq(s); - /* frame interrupted */ - trace_usb_uhci_packet_complete_babble(async->queue->token, async->td); - return TD_RESULT_STOP_FRAME; - - case USB_RET_NAK: - td->ctrl |= TD_CTRL_NAK; - if (pid == USB_TOKEN_SETUP) - break; - return TD_RESULT_NEXT_QH; - - case USB_RET_IOERROR: - case USB_RET_NODEV: - default: - break; } - /* Retry the TD if error count is not zero */ - - td->ctrl |= TD_CTRL_TIMEOUT; - err = (td->ctrl >> TD_CTRL_ERROR_SHIFT) & 3; - if (err != 0) { - err--; - if (err == 0) { - td->ctrl &= ~TD_CTRL_ACTIVE; - s->status |= UHCI_STS_USBERR; - if (td->ctrl & TD_CTRL_IOC) - *int_mask |= 0x01; - uhci_update_irq(s); - trace_usb_uhci_packet_complete_error(async->queue->token, - async->td); + 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; } } - td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) | - (err << TD_CTRL_ERROR_SHIFT); - return TD_RESULT_NEXT_QH; -} -static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, - uint32_t *int_mask, bool queuing) -{ - UHCIAsync *async; - int len = 0, max_len; - uint8_t pid; - USBDevice *dev; - USBEndpoint *ep; + if (q) { + q->valid = QH_VALID; + } /* Is active ? */ - if (!(td->ctrl & TD_CTRL_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; + } - async = uhci_async_find_td(s, addr, td); if (async) { - /* Already submitted */ - async->queue->valid = 32; - - if (!async->done) - return TD_RESULT_ASYNC_CONT; 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 */ - async = uhci_async_alloc(uhci_queue_get(s, td), addr); + if (q == NULL) { + USBDevice *dev = uhci_find_device(s, (td->token >> 8) & 0x7f); + USBEndpoint *ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf); - /* valid needs to be large enough to handle 10 frame delay - * for initial isochronous requests - */ - async->queue->valid = 32; - async->isoc = td->ctrl & TD_CTRL_IOS; + 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; - pid = td->token & 0xff; - - dev = uhci_find_device(s, (td->token >> 8) & 0x7f); - ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf); - usb_packet_setup(&async->packet, pid, ep, addr); - qemu_sglist_add(&async->sgl, td->buffer, max_len); - usb_packet_map(&async->packet, &async->sgl); + 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: - len = usb_handle_packet(dev, &async->packet); - if (len >= 0) - len = max_len; + 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: - len = usb_handle_packet(dev, &async->packet); + usb_handle_packet(q->ep->dev, &async->packet); break; default: @@ -889,19 +911,19 @@ static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uhci_update_irq(s); return TD_RESULT_STOP_FRAME; } - - if (len == USB_RET_ASYNC) { + + if (async->packet.status == USB_RET_ASYNC) { uhci_async_link(async); + if (!queuing) { + uhci_queue_fill(q, td); + } return TD_RESULT_ASYNC_START; } - async->packet.result = len; - done: - len = uhci_complete_td(s, td, async, int_mask); - usb_packet_unmap(&async->packet, &async->sgl); + ret = uhci_complete_td(s, td, async, int_mask); uhci_async_free(async); - return len; + return ret; } static void uhci_async_complete(USBPort *port, USBPacket *packet) @@ -909,31 +931,15 @@ static void uhci_async_complete(USBPort *port, USBPacket *packet) UHCIAsync *async = container_of(packet, UHCIAsync, packet); UHCIState *s = async->queue->uhci; - if (async->isoc) { - UHCI_TD td; - uint32_t link = async->td; - uint32_t int_mask = 0, val; - - pci_dma_read(&s->dev, link & ~0xf, &td, sizeof(td)); - le32_to_cpus(&td.link); - le32_to_cpus(&td.ctrl); - le32_to_cpus(&td.token); - le32_to_cpus(&td.buffer); - - uhci_async_unlink(async); - uhci_complete_td(s, &td, async, &int_mask); - s->pending_int_mask |= int_mask; - - /* update the status bits of the TD */ - val = cpu_to_le32(td.ctrl); - pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val)); - uhci_async_free(async); - } else { - async->done = 1; - if (s->frame_bytes < s->frame_bandwidth) { - qemu_bh_schedule(s->bh); - } + 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) @@ -978,28 +984,23 @@ static int qhdb_insert(QhDb *db, uint32_t addr) return 0; } -static void uhci_fill_queue(UHCIState *s, UHCI_TD *td) +static void uhci_queue_fill(UHCIQueue *q, UHCI_TD *td) { uint32_t int_mask = 0; uint32_t plink = td->link; - uint32_t token = uhci_queue_token(td); UHCI_TD ptd; int ret; while (is_valid(plink)) { - pci_dma_read(&s->dev, plink & ~0xf, &ptd, sizeof(ptd)); - le32_to_cpus(&ptd.link); - le32_to_cpus(&ptd.ctrl); - le32_to_cpus(&ptd.token); - le32_to_cpus(&ptd.buffer); + uhci_read_td(q->uhci, &ptd, plink); if (!(ptd.ctrl & TD_CTRL_ACTIVE)) { break; } - if (uhci_queue_token(&ptd) != token) { + if (uhci_queue_token(&ptd) != q->token) { break; } trace_usb_uhci_td_queue(plink & ~0xf, ptd.ctrl, ptd.token); - ret = uhci_handle_td(s, plink, &ptd, &int_mask, true); + ret = uhci_handle_td(q->uhci, q, q->qh_addr, &ptd, plink, &int_mask); if (ret == TD_RESULT_ASYNC_CONT) { break; } @@ -1007,6 +1008,7 @@ static void uhci_fill_queue(UHCIState *s, UHCI_TD *td) assert(int_mask == 0); plink = ptd.link; } + usb_device_flush_ep_queue(q->ep->dev, q->ep); } static void uhci_process_frame(UHCIState *s) @@ -1029,7 +1031,7 @@ static void uhci_process_frame(UHCIState *s) qhdb_reset(&qhdb); for (cnt = FRAME_MAX_LOOPS; is_valid(link) && cnt; cnt--) { - if (s->frame_bytes >= s->frame_bandwidth) { + 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(); @@ -1075,15 +1077,11 @@ static void uhci_process_frame(UHCIState *s) } /* TD */ - pci_dma_read(&s->dev, link & ~0xf, &td, sizeof(td)); - le32_to_cpus(&td.link); - le32_to_cpus(&td.ctrl); - le32_to_cpus(&td.token); - le32_to_cpus(&td.buffer); + 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, link, &td, &int_mask, false); + 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); @@ -1102,9 +1100,6 @@ static void uhci_process_frame(UHCIState *s) case TD_RESULT_ASYNC_START: trace_usb_uhci_td_async(curr_qh & ~0xf, link & ~0xf); - if (is_valid(td.link)) { - uhci_fill_queue(s, &td); - } link = curr_qh ? qh.link : td.link; continue; @@ -1148,10 +1143,11 @@ static void uhci_bh(void *opaque) 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; - /* prepare the timer for the next frame */ - s->expire_time += (get_ticks_per_sec() / FRAME_TIMER_FREQ); - s->frame_bytes = 0; + s->completions_only = false; qemu_bh_cancel(s->bh); if (!(s->cmd & UHCI_CMD_RS)) { @@ -1164,7 +1160,35 @@ static void uhci_frame_timer(void *opaque) return; } - /* Complete the previous frame */ + /* We still store expire_time in our state, for migration */ + t_last_run = s->expire_time - frame_t; + t_now = qemu_get_clock_ns(vm_clock); + + /* 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; @@ -1172,32 +1196,17 @@ static void uhci_frame_timer(void *opaque) } s->pending_int_mask = 0; - /* Start new frame */ - s->frnum = (s->frnum + 1) & 0x7ff; - - trace_usb_uhci_frame_start(s->frnum); - - uhci_async_validate_begin(s); - - uhci_process_frame(s); - - uhci_async_validate_end(s); - - qemu_mod_timer(s->frame_timer, s->expire_time); + qemu_mod_timer(s->frame_timer, t_now + frame_t); } -static const MemoryRegionPortio uhci_portio[] = { - { 0, 32, 2, .write = uhci_ioport_writew, }, - { 0, 32, 2, .read = uhci_ioport_readw, }, - { 0, 32, 4, .write = uhci_ioport_writel, }, - { 0, 32, 4, .read = uhci_ioport_readl, }, - { 0, 32, 1, .write = uhci_ioport_writeb, }, - { 0, 32, 1, .read = uhci_ioport_readb, }, - PORTIO_END_OF_LIST() -}; - static const MemoryRegionOps uhci_ioport_ops = { - .old_portio = uhci_portio, + .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 = { @@ -1214,6 +1223,7 @@ static USBBusOps uhci_bus_ops = { static int usb_uhci_common_initfn(PCIDevice *dev) { PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); + UHCIPCIDeviceClass *u = container_of(pc, UHCIPCIDeviceClass, parent_class); UHCIState *s = DO_UPCAST(UHCIState, dev, dev); uint8_t *pci_conf = s->dev.config; int i; @@ -1222,20 +1232,7 @@ static int usb_uhci_common_initfn(PCIDevice *dev) /* TODO: reset value should be 0. */ pci_conf[USB_SBRN] = USB_RELEASE_1; // release number - switch (pc->device_id) { - case PCI_DEVICE_ID_INTEL_82801I_UHCI1: - s->irq_pin = 0; /* A */ - break; - case PCI_DEVICE_ID_INTEL_82801I_UHCI2: - s->irq_pin = 1; /* B */ - break; - case PCI_DEVICE_ID_INTEL_82801I_UHCI3: - s->irq_pin = 2; /* C */ - break; - default: - s->irq_pin = 3; /* D */ - break; - } + s->irq_pin = u->info.irq_pin; pci_config_set_interrupt_pin(pci_conf, s->irq_pin + 1); if (s->masterbus) { @@ -1296,146 +1293,112 @@ static Property uhci_properties[] = { 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 void piix3_uhci_class_init(ObjectClass *klass, void *data) +static void uhci_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_uhci_common_initfn; - k->exit = usb_uhci_exit; - k->vendor_id = PCI_VENDOR_ID_INTEL; - k->device_id = PCI_DEVICE_ID_INTEL_82371SB_2; - k->revision = 0x01; - k->class_id = PCI_CLASS_SERIAL_USB; + UHCIPCIDeviceClass *u = container_of(k, UHCIPCIDeviceClass, parent_class); + UHCIInfo *info = data; + + k->init = info->initfn ? info->initfn : usb_uhci_common_initfn; + k->exit = info->unplug ? usb_uhci_exit : NULL; + k->vendor_id = info->vendor_id; + k->device_id = info->device_id; + k->revision = info->revision; + k->class_id = PCI_CLASS_SERIAL_USB; + k->no_hotplug = 1; dc->vmsd = &vmstate_uhci; dc->props = uhci_properties; + u->info = *info; } -static TypeInfo piix3_uhci_info = { - .name = "piix3-usb-uhci", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(UHCIState), - .class_init = piix3_uhci_class_init, -}; - -static void piix4_uhci_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_uhci_common_initfn; - k->exit = usb_uhci_exit; - k->vendor_id = PCI_VENDOR_ID_INTEL; - k->device_id = PCI_DEVICE_ID_INTEL_82371AB_2; - k->revision = 0x01; - k->class_id = PCI_CLASS_SERIAL_USB; - dc->vmsd = &vmstate_uhci; - dc->props = uhci_properties; -} - -static TypeInfo piix4_uhci_info = { - .name = "piix4-usb-uhci", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(UHCIState), - .class_init = piix4_uhci_class_init, -}; - -static void vt82c686b_uhci_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_uhci_vt82c686b_initfn; - k->exit = usb_uhci_exit; - k->vendor_id = PCI_VENDOR_ID_VIA; - k->device_id = PCI_DEVICE_ID_VIA_UHCI; - k->revision = 0x01; - k->class_id = PCI_CLASS_SERIAL_USB; - dc->vmsd = &vmstate_uhci; - dc->props = uhci_properties; -} - -static TypeInfo vt82c686b_uhci_info = { - .name = "vt82c686b-usb-uhci", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(UHCIState), - .class_init = vt82c686b_uhci_class_init, -}; - -static void ich9_uhci1_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_uhci_common_initfn; - k->vendor_id = PCI_VENDOR_ID_INTEL; - k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI1; - k->revision = 0x03; - k->class_id = PCI_CLASS_SERIAL_USB; - dc->vmsd = &vmstate_uhci; - dc->props = uhci_properties; -} - -static TypeInfo ich9_uhci1_info = { - .name = "ich9-usb-uhci1", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(UHCIState), - .class_init = ich9_uhci1_class_init, -}; - -static void ich9_uhci2_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_uhci_common_initfn; - k->vendor_id = PCI_VENDOR_ID_INTEL; - k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI2; - k->revision = 0x03; - k->class_id = PCI_CLASS_SERIAL_USB; - dc->vmsd = &vmstate_uhci; - dc->props = uhci_properties; -} - -static TypeInfo ich9_uhci2_info = { - .name = "ich9-usb-uhci2", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(UHCIState), - .class_init = ich9_uhci2_class_init, -}; - -static void ich9_uhci3_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = usb_uhci_common_initfn; - k->vendor_id = PCI_VENDOR_ID_INTEL; - k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI3; - k->revision = 0x03; - k->class_id = PCI_CLASS_SERIAL_USB; - dc->vmsd = &vmstate_uhci; - dc->props = uhci_properties; -} - -static TypeInfo ich9_uhci3_info = { - .name = "ich9-usb-uhci3", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(UHCIState), - .class_init = ich9_uhci3_class_init, +static 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, + .initfn = usb_uhci_vt82c686b_initfn, + .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) { - type_register_static(&piix3_uhci_info); - type_register_static(&piix4_uhci_info); - type_register_static(&vt82c686b_uhci_info); - type_register_static(&ich9_uhci1_info); - type_register_static(&ich9_uhci2_info); - type_register_static(&ich9_uhci3_info); + TypeInfo uhci_type_info = { + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(UHCIState), + .class_size = sizeof(UHCIPCIDeviceClass), + .class_init = uhci_class_init, + }; + int i; + + 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 index 3eb27fadb8..8813bdf904 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -19,10 +19,11 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ #include "hw/hw.h" -#include "qemu-timer.h" +#include "qemu/timer.h" #include "hw/usb.h" -#include "hw/pci.h" -#include "hw/msi.h" +#include "hw/pci/pci.h" +#include "hw/pci/msi.h" +#include "hw/pci/msix.h" #include "trace.h" //#define DEBUG_XHCI @@ -33,20 +34,17 @@ #else #define DPRINTF(...) do {} while (0) #endif -#define FIXME() do { fprintf(stderr, "FIXME %s:%d\n", \ - __func__, __LINE__); abort(); } while (0) +#define FIXME(_msg) do { fprintf(stderr, "FIXME %s:%d %s\n", \ + __func__, __LINE__, _msg); abort(); } while (0) -#define MAXSLOTS 8 -#define MAXINTRS 1 +#define MAXPORTS_2 15 +#define MAXPORTS_3 15 -#define USB2_PORTS 4 -#define USB3_PORTS 4 - -#define MAXPORTS (USB2_PORTS+USB3_PORTS) +#define MAXPORTS (MAXPORTS_2+MAXPORTS_3) +#define MAXSLOTS 64 +#define MAXINTRS 16 #define TD_QUEUE 24 -#define BG_XFERS 8 -#define BG_PKTS 8 /* Very pessimistic, let's hope it's enough for all cases */ #define EV_QUEUE (((3*TD_QUEUE)+16)*MAXSLOTS) @@ -55,24 +53,28 @@ #define ER_FULL_HACK #define LEN_CAP 0x40 -#define OFF_OPER LEN_CAP #define LEN_OPER (0x400 + 0x10 * MAXPORTS) -#define OFF_RUNTIME ((OFF_OPER + LEN_OPER + 0x20) & ~0x1f) -#define LEN_RUNTIME (0x20 + MAXINTRS * 0x20) -#define OFF_DOORBELL (OFF_RUNTIME + LEN_RUNTIME) +#define LEN_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 0x2000 +#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 -#if MAXINTRS > 1 -# error TODO: only one interrupter supported -#endif - /* bit definitions */ #define USBCMD_RS (1<<0) #define USBCMD_HCRST (1<<1) @@ -144,6 +146,21 @@ typedef struct XHCITRB { 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, @@ -258,6 +275,10 @@ typedef enum TRBCCode { #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 @@ -279,6 +300,20 @@ typedef enum TRBCCode { #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, @@ -291,71 +326,87 @@ typedef enum EPType { } EPType; typedef struct XHCIRing { - dma_addr_t base; dma_addr_t dequeue; bool ccs; } XHCIRing; typedef struct XHCIPort { - USBPort port; + XHCIState *xhci; uint32_t portsc; + uint32_t portnr; + USBPort *uport; + uint32_t speedmask; + char name[16]; + MemoryRegion mem; } XHCIPort; -struct XHCIState; -typedef struct XHCIState XHCIState; - typedef struct XHCITransfer { XHCIState *xhci; USBPacket packet; + QEMUSGList sgl; bool running_async; bool running_retry; bool cancelled; bool complete; - bool backgrounded; + bool int_req; unsigned int iso_pkts; unsigned int slotid; unsigned int epid; + unsigned int streamid; bool in_xfer; bool iso_xfer; - bool bg_xfer; unsigned int trb_count; unsigned int trb_alloced; XHCITRB *trbs; - unsigned int data_length; - unsigned int data_alloced; - uint8_t *data; - TRBCCode status; unsigned int pkts; unsigned int pktsize; unsigned int cur_pkt; + + uint64_t mfindex_kick; } XHCITransfer; -typedef struct XHCIEPContext { +struct XHCIStreamContext { + dma_addr_t pctx; + unsigned int sct; + XHCIRing ring; + XHCIStreamContext *sstreams; +}; + +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; - bool bg_running; - bool bg_updating; - unsigned int next_bg; - XHCITransfer bg_transfers[BG_XFERS]; EPType type; dma_addr_t pctx; unsigned int max_psize; - bool has_bg; uint32_t state; -} XHCIEPContext; + + /* 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; dma_addr_t ctx; - unsigned int port; - unsigned int devaddr; + USBPort *uport; XHCIEPContext * eps[31]; } XHCISlot; @@ -369,14 +420,43 @@ typedef struct XHCIEvent { 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 { PCIDevice pci_dev; USBBus bus; qemu_irq irq; MemoryRegion mem; - const char *name; - uint32_t msi; - unsigned int devaddr; + 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; /* Operational Registers */ uint32_t usbcmd; @@ -388,29 +468,15 @@ struct XHCIState { 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 */ - uint32_t mfindex; - /* note: we only support one interrupter */ - uint32_t iman; - uint32_t imod; - uint32_t erstsz; - uint32_t erstba_low; - uint32_t erstba_high; - uint32_t erdp_low; - uint32_t erdp_high; - - dma_addr_t er_start; - uint32_t er_size; - bool er_pcs; - unsigned int er_ep_idx; - bool er_full; - - XHCIEvent ev_buffer[EV_QUEUE]; - unsigned int ev_buffer_put; - unsigned int ev_buffer_get; + int64_t mfindex_start; + QEMUTimer *mfwrap_timer; + XHCIInterrupter intr[MAXINTRS]; XHCIRing cmd_ring; }; @@ -422,6 +488,18 @@ typedef struct XHCIEvRingSeg { uint32_t rsvd; } XHCIEvRingSeg; +enum xhci_flags { + XHCI_FLAG_USE_MSI = 1, + XHCI_FLAG_USE_MSI_X, +}; + +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_event(XHCIState *xhci, XHCIEvent *event, int v); +static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v); + static const char *TRBType_names[] = { [TRB_RESERVED] = "TRB_RESERVED", [TR_NORMAL] = "TR_NORMAL", @@ -460,6 +538,45 @@ static const char *TRBType_names[] = { [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 *lookup_name(uint32_t index, const char **list, uint32_t llen) { if (index >= llen || list[index] == NULL) { @@ -474,8 +591,42 @@ static const char *trb_name(XHCITRB *trb) ARRAY_SIZE(TRBType_names)); } -static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, - unsigned int epid); +static const char *event_name(XHCIEvent *event) +{ + return lookup_name(event->ccode, TRBCCode_names, + ARRAY_SIZE(TRBCCode_names)); +} + +static uint64_t xhci_mfindex_get(XHCIState *xhci) +{ + int64_t now = qemu_get_clock_ns(vm_clock); + 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_get_clock_ns(vm_clock); + mfindex = ((now - xhci->mfindex_start) / 125000) & 0x3fff; + left = 0x4000 - mfindex; + qemu_mod_timer(xhci->mfwrap_timer, now + left * 125000); + } else { + qemu_del_timer(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) { @@ -495,29 +646,134 @@ static inline dma_addr_t xhci_mask64(uint64_t addr) } } -static void xhci_irq_update(XHCIState *xhci) +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(&xhci->pci_dev, 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(&xhci->pci_dev, 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: + index = uport->index; + break; + case USB_SPEED_SUPER: + index = uport->index + xhci->numports_2; + break; + default: + return NULL; + } + return &xhci->ports[index]; +} + +static void xhci_intx_update(XHCIState *xhci) { int level = 0; - if (xhci->iman & IMAN_IP && xhci->iman & IMAN_IE && + if (msix_enabled(&xhci->pci_dev) || + msi_enabled(&xhci->pci_dev)) { + return; + } + + if (xhci->intr[0].iman & IMAN_IP && + xhci->intr[0].iman & IMAN_IE && xhci->usbcmd & USBCMD_INTE) { level = 1; } - if (xhci->msi && msi_enabled(&xhci->pci_dev)) { - if (level) { - trace_usb_xhci_irq_msi(0); - msi_notify(&xhci->pci_dev, 0); - } + trace_usb_xhci_irq_intx(level); + qemu_set_irq(xhci->irq, level); +} + +static void xhci_msix_update(XHCIState *xhci, int v) +{ + bool enabled; + + if (!msix_enabled(&xhci->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(&xhci->pci_dev, v); + xhci->intr[v].msix_used = true; } else { - trace_usb_xhci_irq_intx(level); - qemu_set_irq(xhci->irq, level); + trace_usb_xhci_irq_msix_unuse(v); + msix_vector_unuse(&xhci->pci_dev, v); + xhci->intr[v].msix_used = false; + } +} + +static void xhci_intr_raise(XHCIState *xhci, int v) +{ + 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(&xhci->pci_dev)) { + trace_usb_xhci_irq_msix(v); + msix_notify(&xhci->pci_dev, v); + return; + } + + if (msi_enabled(&xhci->pci_dev)) { + trace_usb_xhci_irq_msi(v); + msi_notify(&xhci->pci_dev, v); + return; + } + + if (v == 0) { + trace_usb_xhci_irq_intx(1); + qemu_set_irq(xhci->irq, 1); } } static inline int xhci_running(XHCIState *xhci) { - return !(xhci->usbsts & USBSTS_HCH) && !xhci->er_full; + return !(xhci->usbsts & USBSTS_HCH) && !xhci->intr[0].er_full; } static void xhci_die(XHCIState *xhci) @@ -526,8 +782,9 @@ static void xhci_die(XHCIState *xhci) fprintf(stderr, "xhci: asserted controller error\n"); } -static void xhci_write_event(XHCIState *xhci, XHCIEvent *event) +static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v) { + XHCIInterrupter *intr = &xhci->intr[v]; XHCITRB ev_trb; dma_addr_t addr; @@ -535,26 +792,28 @@ static void xhci_write_event(XHCIState *xhci, XHCIEvent *event) ev_trb.status = cpu_to_le32(event->length | (event->ccode << 24)); ev_trb.control = (event->slotid << 24) | (event->epid << 16) | event->flags | (event->type << TRB_TYPE_SHIFT); - if (xhci->er_pcs) { + if (intr->er_pcs) { ev_trb.control |= TRB_C; } ev_trb.control = cpu_to_le32(ev_trb.control); - trace_usb_xhci_queue_event(xhci->er_ep_idx, trb_name(&ev_trb), - ev_trb.parameter, ev_trb.status, 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 = xhci->er_start + TRB_SIZE*xhci->er_ep_idx; + addr = intr->er_start + TRB_SIZE*intr->er_ep_idx; pci_dma_write(&xhci->pci_dev, addr, &ev_trb, TRB_SIZE); - xhci->er_ep_idx++; - if (xhci->er_ep_idx >= xhci->er_size) { - xhci->er_ep_idx = 0; - xhci->er_pcs = !xhci->er_pcs; + 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) +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; @@ -563,128 +822,126 @@ static void xhci_events_update(XHCIState *xhci) return; } - erdp = xhci_addr64(xhci->erdp_low, xhci->erdp_high); - if (erdp < xhci->er_start || - erdp >= (xhci->er_start + TRB_SIZE*xhci->er_size)) { + erdp = xhci_addr64(intr->erdp_low, intr->erdp_high); + if (erdp < intr->er_start || + erdp >= (intr->er_start + TRB_SIZE*intr->er_size)) { fprintf(stderr, "xhci: ERDP out of bounds: "DMA_ADDR_FMT"\n", erdp); - fprintf(stderr, "xhci: ER at "DMA_ADDR_FMT" len %d\n", - xhci->er_start, xhci->er_size); + fprintf(stderr, "xhci: ER[%d] at "DMA_ADDR_FMT" len %d\n", + v, intr->er_start, intr->er_size); xhci_die(xhci); return; } - dp_idx = (erdp - xhci->er_start) / TRB_SIZE; - assert(dp_idx < xhci->er_size); + 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 (xhci->er_full) { - int er_free = dp_idx - xhci->er_ep_idx; + if (intr->er_full) { + int er_free = dp_idx - intr->er_ep_idx; if (er_free <= 0) { - er_free += xhci->er_size; + er_free += intr->er_size; } - if (er_free < (xhci->er_size/2)) { + if (er_free < (intr->er_size/2)) { DPRINTF("xhci_events_update(): event ring still " "more than half full (hack)\n"); return; } } - while (xhci->ev_buffer_put != xhci->ev_buffer_get) { - assert(xhci->er_full); - if (((xhci->er_ep_idx+1) % xhci->er_size) == dp_idx) { + 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); + xhci_write_event(xhci, &full, v); #endif do_irq = 1; break; } - XHCIEvent *event = &xhci->ev_buffer[xhci->ev_buffer_get]; - xhci_write_event(xhci, event); - xhci->ev_buffer_get++; + XHCIEvent *event = &intr->ev_buffer[intr->ev_buffer_get]; + xhci_write_event(xhci, event, v); + intr->ev_buffer_get++; do_irq = 1; - if (xhci->ev_buffer_get == EV_QUEUE) { - xhci->ev_buffer_get = 0; + if (intr->ev_buffer_get == EV_QUEUE) { + intr->ev_buffer_get = 0; } } if (do_irq) { - xhci->erdp_low |= ERDP_EHB; - xhci->iman |= IMAN_IP; - xhci->usbsts |= USBSTS_EINT; - xhci_irq_update(xhci); + xhci_intr_raise(xhci, v); } - if (xhci->er_full && xhci->ev_buffer_put == xhci->ev_buffer_get) { + if (intr->er_full && intr->ev_buffer_put == intr->ev_buffer_get) { DPRINTF("xhci_events_update(): event ring no longer full\n"); - xhci->er_full = 0; + intr->er_full = 0; } - return; } -static void xhci_event(XHCIState *xhci, XHCIEvent *event) +static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v) { + XHCIInterrupter *intr; dma_addr_t erdp; unsigned int dp_idx; - if (xhci->er_full) { + 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 (((xhci->ev_buffer_put+1) % EV_QUEUE) == xhci->ev_buffer_get) { + if (((intr->ev_buffer_put+1) % EV_QUEUE) == intr->ev_buffer_get) { fprintf(stderr, "xhci: event queue full, dropping event!\n"); return; } - xhci->ev_buffer[xhci->ev_buffer_put++] = *event; - if (xhci->ev_buffer_put == EV_QUEUE) { - xhci->ev_buffer_put = 0; + intr->ev_buffer[intr->ev_buffer_put++] = *event; + if (intr->ev_buffer_put == EV_QUEUE) { + intr->ev_buffer_put = 0; } return; } - erdp = xhci_addr64(xhci->erdp_low, xhci->erdp_high); - if (erdp < xhci->er_start || - erdp >= (xhci->er_start + TRB_SIZE*xhci->er_size)) { + erdp = xhci_addr64(intr->erdp_low, intr->erdp_high); + if (erdp < intr->er_start || + erdp >= (intr->er_start + TRB_SIZE*intr->er_size)) { fprintf(stderr, "xhci: ERDP out of bounds: "DMA_ADDR_FMT"\n", erdp); - fprintf(stderr, "xhci: ER at "DMA_ADDR_FMT" len %d\n", - xhci->er_start, xhci->er_size); + fprintf(stderr, "xhci: ER[%d] at "DMA_ADDR_FMT" len %d\n", + v, intr->er_start, intr->er_size); xhci_die(xhci); return; } - dp_idx = (erdp - xhci->er_start) / TRB_SIZE; - assert(dp_idx < xhci->er_size); + dp_idx = (erdp - intr->er_start) / TRB_SIZE; + assert(dp_idx < intr->er_size); - if ((xhci->er_ep_idx+1) % xhci->er_size == dp_idx) { + 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 - xhci->er_full = 1; - if (((xhci->ev_buffer_put+1) % EV_QUEUE) == xhci->ev_buffer_get) { + intr->er_full = 1; + if (((intr->ev_buffer_put+1) % EV_QUEUE) == intr->ev_buffer_get) { fprintf(stderr, "xhci: event queue full, dropping event!\n"); return; } - xhci->ev_buffer[xhci->ev_buffer_put++] = *event; - if (xhci->ev_buffer_put == EV_QUEUE) { - xhci->ev_buffer_put = 0; + 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); + xhci_write_event(xhci, event, v); } - xhci->erdp_low |= ERDP_EHB; - xhci->iman |= IMAN_IP; - xhci->usbsts |= USBSTS_EINT; - - xhci_irq_update(xhci); + xhci_intr_raise(xhci, v); } static void xhci_ring_init(XHCIState *xhci, XHCIRing *ring, dma_addr_t base) { - ring->base = base; ring->dequeue = base; ring->ccs = 1; } @@ -770,17 +1027,24 @@ static int xhci_ring_chain_length(XHCIState *xhci, const XHCIRing *ring) } } -static void xhci_er_reset(XHCIState *xhci) +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 (xhci->erstsz != 1) { - fprintf(stderr, "xhci: invalid value for ERSTSZ: %d\n", xhci->erstsz); + if (intr->erstsz != 1) { + fprintf(stderr, "xhci: invalid value for ERSTSZ: %d\n", intr->erstsz); xhci_die(xhci); return; } - dma_addr_t erstba = xhci_addr64(xhci->erstba_low, xhci->erstba_high); + dma_addr_t erstba = xhci_addr64(intr->erstba_low, intr->erstba_high); pci_dma_read(&xhci->pci_dev, erstba, &seg, sizeof(seg)); le32_to_cpus(&seg.addr_low); le32_to_cpus(&seg.addr_high); @@ -790,21 +1054,22 @@ static void xhci_er_reset(XHCIState *xhci) xhci_die(xhci); return; } - xhci->er_start = xhci_addr64(seg.addr_low, seg.addr_high); - xhci->er_size = seg.size; + intr->er_start = xhci_addr64(seg.addr_low, seg.addr_high); + intr->er_size = seg.size; - xhci->er_ep_idx = 0; - xhci->er_pcs = 1; - xhci->er_full = 0; + intr->er_ep_idx = 0; + intr->er_pcs = 1; + intr->er_full = 0; - DPRINTF("xhci: event ring:" DMA_ADDR_FMT " [%d]\n", - xhci->er_start, xhci->er_size); + 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_get_clock_ns(vm_clock); } static void xhci_stop(XHCIState *xhci) @@ -814,25 +1079,124 @@ static void xhci_stop(XHCIState *xhci) 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; + g_free(epctx->pstreams[i].sstreams); + } +} + +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) +{ + int i; + + assert(epctx->pstreams != NULL); + + if (!epctx->lsa) { + for (i = 0; i < epctx->nr_pstreams; i++) { + g_free(epctx->pstreams[i].sstreams); + } + } + g_free(epctx->pstreams); + epctx->pstreams = NULL; + epctx->nr_pstreams = 0; +} + +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)); + fprintf(stderr, "%s: init sctx #%d @ " DMA_ADDR_FMT ": %08x %08x\n", + __func__, streamid, sctx->pctx, ctx[0], ctx[1]); + 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, - uint32_t state) + XHCIStreamContext *sctx, uint32_t state) { uint32_t ctx[5]; - if (epctx->state == state) { - return; - } + uint32_t ctx2[2]; - pci_dma_read(&xhci->pci_dev, epctx->pctx, ctx, sizeof(ctx)); + xhci_dma_read_u32s(xhci, epctx->pctx, ctx, sizeof(ctx)); ctx[0] &= ~EP_STATE_MASK; ctx[0] |= state; - ctx[2] = epctx->ring.dequeue | epctx->ring.ccs; - ctx[3] = (epctx->ring.dequeue >> 16) >> 16; - DPRINTF("xhci: set epctx: " DMA_ADDR_FMT " state=%d dequeue=%08x%08x\n", - epctx->pctx, state, ctx[3], ctx[2]); - pci_dma_write(&xhci->pci_dev, epctx->pctx, ctx, sizeof(ctx)); + + /* update ring dequeue ptr */ + if (epctx->nr_pstreams) { + if (sctx != NULL) { + 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 { + ctx[2] = epctx->ring.dequeue | epctx->ring.ccs; + ctx[3] = (epctx->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)); 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 TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid, unsigned int epid, dma_addr_t pctx, uint32_t *ctx) @@ -843,39 +1207,47 @@ static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid, int i; trace_usb_xhci_ep_enable(slotid, epid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); assert(epid >= 1 && epid <= 31); slot = &xhci->slots[slotid-1]; if (slot->eps[epid-1]) { - fprintf(stderr, "xhci: slot %d ep %d already enabled!\n", slotid, epid); - return CC_TRB_ERROR; + xhci_disable_ep(xhci, slotid, epid); } epctx = g_malloc(sizeof(XHCIEPContext)); memset(epctx, 0, sizeof(XHCIEPContext)); + epctx->xhci = xhci; + epctx->slotid = slotid; + epctx->epid = epid; slot->eps[epid-1] = epctx; dequeue = xhci_addr64(ctx[2] & ~0xf, ctx[3]); - xhci_ring_init(xhci, &epctx->ring, dequeue); - epctx->ring.ccs = ctx[2] & 1; epctx->type = (ctx[1] >> EP_TYPE_SHIFT) & EP_TYPE_MASK; DPRINTF("xhci: endpoint %d.%d type is %d\n", epid/2, epid%2, epctx->type); epctx->pctx = pctx; epctx->max_psize = ctx[1]>>16; epctx->max_psize *= 1+((ctx[1]>>8)&0xff); - epctx->has_bg = false; - if (epctx->type == ET_ISO_IN) { - epctx->has_bg = true; - } + epctx->max_pstreams = (ctx[0] >> 10) & 0xf; + epctx->lsa = (ctx[0] >> 15) & 1; DPRINTF("xhci: endpoint %d.%d max transaction (burst) size is %d\n", epid/2, epid%2, epctx->max_psize); + if (epctx->max_pstreams) { + xhci_alloc_streams(epctx, dequeue); + } else { + xhci_ring_init(xhci, &epctx->ring, dequeue); + epctx->ring.ccs = ctx[2] & 1; + } for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) { usb_packet_init(&epctx->transfers[i].packet); } + epctx->interval = 1 << (ctx[0] >> 16) & 0xff; + epctx->mfindex_last = 0; + epctx->kick_timer = qemu_new_timer_ns(vm_clock, xhci_ep_kick_timer, epctx); + epctx->state = EP_RUNNING; ctx[0] &= ~EP_STATE_MASK; ctx[0] |= EP_RUNNING; @@ -883,13 +1255,43 @@ static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid, return CC_SUCCESS; } +static int xhci_ep_nuke_one_xfer(XHCITransfer *t) +{ + int killed = 0; + + if (t->running_async) { + usb_cancel_packet(&t->packet); + t->running_async = 0; + t->cancelled = 1; + DPRINTF("xhci: cancelling transfer, waiting for it to complete\n"); + killed = 1; + } + if (t->running_retry) { + XHCIEPContext *epctx = t->xhci->slots[t->slotid-1].eps[t->epid-1]; + if (epctx) { + epctx->retry = NULL; + qemu_del_timer(epctx->kick_timer); + } + t->running_retry = 0; + } + 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) { XHCISlot *slot; XHCIEPContext *epctx; int i, xferi, killed = 0; - assert(slotid >= 1 && slotid <= MAXSLOTS); + USBEndpoint *ep = NULL; + assert(slotid >= 1 && slotid <= xhci->numslots); assert(epid >= 1 && epid <= 31); DPRINTF("xhci_ep_nuke_xfers(%d, %d)\n", slotid, epid); @@ -904,52 +1306,15 @@ static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid, xferi = epctx->next_xfer; for (i = 0; i < TD_QUEUE; i++) { - XHCITransfer *t = &epctx->transfers[xferi]; - if (t->running_async) { - usb_cancel_packet(&t->packet); - t->running_async = 0; - t->cancelled = 1; - DPRINTF("xhci: cancelling transfer %d, waiting for it to complete...\n", i); - killed++; - } - if (t->running_retry) { - t->running_retry = 0; - epctx->retry = NULL; + if (epctx->transfers[xferi].packet.ep) { + ep = epctx->transfers[xferi].packet.ep; } - if (t->backgrounded) { - t->backgrounded = 0; - } - if (t->trbs) { - g_free(t->trbs); - } - if (t->data) { - g_free(t->data); - } - - t->trbs = NULL; - t->data = NULL; - t->trb_count = t->trb_alloced = 0; - t->data_length = t->data_alloced = 0; + killed += xhci_ep_nuke_one_xfer(&epctx->transfers[xferi]); + epctx->transfers[xferi].packet.ep = NULL; xferi = (xferi + 1) % TD_QUEUE; } - if (epctx->has_bg) { - xferi = epctx->next_bg; - for (i = 0; i < BG_XFERS; i++) { - XHCITransfer *t = &epctx->bg_transfers[xferi]; - if (t->running_async) { - usb_cancel_packet(&t->packet); - t->running_async = 0; - t->cancelled = 1; - DPRINTF("xhci: cancelling bg transfer %d, waiting for it to complete...\n", i); - killed++; - } - if (t->data) { - g_free(t->data); - } - - t->data = NULL; - xferi = (xferi + 1) % BG_XFERS; - } + if (ep) { + usb_device_ep_stopped(ep->dev, ep); } return killed; } @@ -961,7 +1326,7 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, XHCIEPContext *epctx; trace_usb_xhci_ep_disable(slotid, epid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); assert(epid >= 1 && epid <= 31); slot = &xhci->slots[slotid-1]; @@ -975,8 +1340,13 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, epctx = slot->eps[epid-1]; - xhci_set_ep_state(xhci, epctx, EP_DISABLED); + if (epctx->nr_pstreams) { + xhci_free_streams(epctx); + } + + xhci_set_ep_state(xhci, epctx, NULL, EP_DISABLED); + qemu_free_timer(epctx->kick_timer); g_free(epctx); slot->eps[epid-1] = NULL; @@ -990,7 +1360,7 @@ static TRBCCode xhci_stop_ep(XHCIState *xhci, unsigned int slotid, XHCIEPContext *epctx; trace_usb_xhci_ep_stop(slotid, epid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); if (epid < 1 || epid > 31) { fprintf(stderr, "xhci: bad ep %d\n", epid); @@ -1011,7 +1381,11 @@ static TRBCCode xhci_stop_ep(XHCIState *xhci, unsigned int slotid, epctx = slot->eps[epid-1]; - xhci_set_ep_state(xhci, epctx, EP_STOPPED); + xhci_set_ep_state(xhci, epctx, NULL, EP_STOPPED); + + if (epctx->nr_pstreams) { + xhci_reset_streams(epctx); + } return CC_SUCCESS; } @@ -1024,7 +1398,7 @@ static TRBCCode xhci_reset_ep(XHCIState *xhci, unsigned int slotid, USBDevice *dev; trace_usb_xhci_ep_reset(slotid, epid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); if (epid < 1 || epid > 31) { fprintf(stderr, "xhci: bad ep %d\n", epid); @@ -1057,31 +1431,37 @@ static TRBCCode xhci_reset_ep(XHCIState *xhci, unsigned int slotid, ep |= 0x80; } - dev = xhci->ports[xhci->slots[slotid-1].port-1].port.dev; + dev = xhci->slots[slotid-1].uport->dev; if (!dev) { return CC_USB_TRANSACTION_ERROR; } - xhci_set_ep_state(xhci, epctx, EP_STOPPED); + 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, uint64_t pdequeue) + unsigned int epid, unsigned int streamid, + uint64_t pdequeue) { XHCISlot *slot; XHCIEPContext *epctx; + XHCIStreamContext *sctx; dma_addr_t dequeue; - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); if (epid < 1 || epid > 31) { fprintf(stderr, "xhci: bad ep %d\n", epid); return CC_TRB_ERROR; } - DPRINTF("xhci_set_ep_dequeue(%d, %d, %016"PRIx64")\n", slotid, epid, pdequeue); + trace_usb_xhci_ep_set_dequeue(slotid, epid, streamid, pdequeue); dequeue = xhci_mask64(pdequeue); slot = &xhci->slots[slotid-1]; @@ -1093,95 +1473,113 @@ static TRBCCode xhci_set_ep_dequeue(XHCIState *xhci, unsigned int slotid, epctx = slot->eps[epid-1]; - if (epctx->state != EP_STOPPED) { fprintf(stderr, "xhci: set EP dequeue pointer while EP %d not stopped\n", epid); return CC_CONTEXT_STATE_ERROR; } - xhci_ring_init(xhci, &epctx->ring, dequeue & ~0xF); - epctx->ring.ccs = dequeue & 1; + 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, EP_STOPPED); + xhci_set_ep_state(xhci, epctx, sctx, EP_STOPPED); return CC_SUCCESS; } -static int xhci_xfer_data(XHCITransfer *xfer, uint8_t *data, - unsigned int length, bool in_xfer, bool out_xfer, - bool report) +static int xhci_xfer_create_sgl(XHCITransfer *xfer, int in_xfer) { - int i; - uint32_t edtla = 0; - unsigned int transferred = 0; - unsigned int left = length; - bool reported = 0; - bool shortpkt = 0; - XHCIEvent event = {ER_TRANSFER, CC_SUCCESS}; XHCIState *xhci = xfer->xhci; + int i; - DPRINTF("xhci_xfer_data(len=%d, in_xfer=%d, out_xfer=%d, report=%d)\n", - length, in_xfer, out_xfer, report); - - assert(!(in_xfer && out_xfer)); - + xfer->int_req = false; + pci_dma_sglist_init(&xfer->sgl, &xhci->pci_dev, 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)) { fprintf(stderr, "xhci: data direction mismatch for TR_DATA\n"); - xhci_die(xhci); - return transferred; + 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) { + fprintf(stderr, "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; - shortpkt = 1; - } - if (in_xfer || out_xfer) { - if (trb->control & TRB_TR_IDT) { - uint64_t idata; - if (chunk > 8 || in_xfer) { - fprintf(stderr, "xhci: invalid immediate data TRB\n"); - xhci_die(xhci); - return transferred; - } - idata = le64_to_cpu(trb->parameter); - memcpy(data, &idata, chunk); - } else { - DPRINTF("xhci_xfer_data: r/w(%d) %d bytes at " - DMA_ADDR_FMT "\n", in_xfer, chunk, addr); - if (in_xfer) { - pci_dma_write(&xhci->pci_dev, addr, data, chunk); - } else { - pci_dma_read(&xhci->pci_dev, addr, data, chunk); - } -#ifdef DEBUG_DATA - unsigned int count = chunk; - int i; - if (count > 16) { - count = 16; - } - DPRINTF(" ::"); - for (i = 0; i < count; i++) { - DPRINTF(" %02x", data[i]); - } - DPRINTF("\n"); -#endif + if (xfer->status == CC_SUCCESS) { + shortpkt = 1; } } left -= chunk; - data += chunk; edtla += chunk; - transferred += chunk; break; case TR_STATUS: reported = 0; @@ -1189,8 +1587,9 @@ static int xhci_xfer_data(XHCITransfer *xfer, uint8_t *data, break; } - if (report && !reported && (trb->control & TRB_TR_IOC || - (shortpkt && (trb->control & TRB_TR_ISP)))) { + 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; @@ -1208,11 +1607,13 @@ static int xhci_xfer_data(XHCITransfer *xfer, uint8_t *data, DPRINTF("xhci_xfer_data: EDTLA=%d\n", event.length); edtla = 0; } - xhci_event(xhci, &event); + xhci_event(xhci, &event, TRB_INTR(*trb)); reported = 1; + if (xfer->status != CC_SUCCESS) { + return; + } } } - return transferred; } static void xhci_stall_ep(XHCITransfer *xfer) @@ -1220,195 +1621,68 @@ 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; - epctx->ring.dequeue = xfer->trbs[0].addr; - epctx->ring.ccs = xfer->trbs[0].ccs; - xhci_set_ep_state(xhci, epctx, EP_HALTED); - DPRINTF("xhci: stalled slot %d ep %d\n", xfer->slotid, xfer->epid); - DPRINTF("xhci: will continue at "DMA_ADDR_FMT"\n", epctx->ring.dequeue); -} - -static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, - XHCIEPContext *epctx); - -static void xhci_bg_update(XHCIState *xhci, XHCIEPContext *epctx) -{ - if (epctx->bg_updating) { - return; - } - DPRINTF("xhci_bg_update(%p, %p)\n", xhci, epctx); - assert(epctx->has_bg); - DPRINTF("xhci: fg=%d bg=%d\n", epctx->comp_xfer, epctx->next_bg); - epctx->bg_updating = 1; - while (epctx->transfers[epctx->comp_xfer].backgrounded && - epctx->bg_transfers[epctx->next_bg].complete) { - XHCITransfer *fg = &epctx->transfers[epctx->comp_xfer]; - XHCITransfer *bg = &epctx->bg_transfers[epctx->next_bg]; -#if 0 - DPRINTF("xhci: completing fg %d from bg %d.%d (stat: %d)\n", - epctx->comp_xfer, epctx->next_bg, bg->cur_pkt, - bg->usbxfer->iso_packet_desc[bg->cur_pkt].status - ); -#endif - assert(epctx->type == ET_ISO_IN); - assert(bg->iso_xfer); - assert(bg->in_xfer); - uint8_t *p = bg->data + bg->cur_pkt * bg->pktsize; -#if 0 - int len = bg->usbxfer->iso_packet_desc[bg->cur_pkt].actual_length; - fg->status = libusb_to_ccode(bg->usbxfer->iso_packet_desc[bg->cur_pkt].status); -#else - int len = 0; - FIXME(); -#endif - fg->complete = 1; - fg->backgrounded = 0; - - if (fg->status == CC_STALL_ERROR) { - xhci_stall_ep(fg); - } - - xhci_xfer_data(fg, p, len, 1, 0, 1); - - epctx->comp_xfer++; - if (epctx->comp_xfer == TD_QUEUE) { - epctx->comp_xfer = 0; - } - DPRINTF("next fg xfer: %d\n", epctx->comp_xfer); - bg->cur_pkt++; - if (bg->cur_pkt == bg->pkts) { - bg->complete = 0; - if (xhci_submit(xhci, bg, epctx) < 0) { - fprintf(stderr, "xhci: bg resubmit failed\n"); - } - epctx->next_bg++; - if (epctx->next_bg == BG_XFERS) { - epctx->next_bg = 0; - } - DPRINTF("next bg xfer: %d\n", epctx->next_bg); - - xhci_kick_ep(xhci, fg->slotid, fg->epid); - } - } - epctx->bg_updating = 0; -} - -#if 0 -static void xhci_xfer_cb(struct libusb_transfer *transfer) -{ - XHCIState *xhci; - XHCITransfer *xfer; - - xfer = (XHCITransfer *)transfer->user_data; - xhci = xfer->xhci; - - DPRINTF("xhci_xfer_cb(slot=%d, ep=%d, status=%d)\n", xfer->slotid, - xfer->epid, transfer->status); - - assert(xfer->slotid >= 1 && xfer->slotid <= MAXSLOTS); - assert(xfer->epid >= 1 && xfer->epid <= 31); - - if (xfer->cancelled) { - DPRINTF("xhci: transfer cancelled, not reporting anything\n"); - xfer->running = 0; - return; - } - - XHCIEPContext *epctx; - XHCISlot *slot; - slot = &xhci->slots[xfer->slotid-1]; - assert(slot->eps[xfer->epid-1]); - epctx = slot->eps[xfer->epid-1]; - - if (xfer->bg_xfer) { - DPRINTF("xhci: background transfer, updating\n"); - xfer->complete = 1; - xfer->running = 0; - xhci_bg_update(xhci, epctx); - return; - } - - if (xfer->iso_xfer) { - transfer->status = transfer->iso_packet_desc[0].status; - transfer->actual_length = transfer->iso_packet_desc[0].actual_length; - } - - xfer->status = libusb_to_ccode(transfer->status); - - xfer->complete = 1; - xfer->running = 0; - - if (transfer->status == LIBUSB_TRANSFER_STALL) - xhci_stall_ep(xhci, epctx, xfer); - - DPRINTF("xhci: transfer actual length = %d\n", transfer->actual_length); - - if (xfer->in_xfer) { - if (xfer->epid == 1) { - xhci_xfer_data(xhci, xfer, xfer->data + 8, - transfer->actual_length, 1, 0, 1); - } else { - xhci_xfer_data(xhci, xfer, xfer->data, - transfer->actual_length, 1, 0, 1); + 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 { - xhci_xfer_data(xhci, xfer, NULL, transfer->actual_length, 0, 0, 1); + epctx->ring.dequeue = xfer->trbs[0].addr; + epctx->ring.ccs = xfer->trbs[0].ccs; + xhci_set_ep_state(xhci, epctx, NULL, EP_HALTED); } - - xhci_kick_ep(xhci, xfer->slotid, xfer->epid); } -static int xhci_hle_control(XHCIState *xhci, XHCITransfer *xfer, - uint8_t bmRequestType, uint8_t bRequest, - uint16_t wValue, uint16_t wIndex, uint16_t wLength) -{ - uint16_t type_req = (bmRequestType << 8) | bRequest; - - switch (type_req) { - case 0x0000 | USB_REQ_SET_CONFIGURATION: - DPRINTF("xhci: HLE switch configuration\n"); - return xhci_switch_config(xhci, xfer->slotid, wValue) == 0; - case 0x0100 | USB_REQ_SET_INTERFACE: - DPRINTF("xhci: HLE set interface altsetting\n"); - return xhci_set_iface_alt(xhci, xfer->slotid, wIndex, wValue) == 0; - case 0x0200 | USB_REQ_CLEAR_FEATURE: - if (wValue == 0) { // endpoint halt - DPRINTF("xhci: HLE clear halt\n"); - return xhci_clear_halt(xhci, xfer->slotid, wIndex); - } - case 0x0000 | USB_REQ_SET_ADDRESS: - fprintf(stderr, "xhci: warn: illegal SET_ADDRESS request\n"); - return 0; - default: - return 0; - } -} -#endif +static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx); -static int xhci_setup_packet(XHCITransfer *xfer, USBDevice *dev) +static int xhci_setup_packet(XHCITransfer *xfer) { + XHCIState *xhci = xfer->xhci; + USBDevice *dev; USBEndpoint *ep; int dir; dir = xfer->in_xfer ? USB_TOKEN_IN : USB_TOKEN_OUT; - ep = usb_ep_get(dev, dir, xfer->epid >> 1); - usb_packet_setup(&xfer->packet, dir, ep, xfer->trbs[0].addr); - usb_packet_addbuf(&xfer->packet, xfer->data, xfer->data_length); + + if (xfer->packet.ep) { + ep = xfer->packet.ep; + dev = ep->dev; + } else { + if (!xhci->slots[xfer->slotid-1].uport) { + fprintf(stderr, "xhci: slot %d has no device\n", + xfer->slotid); + return -1; + } + dev = xhci->slots[xfer->slotid-1].uport->dev; + ep = usb_ep_get(dev, dir, xfer->epid >> 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, dev->addr, ep->nr); return 0; } -static int xhci_complete_packet(XHCITransfer *xfer, int ret) +static int xhci_complete_packet(XHCITransfer *xfer) { - if (ret == USB_RET_ASYNC) { + if (xfer->packet.status == USB_RET_ASYNC) { trace_usb_xhci_xfer_async(xfer); xfer->running_async = 1; xfer->running_retry = 0; xfer->complete = 0; xfer->cancelled = 0; return 0; - } else if (ret == USB_RET_NAK) { + } else if (xfer->packet.status == USB_RET_NAK) { trace_usb_xhci_xfer_nak(xfer); xfer->running_async = 0; xfer->running_retry = 1; @@ -1419,57 +1693,51 @@ static int xhci_complete_packet(XHCITransfer *xfer, int ret) xfer->running_async = 0; xfer->running_retry = 0; xfer->complete = 1; + xhci_xfer_unmap(xfer); } - if (ret >= 0) { + if (xfer->packet.status == USB_RET_SUCCESS) { + trace_usb_xhci_xfer_success(xfer, xfer->packet.actual_length); xfer->status = CC_SUCCESS; - xhci_xfer_data(xfer, xfer->data, ret, xfer->in_xfer, 0, 1); - trace_usb_xhci_xfer_success(xfer, ret); + xhci_xfer_report(xfer); return 0; } /* error */ - trace_usb_xhci_xfer_error(xfer, ret); - switch (ret) { + trace_usb_xhci_xfer_error(xfer, xfer->packet.status); + switch (xfer->packet.status) { case USB_RET_NODEV: xfer->status = CC_USB_TRANSACTION_ERROR; - xhci_xfer_data(xfer, xfer->data, 0, xfer->in_xfer, 0, 1); + xhci_xfer_report(xfer); xhci_stall_ep(xfer); break; case USB_RET_STALL: xfer->status = CC_STALL_ERROR; - xhci_xfer_data(xfer, xfer->data, 0, xfer->in_xfer, 0, 1); + xhci_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: - fprintf(stderr, "%s: FIXME: ret = %d\n", __FUNCTION__, ret); - FIXME(); + fprintf(stderr, "%s: FIXME: status = %d\n", __func__, + xfer->packet.status); + FIXME("unhandled USB_RET_*"); } return 0; } -static USBDevice *xhci_find_device(XHCIPort *port, uint8_t addr) -{ - if (!(port->portsc & PORTSC_PED)) { - return NULL; - } - return usb_find_device(&port->port, addr); -} - static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer) { XHCITRB *trb_setup, *trb_status; uint8_t bmRequestType; - uint16_t wLength; - XHCIPort *port; - USBDevice *dev; - int ret; trb_setup = &xfer->trbs[0]; trb_status = &xfer->trbs[xfer->trb_count-1]; - trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->epid, - trb_setup->parameter >> 48); + 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) { @@ -1498,93 +1766,87 @@ static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer) } bmRequestType = trb_setup->parameter; - wLength = trb_setup->parameter >> 48; - - if (xfer->data && xfer->data_alloced < wLength) { - xfer->data_alloced = 0; - g_free(xfer->data); - xfer->data = NULL; - } - if (!xfer->data) { - DPRINTF("xhci: alloc %d bytes data\n", wLength); - xfer->data = g_malloc(wLength+1); - xfer->data_alloced = wLength; - } - xfer->data_length = wLength; - - port = &xhci->ports[xhci->slots[xfer->slotid-1].port-1]; - dev = xhci_find_device(port, xhci->slots[xfer->slotid-1].devaddr); - if (!dev) { - fprintf(stderr, "xhci: slot %d port %d has no device\n", xfer->slotid, - xhci->slots[xfer->slotid-1].port); - return -1; - } xfer->in_xfer = bmRequestType & USB_DIR_IN; xfer->iso_xfer = false; - xhci_setup_packet(xfer, dev); - xfer->packet.parameter = trb_setup->parameter; - if (!xfer->in_xfer) { - xhci_xfer_data(xfer, xfer->data, wLength, 0, 1, 0); + if (xhci_setup_packet(xfer) < 0) { + return -1; } + xfer->packet.parameter = trb_setup->parameter; - ret = usb_handle_packet(dev, &xfer->packet); + usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); - xhci_complete_packet(xfer, ret); + xhci_complete_packet(xfer); if (!xfer->running_async && !xfer->running_retry) { - xhci_kick_ep(xhci, xfer->slotid, xfer->epid); + xhci_kick_ep(xhci, xfer->slotid, xfer->epid, 0); } return 0; } -static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx) +static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx, uint64_t mfindex) { - XHCIPort *port; - USBDevice *dev; - int ret; - - DPRINTF("xhci_submit(slotid=%d,epid=%d)\n", xfer->slotid, xfer->epid); - - xfer->in_xfer = epctx->type>>2; - - if (xfer->data && xfer->data_alloced < xfer->data_length) { - xfer->data_alloced = 0; - g_free(xfer->data); - xfer->data = NULL; - } - if (!xfer->data && xfer->data_length) { - DPRINTF("xhci: alloc %d bytes data\n", xfer->data_length); - xfer->data = g_malloc(xfer->data_length); - xfer->data_alloced = xfer->data_length; - } - if (epctx->type == ET_ISO_IN || epctx->type == ET_ISO_OUT) { - if (!xfer->bg_xfer) { - xfer->pkts = 1; + 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->pkts = 0; + xfer->mfindex_kick = (xfer->trbs[0].control >> TRB_TR_FRAMEID_SHIFT) + & TRB_TR_FRAMEID_MASK; + xfer->mfindex_kick |= mfindex & ~0x3fff; + if (xfer->mfindex_kick < mfindex) { + xfer->mfindex_kick += 0x4000; + } } +} - port = &xhci->ports[xhci->slots[xfer->slotid-1].port-1]; - dev = xhci_find_device(port, xhci->slots[xfer->slotid-1].devaddr); - if (!dev) { - fprintf(stderr, "xhci: slot %d port %d has no device\n", xfer->slotid, - xhci->slots[xfer->slotid-1].port); - return -1; +static void xhci_check_iso_kick(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx, uint64_t mfindex) +{ + if (xfer->mfindex_kick > mfindex) { + qemu_mod_timer(epctx->kick_timer, qemu_get_clock_ns(vm_clock) + + (xfer->mfindex_kick - mfindex) * 125000); + xfer->running_retry = 1; + } else { + epctx->mfindex_last = xfer->mfindex_kick; + qemu_del_timer(epctx->kick_timer); + xfer->running_retry = 0; } +} + - xhci_setup_packet(xfer, dev); +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: case ET_BULK_OUT: case ET_BULK_IN: + xfer->pkts = 0; + xfer->iso_xfer = false; break; case ET_ISO_OUT: case ET_ISO_IN: - FIXME(); + xfer->pkts = 1; + xfer->iso_xfer = true; + mfindex = xhci_mfindex_get(xhci); + xhci_calc_iso_kick(xhci, xfer, epctx, mfindex); + xhci_check_iso_kick(xhci, xfer, epctx, mfindex); + if (xfer->running_retry) { + return -1; + } break; default: fprintf(stderr, "xhci: unknown or unhandled EP " @@ -1593,69 +1855,37 @@ static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx return -1; } - if (!xfer->in_xfer) { - xhci_xfer_data(xfer, xfer->data, xfer->data_length, 0, 1, 0); + if (xhci_setup_packet(xfer) < 0) { + return -1; } - ret = usb_handle_packet(dev, &xfer->packet); + usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); - xhci_complete_packet(xfer, ret); + xhci_complete_packet(xfer); if (!xfer->running_async && !xfer->running_retry) { - xhci_kick_ep(xhci, xfer->slotid, xfer->epid); + xhci_kick_ep(xhci, xfer->slotid, xfer->epid, xfer->streamid); } return 0; } static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx) { - int i; - unsigned int length = 0; - XHCITRB *trb; - - for (i = 0; i < xfer->trb_count; i++) { - trb = &xfer->trbs[i]; - if (TRB_TYPE(*trb) == TR_NORMAL || TRB_TYPE(*trb) == TR_ISOCH) { - length += trb->status & 0x1ffff; - } - } - - trace_usb_xhci_xfer_start(xfer, xfer->slotid, xfer->epid, length); - - if (!epctx->has_bg) { - xfer->data_length = length; - xfer->backgrounded = 0; - return xhci_submit(xhci, xfer, epctx); - } else { - if (!epctx->bg_running) { - for (i = 0; i < BG_XFERS; i++) { - XHCITransfer *t = &epctx->bg_transfers[i]; - t->xhci = xhci; - t->epid = xfer->epid; - t->slotid = xfer->slotid; - t->pkts = BG_PKTS; - t->pktsize = epctx->max_psize; - t->data_length = t->pkts * t->pktsize; - t->bg_xfer = 1; - if (xhci_submit(xhci, t, epctx) < 0) { - fprintf(stderr, "xhci: bg submit failed\n"); - return -1; - } - } - epctx->bg_running = 1; - } - xfer->backgrounded = 1; - xhci_bg_update(xhci, epctx); - return 0; - } + 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) +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); - assert(slotid >= 1 && slotid <= MAXSLOTS); + 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) { @@ -1670,18 +1900,34 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, unsigned int epid } if (epctx->retry) { - /* retry nak'ed transfer */ XHCITransfer *xfer = epctx->retry; - int result; trace_usb_xhci_xfer_retry(xfer); assert(xfer->running_retry); - xhci_setup_packet(xfer, xfer->packet.ep->dev); - result = usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); - if (result == USB_RET_NAK) { - return; + if (xfer->iso_xfer) { + /* retry delayed iso transfer */ + mfindex = xhci_mfindex_get(xhci); + xhci_check_iso_kick(xhci, xfer, epctx, mfindex); + if (xfer->running_retry) { + return; + } + 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); } - xhci_complete_packet(xfer, result); assert(!xfer->running_retry); epctx->retry = NULL; } @@ -1691,14 +1937,28 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, unsigned int epid return; } - xhci_set_ep_state(xhci, epctx, EP_RUNNING); + + 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 || xfer->backgrounded) { + if (xfer->running_async || xfer->running_retry) { break; } - length = xhci_ring_chain_length(xhci, &epctx->ring); + length = xhci_ring_chain_length(xhci, ring); if (length < 0) { break; } else if (length == 0) { @@ -1717,23 +1977,28 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, unsigned int epid xfer->trb_count = length; for (i = 0; i < length; i++) { - assert(xhci_ring_fetch(xhci, &epctx->ring, &xfer->trbs[i], NULL)); + assert(xhci_ring_fetch(xhci, ring, &xfer->trbs[i], NULL)); } xfer->xhci = xhci; xfer->epid = epid; xfer->slotid = slotid; + xfer->streamid = streamid; if (epid == 1) { if (xhci_fire_ctl_transfer(xhci, xfer) >= 0) { epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; + ep = xfer->packet.ep; } else { fprintf(stderr, "xhci: error firing CTL transfer\n"); } } else { if (xhci_fire_transfer(xhci, xfer, epctx) >= 0) { epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; + ep = xfer->packet.ep; } else { - fprintf(stderr, "xhci: error firing data transfer\n"); + if (!xfer->iso_xfer) { + fprintf(stderr, "xhci: error firing data transfer\n"); + } } } @@ -1746,14 +2011,17 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, unsigned int epid break; } } + 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 <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); xhci->slots[slotid-1].enabled = 1; - xhci->slots[slotid-1].port = 0; + xhci->slots[slotid-1].uport = NULL; memset(xhci->slots[slotid-1].eps, 0, sizeof(XHCIEPContext*)*31); return CC_SUCCESS; @@ -1764,7 +2032,7 @@ static TRBCCode xhci_disable_slot(XHCIState *xhci, unsigned int slotid) int i; trace_usb_xhci_slot_disable(slotid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); for (i = 1; i <= 31; i++) { if (xhci->slots[slotid-1].eps[i-1]) { @@ -1776,32 +2044,57 @@ static TRBCCode xhci_disable_slot(XHCIState *xhci, unsigned int slotid) 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; + 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]; - unsigned int port; int i; TRBCCode res; trace_usb_xhci_slot_address(slotid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); dcbaap = xhci_addr64(xhci->dcbaap_low, xhci->dcbaap_high); - pci_dma_read(&xhci->pci_dev, dcbaap + 8*slotid, &poctx, sizeof(poctx)); + poctx = ldq_le_pci_dma(&xhci->pci_dev, dcbaap + 8*slotid); ictx = xhci_mask64(pictx); - octx = xhci_mask64(le64_to_cpu(poctx)); + octx = xhci_mask64(poctx); DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx); DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx); - pci_dma_read(&xhci->pci_dev, ictx, ictl_ctx, sizeof(ictl_ctx)); + xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx)); if (ictl_ctx[0] != 0x0 || ictl_ctx[1] != 0x3) { fprintf(stderr, "xhci: invalid input context control %08x %08x\n", @@ -1809,8 +2102,8 @@ static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid, return CC_TRB_ERROR; } - pci_dma_read(&xhci->pci_dev, ictx+32, slot_ctx, sizeof(slot_ctx)); - pci_dma_read(&xhci->pci_dev, ictx+64, ep0_ctx, sizeof(ep0_ctx)); + 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]); @@ -1818,38 +2111,50 @@ static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid, DPRINTF("xhci: input ep0 context: %08x %08x %08x %08x %08x\n", ep0_ctx[0], ep0_ctx[1], ep0_ctx[2], ep0_ctx[3], ep0_ctx[4]); - port = (slot_ctx[1]>>16) & 0xFF; - dev = xhci->ports[port-1].port.dev; - - if (port < 1 || port > MAXPORTS) { - fprintf(stderr, "xhci: bad port %d\n", port); + uport = xhci_lookup_uport(xhci, slot_ctx); + if (uport == NULL) { + fprintf(stderr, "xhci: port not found\n"); return CC_TRB_ERROR; - } else if (!dev) { - fprintf(stderr, "xhci: port %d not connected\n", port); + } + + dev = uport->dev; + if (!dev) { + fprintf(stderr, "xhci: port %s not connected\n", uport->path); return CC_USB_TRANSACTION_ERROR; } - for (i = 0; i < MAXSLOTS; i++) { - if (xhci->slots[i].port == port) { - fprintf(stderr, "xhci: port %d already assigned to slot %d\n", - port, i+1); + for (i = 0; i < xhci->numslots; i++) { + if (i == slotid-1) { + continue; + } + if (xhci->slots[i].uport == uport) { + fprintf(stderr, "xhci: port %s already assigned to slot %d\n", + uport->path, i+1); return CC_TRB_ERROR; } } slot = &xhci->slots[slotid-1]; - slot->port = port; + slot->uport = uport; slot->ctx = octx; if (bsr) { slot_ctx[3] = SLOT_DEFAULT << SLOT_STATE_SHIFT; } else { - slot->devaddr = xhci->devaddr++; - slot_ctx[3] = (SLOT_ADDRESSED << SLOT_STATE_SHIFT) | slot->devaddr; - DPRINTF("xhci: device address is %d\n", slot->devaddr); - usb_device_handle_control(dev, NULL, + 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, - slot->devaddr, 0, 0, NULL); + slotid, 0, 0, NULL); + assert(p.status != USB_RET_ASYNC); } res = xhci_enable_ep(xhci, slotid, 1, octx+32, ep0_ctx); @@ -1859,8 +2164,8 @@ static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid, 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]); - pci_dma_write(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); - pci_dma_write(&xhci->pci_dev, octx+32, ep0_ctx, sizeof(ep0_ctx)); + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); + xhci_dma_write_u32s(xhci, octx+32, ep0_ctx, sizeof(ep0_ctx)); return res; } @@ -1878,7 +2183,7 @@ static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, TRBCCode res; trace_usb_xhci_slot_configure(slotid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); ictx = xhci_mask64(pictx); octx = xhci->slots[slotid-1].ctx; @@ -1893,17 +2198,17 @@ static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, } } - pci_dma_read(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + 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]); - pci_dma_write(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); return CC_SUCCESS; } - pci_dma_read(&xhci->pci_dev, ictx, ictl_ctx, sizeof(ictl_ctx)); + xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx)); if ((ictl_ctx[0] & 0x3) != 0x0 || (ictl_ctx[1] & 0x3) != 0x1) { fprintf(stderr, "xhci: invalid input context control %08x %08x\n", @@ -1911,8 +2216,8 @@ static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, return CC_TRB_ERROR; } - pci_dma_read(&xhci->pci_dev, ictx+32, islot_ctx, sizeof(islot_ctx)); - pci_dma_read(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + 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) { fprintf(stderr, "xhci: invalid slot state %08x\n", slot_ctx[3]); @@ -1924,8 +2229,7 @@ static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, xhci_disable_ep(xhci, slotid, i); } if (ictl_ctx[1] & (1<<i)) { - pci_dma_read(&xhci->pci_dev, ictx+32+(32*i), ep_ctx, - sizeof(ep_ctx)); + 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]); @@ -1937,7 +2241,7 @@ static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, 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]); - pci_dma_write(&xhci->pci_dev, octx+(32*i), ep_ctx, sizeof(ep_ctx)); + xhci_dma_write_u32s(xhci, octx+(32*i), ep_ctx, sizeof(ep_ctx)); } } @@ -1949,7 +2253,7 @@ static TRBCCode xhci_configure_slot(XHCIState *xhci, unsigned int slotid, DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); - pci_dma_write(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); return CC_SUCCESS; } @@ -1966,7 +2270,7 @@ static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid, uint32_t slot_ctx[4]; trace_usb_xhci_slot_evaluate(slotid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); ictx = xhci_mask64(pictx); octx = xhci->slots[slotid-1].ctx; @@ -1974,7 +2278,7 @@ static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid, DPRINTF("xhci: input context at "DMA_ADDR_FMT"\n", ictx); DPRINTF("xhci: output context at "DMA_ADDR_FMT"\n", octx); - pci_dma_read(&xhci->pci_dev, ictx, ictl_ctx, sizeof(ictl_ctx)); + xhci_dma_read_u32s(xhci, ictx, ictl_ctx, sizeof(ictl_ctx)); if (ictl_ctx[0] != 0x0 || ictl_ctx[1] & ~0x3) { fprintf(stderr, "xhci: invalid input context control %08x %08x\n", @@ -1983,12 +2287,12 @@ static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid, } if (ictl_ctx[1] & 0x1) { - pci_dma_read(&xhci->pci_dev, ictx+32, islot_ctx, sizeof(islot_ctx)); + 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]); - pci_dma_read(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + 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; @@ -1998,17 +2302,17 @@ static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid, DPRINTF("xhci: output slot context: %08x %08x %08x %08x\n", slot_ctx[0], slot_ctx[1], slot_ctx[2], slot_ctx[3]); - pci_dma_write(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); } if (ictl_ctx[1] & 0x2) { - pci_dma_read(&xhci->pci_dev, ictx+64, iep0_ctx, sizeof(iep0_ctx)); + 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]); - pci_dma_read(&xhci->pci_dev, octx+32, ep0_ctx, sizeof(ep0_ctx)); + 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; @@ -2016,7 +2320,7 @@ static TRBCCode xhci_evaluate_slot(XHCIState *xhci, unsigned int slotid, 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]); - pci_dma_write(&xhci->pci_dev, octx+32, ep0_ctx, sizeof(ep0_ctx)); + xhci_dma_write_u32s(xhci, octx+32, ep0_ctx, sizeof(ep0_ctx)); } return CC_SUCCESS; @@ -2029,7 +2333,7 @@ static TRBCCode xhci_reset_slot(XHCIState *xhci, unsigned int slotid) int i; trace_usb_xhci_slot_reset(slotid); - assert(slotid >= 1 && slotid <= MAXSLOTS); + assert(slotid >= 1 && slotid <= xhci->numslots); octx = xhci->slots[slotid-1].ctx; @@ -2041,12 +2345,12 @@ static TRBCCode xhci_reset_slot(XHCIState *xhci, unsigned int slotid) } } - pci_dma_read(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + 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]); - pci_dma_write(&xhci->pci_dev, octx, slot_ctx, sizeof(slot_ctx)); + xhci_dma_write_u32s(xhci, octx, slot_ctx, sizeof(slot_ctx)); return CC_SUCCESS; } @@ -2055,7 +2359,7 @@ static unsigned int xhci_get_slot(XHCIState *xhci, XHCIEvent *event, XHCITRB *tr { unsigned int slotid; slotid = (trb->control >> TRB_CR_SLOTID_SHIFT) & TRB_CR_SLOTID_MASK; - if (slotid < 1 || slotid > MAXSLOTS) { + if (slotid < 1 || slotid > xhci->numslots) { fprintf(stderr, "xhci: bad slot id %d\n", slotid); event->ccode = CC_TRB_ERROR; return 0; @@ -2067,10 +2371,32 @@ static unsigned int xhci_get_slot(XHCIState *xhci, XHCIEvent *event, XHCITRB *tr 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); + } + } + xhci->slots[slot].uport = NULL; +} + static TRBCCode xhci_get_port_bandwidth(XHCIState *xhci, uint64_t pctx) { dma_addr_t ctx; - uint8_t bw_ctx[MAXPORTS+1]; + uint8_t bw_ctx[xhci->numports+1]; DPRINTF("xhci_get_port_bandwidth()\n"); @@ -2080,7 +2406,7 @@ static TRBCCode xhci_get_port_bandwidth(XHCIState *xhci, uint64_t pctx) /* TODO: actually implement real values here */ bw_ctx[0] = 0; - memset(&bw_ctx[1], 80, MAXPORTS); /* 80% */ + memset(&bw_ctx[1], 80, xhci->numports); /* 80% */ pci_dma_write(&xhci->pci_dev, ctx, bw_ctx, sizeof(bw_ctx)); return CC_SUCCESS; @@ -2147,12 +2473,12 @@ static void xhci_process_commands(XHCIState *xhci) event.ptr = addr; switch (type) { case CR_ENABLE_SLOT: - for (i = 0; i < MAXSLOTS; i++) { + for (i = 0; i < xhci->numslots; i++) { if (!xhci->slots[i].enabled) { break; } } - if (i >= MAXSLOTS) { + if (i >= xhci->numslots) { fprintf(stderr, "xhci: no device slots available\n"); event.ccode = CC_NO_SLOTS_ERROR; } else { @@ -2207,7 +2533,9 @@ static void xhci_process_commands(XHCIState *xhci) if (slotid) { unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT) & TRB_CR_EPID_MASK; - event.ccode = xhci_set_ep_dequeue(xhci, slotid, epid, + unsigned int streamid = (trb.status >> 16) & 0xffff; + event.ccode = xhci_set_ep_dequeue(xhci, slotid, + epid, streamid, trb.parameter); } break; @@ -2239,41 +2567,96 @@ static void xhci_process_commands(XHCIState *xhci) } break; default: - fprintf(stderr, "xhci: unimplemented command %d\n", type); + trace_usb_xhci_unimplemented("command", type); event.ccode = CC_TRB_ERROR; break; } event.slotid = slotid; - xhci_event(xhci, &event); + xhci_event(xhci, &event, 0); } } -static void xhci_update_port(XHCIState *xhci, XHCIPort *port, int is_detach) +static bool xhci_port_have_device(XHCIPort *port) { - int nr = port->port.index + 1; + 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 (port->port.dev && port->port.dev->attached && !is_detach) { + if (!is_detach && xhci_port_have_device(port)) { port->portsc |= PORTSC_CCS; - switch (port->port.dev->speed) { + 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) +{ + trace_usb_xhci_port_reset(port->portnr); - if (xhci_running(xhci)) { - port->portsc |= PORTSC_CSC; - XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS, nr << 24}; - xhci_event(xhci, &ev); - DPRINTF("xhci: port change event for port %d\n", nr); + if (!xhci_port_have_device(port)) { + return; } + + usb_device_reset(port->uport->dev); + + switch (port->uport->dev->speed) { + 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) @@ -2294,34 +2677,39 @@ static void xhci_reset(DeviceState *dev) xhci->dcbaap_low = 0; xhci->dcbaap_high = 0; xhci->config = 0; - xhci->devaddr = 2; - for (i = 0; i < MAXSLOTS; i++) { + for (i = 0; i < xhci->numslots; i++) { xhci_disable_slot(xhci, i+1); } - for (i = 0; i < MAXPORTS; i++) { - xhci_update_port(xhci, xhci->ports + i, 0); + for (i = 0; i < xhci->numports; i++) { + xhci_port_update(xhci->ports + i, 0); } - xhci->mfindex = 0; - xhci->iman = 0; - xhci->imod = 0; - xhci->erstsz = 0; - xhci->erstba_low = 0; - xhci->erstba_high = 0; - xhci->erdp_low = 0; - xhci->erdp_high = 0; + 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->er_ep_idx = 0; - xhci->er_pcs = 1; - xhci->er_full = 0; - xhci->ev_buffer_put = 0; - xhci->ev_buffer_get = 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_get_clock_ns(vm_clock); + xhci_mfwrap_update(xhci); } -static uint32_t xhci_cap_read(XHCIState *xhci, uint32_t reg) +static uint64_t xhci_cap_read(void *ptr, hwaddr reg, unsigned size) { + XHCIState *xhci = ptr; uint32_t ret; switch (reg) { @@ -2329,7 +2717,8 @@ static uint32_t xhci_cap_read(XHCIState *xhci, uint32_t reg) ret = 0x01000000 | LEN_CAP; break; case 0x04: /* HCSPARAMS 1 */ - ret = (MAXPORTS<<24) | (MAXINTRS<<8) | MAXSLOTS; + ret = ((xhci->numports_2+xhci->numports_3)<<24) + | (xhci->numintrs<<8) | xhci->numslots; break; case 0x08: /* HCSPARAMS 2 */ ret = 0x0000000f; @@ -2339,9 +2728,9 @@ static uint32_t xhci_cap_read(XHCIState *xhci, uint32_t reg) break; case 0x10: /* HCCPARAMS */ if (sizeof(dma_addr_t) == 4) { - ret = 0x00081000; + ret = 0x00087000; } else { - ret = 0x00081001; + ret = 0x00087001; } break; case 0x14: /* DBOFF */ @@ -2356,10 +2745,10 @@ static uint32_t xhci_cap_read(XHCIState *xhci, uint32_t reg) ret = 0x02000402; /* USB 2.0 */ break; case 0x24: /* Supported Protocol:04 */ - ret = 0x20425455; /* "USB " */ + ret = 0x20425355; /* "USB " */ break; case 0x28: /* Supported Protocol:08 */ - ret = 0x00000001 | (USB2_PORTS<<8); + ret = 0x00000001 | (xhci->numports_2<<8); break; case 0x2c: /* Supported Protocol:0c */ ret = 0x00000000; /* reserved */ @@ -2368,16 +2757,16 @@ static uint32_t xhci_cap_read(XHCIState *xhci, uint32_t reg) ret = 0x03000002; /* USB 3.0 */ break; case 0x34: /* Supported Protocol:04 */ - ret = 0x20425455; /* "USB " */ + ret = 0x20425355; /* "USB " */ break; case 0x38: /* Supported Protocol:08 */ - ret = 0x00000000 | (USB2_PORTS+1) | (USB3_PORTS<<8); + ret = 0x00000000 | (xhci->numports_2+1) | (xhci->numports_3<<8); break; case 0x3c: /* Supported Protocol:0c */ ret = 0x00000000; /* reserved */ break; default: - fprintf(stderr, "xhci_cap_read: reg %d unimplemented\n", reg); + trace_usb_xhci_unimplemented("cap read", reg); ret = 0; } @@ -2385,20 +2774,14 @@ static uint32_t xhci_cap_read(XHCIState *xhci, uint32_t reg) return ret; } -static uint32_t xhci_port_read(XHCIState *xhci, uint32_t reg) +static uint64_t xhci_port_read(void *ptr, hwaddr reg, unsigned size) { - uint32_t port = reg >> 4; + XHCIPort *port = ptr; uint32_t ret; - if (port >= MAXPORTS) { - fprintf(stderr, "xhci_port_read: port %d out of bounds\n", port); - ret = 0; - goto out; - } - - switch (reg & 0xf) { + switch (reg) { case 0x00: /* PORTSC */ - ret = xhci->ports[port].portsc; + ret = port->portsc; break; case 0x04: /* PORTPMSC */ case 0x08: /* PORTLI */ @@ -2406,66 +2789,82 @@ static uint32_t xhci_port_read(XHCIState *xhci, uint32_t reg) break; case 0x0c: /* reserved */ default: - fprintf(stderr, "xhci_port_read (port %d): reg 0x%x unimplemented\n", - port, reg); + trace_usb_xhci_unimplemented("port read", reg); ret = 0; } -out: - trace_usb_xhci_port_read(port, reg & 0x0f, ret); + trace_usb_xhci_port_read(port->portnr, reg, ret); return ret; } -static void xhci_port_write(XHCIState *xhci, uint32_t reg, uint32_t val) +static void xhci_port_write(void *ptr, hwaddr reg, + uint64_t val, unsigned size) { - uint32_t port = reg >> 4; - uint32_t portsc; + XHCIPort *port = ptr; + uint32_t portsc, notify; - trace_usb_xhci_port_write(port, reg & 0x0f, val); + trace_usb_xhci_port_write(port->portnr, reg, val); - if (port >= MAXPORTS) { - fprintf(stderr, "xhci_port_read: port %d out of bounds\n", port); - return; - } - - switch (reg & 0xf) { + switch (reg) { case 0x00: /* PORTSC */ - portsc = xhci->ports[port].portsc; + /* write-1-to-start bits */ + if (val & PORTSC_PR) { + xhci_port_reset(port); + 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 */ - portsc &= ~(PORTSC_PLS_MASK << PORTSC_PLS_SHIFT); - portsc |= val & (PORTSC_PLS_MASK << PORTSC_PLS_SHIFT); + 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: + fprintf(stderr, "%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)); - /* write-1-to-start bits */ - if (val & PORTSC_PR) { - DPRINTF("xhci: port %d reset\n", port); - usb_device_reset(xhci->ports[port].port.dev); - portsc |= PORTSC_PRC | PORTSC_PED; + port->portsc = portsc; + if (notify) { + xhci_port_notify(port, notify); } - xhci->ports[port].portsc = portsc; break; case 0x04: /* PORTPMSC */ case 0x08: /* PORTLI */ default: - fprintf(stderr, "xhci_port_write (port %d): reg 0x%x unimplemented\n", - port, reg); + trace_usb_xhci_unimplemented("port write", reg); } } -static uint32_t xhci_oper_read(XHCIState *xhci, uint32_t reg) +static uint64_t xhci_oper_read(void *ptr, hwaddr reg, unsigned size) { + XHCIState *xhci = ptr; uint32_t ret; - if (reg >= 0x400) { - return xhci_port_read(xhci, reg - 0x400); - } - switch (reg) { case 0x00: /* USBCMD */ ret = xhci->usbcmd; @@ -2495,7 +2894,7 @@ static uint32_t xhci_oper_read(XHCIState *xhci, uint32_t reg) ret = xhci->config; break; default: - fprintf(stderr, "xhci_oper_read: reg 0x%x unimplemented\n", reg); + trace_usb_xhci_unimplemented("oper read", reg); ret = 0; } @@ -2503,12 +2902,10 @@ static uint32_t xhci_oper_read(XHCIState *xhci, uint32_t reg) return ret; } -static void xhci_oper_write(XHCIState *xhci, uint32_t reg, uint32_t val) +static void xhci_oper_write(void *ptr, hwaddr reg, + uint64_t val, unsigned size) { - if (reg >= 0x400) { - xhci_port_write(xhci, reg - 0x400, val); - return; - } + XHCIState *xhci = ptr; trace_usb_xhci_oper_write(reg, val); @@ -2520,16 +2917,17 @@ static void xhci_oper_write(XHCIState *xhci, uint32_t reg, uint32_t val) xhci_stop(xhci); } xhci->usbcmd = val & 0xc0f; + xhci_mfwrap_update(xhci); if (val & USBCMD_HCRST) { xhci_reset(&xhci->pci_dev.qdev); } - xhci_irq_update(xhci); + 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_irq_update(xhci); + xhci_intx_update(xhci); break; case 0x14: /* DNCTRL */ @@ -2543,7 +2941,7 @@ static void xhci_oper_write(XHCIState *xhci, uint32_t reg, uint32_t 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); + 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); @@ -2561,101 +2959,126 @@ static void xhci_oper_write(XHCIState *xhci, uint32_t reg, uint32_t val) xhci->config = val & 0xff; break; default: - fprintf(stderr, "xhci_oper_write: reg 0x%x unimplemented\n", reg); + trace_usb_xhci_unimplemented("oper write", reg); } } -static uint32_t xhci_runtime_read(XHCIState *xhci, uint32_t reg) +static uint64_t xhci_runtime_read(void *ptr, hwaddr reg, + unsigned size) { - uint32_t ret; + XHCIState *xhci = ptr; + uint32_t ret = 0; - switch (reg) { - case 0x00: /* MFINDEX */ - fprintf(stderr, "xhci_runtime_read: MFINDEX not yet implemented\n"); - ret = xhci->mfindex; - break; - case 0x20: /* IMAN */ - ret = xhci->iman; - break; - case 0x24: /* IMOD */ - ret = xhci->imod; - break; - case 0x28: /* ERSTSZ */ - ret = xhci->erstsz; - break; - case 0x30: /* ERSTBA low */ - ret = xhci->erstba_low; - break; - case 0x34: /* ERSTBA high */ - ret = xhci->erstba_high; - break; - case 0x38: /* ERDP low */ - ret = xhci->erdp_low; - break; - case 0x3c: /* ERDP high */ - ret = xhci->erdp_high; - break; - default: - fprintf(stderr, "xhci_runtime_read: reg 0x%x unimplemented\n", reg); - 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(XHCIState *xhci, uint32_t reg, uint32_t val) +static void xhci_runtime_write(void *ptr, hwaddr reg, + uint64_t val, unsigned size) { - trace_usb_xhci_runtime_read(reg, val); + XHCIState *xhci = ptr; + int v = (reg - 0x20) / 0x20; + XHCIInterrupter *intr = &xhci->intr[v]; + trace_usb_xhci_runtime_write(reg, val); - switch (reg) { - case 0x20: /* IMAN */ + if (reg < 0x20) { + trace_usb_xhci_unimplemented("runtime write", reg); + return; + } + + switch (reg & 0x1f) { + case 0x00: /* IMAN */ if (val & IMAN_IP) { - xhci->iman &= ~IMAN_IP; + intr->iman &= ~IMAN_IP; + } + intr->iman &= ~IMAN_IE; + intr->iman |= val & IMAN_IE; + if (v == 0) { + xhci_intx_update(xhci); } - xhci->iman &= ~IMAN_IE; - xhci->iman |= val & IMAN_IE; - xhci_irq_update(xhci); + xhci_msix_update(xhci, v); break; - case 0x24: /* IMOD */ - xhci->imod = val; + case 0x04: /* IMOD */ + intr->imod = val; break; - case 0x28: /* ERSTSZ */ - xhci->erstsz = val & 0xffff; + case 0x08: /* ERSTSZ */ + intr->erstsz = val & 0xffff; break; - case 0x30: /* ERSTBA low */ + case 0x10: /* ERSTBA low */ /* XXX NEC driver bug: it doesn't align this to 64 bytes - xhci->erstba_low = val & 0xffffffc0; */ - xhci->erstba_low = val & 0xfffffff0; + intr->erstba_low = val & 0xffffffc0; */ + intr->erstba_low = val & 0xfffffff0; break; - case 0x34: /* ERSTBA high */ - xhci->erstba_high = val; - xhci_er_reset(xhci); + case 0x14: /* ERSTBA high */ + intr->erstba_high = val; + xhci_er_reset(xhci, v); break; - case 0x38: /* ERDP low */ + case 0x18: /* ERDP low */ if (val & ERDP_EHB) { - xhci->erdp_low &= ~ERDP_EHB; + intr->erdp_low &= ~ERDP_EHB; } - xhci->erdp_low = (val & ~ERDP_EHB) | (xhci->erdp_low & ERDP_EHB); + intr->erdp_low = (val & ~ERDP_EHB) | (intr->erdp_low & ERDP_EHB); break; - case 0x3c: /* ERDP high */ - xhci->erdp_high = val; - xhci_events_update(xhci); + case 0x1c: /* ERDP high */ + intr->erdp_high = val; + xhci_events_update(xhci, v); break; default: - fprintf(stderr, "xhci_oper_write: reg 0x%x unimplemented\n", reg); + trace_usb_xhci_unimplemented("oper write", reg); } } -static uint32_t xhci_doorbell_read(XHCIState *xhci, uint32_t 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(XHCIState *xhci, uint32_t reg, uint32_t val) +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)) { @@ -2669,69 +3092,66 @@ static void xhci_doorbell_write(XHCIState *xhci, uint32_t reg, uint32_t val) if (val == 0) { xhci_process_commands(xhci); } else { - fprintf(stderr, "xhci: bad doorbell 0 write: 0x%x\n", val); + fprintf(stderr, "xhci: bad doorbell 0 write: 0x%x\n", + (uint32_t)val); } } else { - if (reg > MAXSLOTS) { - fprintf(stderr, "xhci: bad doorbell %d\n", reg); - } else if (val > 31) { - fprintf(stderr, "xhci: bad doorbell %d write: 0x%x\n", reg, val); + epid = val & 0xff; + streamid = (val >> 16) & 0xffff; + if (reg > xhci->numslots) { + fprintf(stderr, "xhci: bad doorbell %d\n", (int)reg); + } else if (epid > 31) { + fprintf(stderr, "xhci: bad doorbell %d write: 0x%x\n", + (int)reg, (uint32_t)val); } else { - xhci_kick_ep(xhci, reg, val); + xhci_kick_ep(xhci, reg, epid, streamid); } } } -static uint64_t xhci_mem_read(void *ptr, target_phys_addr_t addr, - unsigned size) +static void xhci_cap_write(void *opaque, hwaddr addr, uint64_t val, + unsigned width) { - XHCIState *xhci = ptr; - - /* Only aligned reads are allowed on xHCI */ - if (addr & 3) { - fprintf(stderr, "xhci_mem_read: Mis-aligned read\n"); - return 0; - } - - if (addr < LEN_CAP) { - return xhci_cap_read(xhci, addr); - } else if (addr >= OFF_OPER && addr < (OFF_OPER + LEN_OPER)) { - return xhci_oper_read(xhci, addr - OFF_OPER); - } else if (addr >= OFF_RUNTIME && addr < (OFF_RUNTIME + LEN_RUNTIME)) { - return xhci_runtime_read(xhci, addr - OFF_RUNTIME); - } else if (addr >= OFF_DOORBELL && addr < (OFF_DOORBELL + LEN_DOORBELL)) { - return xhci_doorbell_read(xhci, addr - OFF_DOORBELL); - } else { - fprintf(stderr, "xhci_mem_read: Bad offset %x\n", (int)addr); - return 0; - } + /* nothing */ } -static void xhci_mem_write(void *ptr, target_phys_addr_t addr, - uint64_t val, unsigned size) -{ - XHCIState *xhci = ptr; +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, +}; - /* Only aligned writes are allowed on xHCI */ - if (addr & 3) { - fprintf(stderr, "xhci_mem_write: Mis-aligned write\n"); - return; - } +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, +}; - if (addr >= OFF_OPER && addr < (OFF_OPER + LEN_OPER)) { - xhci_oper_write(xhci, addr - OFF_OPER, val); - } else if (addr >= OFF_RUNTIME && addr < (OFF_RUNTIME + LEN_RUNTIME)) { - xhci_runtime_write(xhci, addr - OFF_RUNTIME, val); - } else if (addr >= OFF_DOORBELL && addr < (OFF_DOORBELL + LEN_DOORBELL)) { - xhci_doorbell_write(xhci, addr - OFF_DOORBELL, val); - } else { - fprintf(stderr, "xhci_mem_write: Bad offset %x\n", (int)addr); - } -} +static const MemoryRegionOps xhci_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_mem_ops = { - .read = xhci_mem_read, - .write = xhci_mem_write, +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, @@ -2740,53 +3160,53 @@ static const MemoryRegionOps xhci_mem_ops = { static void xhci_attach(USBPort *usbport) { XHCIState *xhci = usbport->opaque; - XHCIPort *port = &xhci->ports[usbport->index]; + XHCIPort *port = xhci_lookup_port(xhci, usbport); - xhci_update_port(xhci, port, 0); + xhci_port_update(port, 0); } static void xhci_detach(USBPort *usbport) { XHCIState *xhci = usbport->opaque; - XHCIPort *port = &xhci->ports[usbport->index]; + XHCIPort *port = xhci_lookup_port(xhci, usbport); - xhci_update_port(xhci, port, 1); + xhci_detach_slot(xhci, usbport); + xhci_port_update(port, 1); } static void xhci_wakeup(USBPort *usbport) { XHCIState *xhci = usbport->opaque; - XHCIPort *port = &xhci->ports[usbport->index]; - int nr = port->port.index + 1; - XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS, nr << 24}; - uint32_t pls; + XHCIPort *port = xhci_lookup_port(xhci, usbport); - pls = (port->portsc >> PORTSC_PLS_SHIFT) & PORTSC_PLS_MASK; - if (pls != 3) { - return; - } - port->portsc |= 0xf << PORTSC_PLS_SHIFT; - if (port->portsc & PORTSC_PLC) { + if (get_field(port->portsc, PORTSC_PLS) != PLS_U3) { return; } - port->portsc |= PORTSC_PLC; - xhci_event(xhci, &ev); + 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); - xhci_complete_packet(xfer, packet->result); - xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid); + if (packet->status == USB_RET_REMOVE_FROM_QUEUE) { + xhci_ep_nuke_one_xfer(xfer); + return; + } + xhci_complete_packet(xfer); + xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid, xfer->streamid); } -static void xhci_child_detach(USBPort *port, USBDevice *child) +static void xhci_child_detach(USBPort *uport, USBDevice *child) { - FIXME(); + USBBus *bus = usb_bus_from_device(child); + XHCIState *xhci = container_of(bus, XHCIState, bus); + + xhci_detach_slot(xhci, uport); } -static USBPortOps xhci_port_ops = { +static USBPortOps xhci_uport_ops = { .attach = xhci_attach, .detach = xhci_detach, .wakeup = xhci_wakeup, @@ -2794,20 +3214,6 @@ static USBPortOps xhci_port_ops = { .child_detach = xhci_child_detach, }; -static int xhci_find_slotid(XHCIState *xhci, USBDevice *dev) -{ - XHCISlot *slot; - int slotid; - - for (slotid = 1; slotid <= MAXSLOTS; slotid++) { - slot = &xhci->slots[slotid-1]; - if (slot->devaddr == dev->addr) { - return slotid; - } - } - return 0; -} - static int xhci_find_epid(USBEndpoint *ep) { if (ep->nr == 0) { @@ -2820,18 +3226,19 @@ static int xhci_find_epid(USBEndpoint *ep) } } -static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep) +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 = xhci_find_slotid(xhci, ep->dev); + 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)); + xhci_kick_ep(xhci, slotid, xhci_find_epid(ep), stream); } static USBBusOps xhci_bus_ops = { @@ -2840,28 +3247,51 @@ static USBBusOps xhci_bus_ops = { static void usb_xhci_init(XHCIState *xhci, DeviceState *dev) { - int i; + 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, &xhci_bus_ops, &xhci->pci_dev.qdev); - for (i = 0; i < MAXPORTS; i++) { - memset(&xhci->ports[i], 0, sizeof(xhci->ports[i])); - usb_register_port(&xhci->bus, &xhci->ports[i].port, xhci, i, - &xhci_port_ops, - USB_SPEED_MASK_LOW | - USB_SPEED_MASK_FULL | - USB_SPEED_MASK_HIGH); - } - for (i = 0; i < MAXSLOTS; i++) { - xhci->slots[i].enabled = 0; + for (i = 0; i < usbports; i++) { + speedmask = 0; + if (i < xhci->numports_2) { + 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) { + 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 int usb_xhci_initfn(struct PCIDevice *dev) { - int ret; + int i, ret; XHCIState *xhci = DO_UPCAST(XHCIState, pci_dev, dev); @@ -2872,43 +3302,82 @@ static int usb_xhci_initfn(struct PCIDevice *dev) usb_xhci_init(xhci, &dev->qdev); + 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; + } + + xhci->mfwrap_timer = qemu_new_timer_ns(vm_clock, xhci_mfwrap_timer, xhci); + xhci->irq = xhci->pci_dev.irq[0]; - memory_region_init_io(&xhci->mem, &xhci_mem_ops, xhci, - "xhci", LEN_REGS); + memory_region_init(&xhci->mem, "xhci", LEN_REGS); + memory_region_init_io(&xhci->mem_cap, &xhci_cap_ops, xhci, + "capabilities", LEN_CAP); + memory_region_init_io(&xhci->mem_oper, &xhci_oper_ops, xhci, + "operational", 0x400); + memory_region_init_io(&xhci->mem_runtime, &xhci_runtime_ops, xhci, + "runtime", LEN_RUNTIME); + memory_region_init_io(&xhci->mem_doorbell, &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, &xhci_port_ops, port, + port->name, 0x10); + memory_region_add_subregion(&xhci->mem, offset, &port->mem); + } + pci_register_bar(&xhci->pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64, &xhci->mem); - ret = pcie_cap_init(&xhci->pci_dev, 0xa0, PCI_EXP_TYPE_ENDPOINT, 0); + ret = pcie_endpoint_cap_init(&xhci->pci_dev, 0xa0); assert(ret >= 0); - if (xhci->msi) { - ret = msi_init(&xhci->pci_dev, 0x70, 1, true, false); - assert(ret >= 0); + if (xhci->flags & (1 << XHCI_FLAG_USE_MSI)) { + msi_init(&xhci->pci_dev, 0x70, xhci->numintrs, true, false); + } + if (xhci->flags & (1 << XHCI_FLAG_USE_MSI_X)) { + msix_init(&xhci->pci_dev, xhci->numintrs, + &xhci->mem, 0, OFF_MSIX_TABLE, + &xhci->mem, 0, OFF_MSIX_PBA, + 0x90); } return 0; } -static void xhci_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, - int len) -{ - XHCIState *xhci = DO_UPCAST(XHCIState, pci_dev, dev); - - pci_default_write_config(dev, addr, val, len); - if (xhci->msi) { - msi_write_config(dev, addr, val, len); - } -} - static const VMStateDescription vmstate_xhci = { .name = "xhci", .unmigratable = 1, }; static Property xhci_properties[] = { - DEFINE_PROP_UINT32("msi", XHCIState, msi, 0), + DEFINE_PROP_BIT("msi", XHCIState, flags, XHCI_FLAG_USE_MSI, true), + DEFINE_PROP_BIT("msix", XHCIState, flags, XHCI_FLAG_USE_MSI_X, 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(), }; @@ -2926,10 +3395,10 @@ static void xhci_class_init(ObjectClass *klass, void *data) k->class_id = PCI_CLASS_SERIAL_USB; k->revision = 0x03; k->is_express = 1; - k->config_write = xhci_write_config; + k->no_hotplug = 1; } -static TypeInfo xhci_info = { +static const TypeInfo xhci_info = { .name = "nec-usb-xhci", .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(XHCIState), diff --git a/hw/usb/host-bsd.c b/hw/usb/host-bsd.c index ec26266620..39f22810b3 100644 --- a/hw/usb/host-bsd.c +++ b/hw/usb/host-bsd.c @@ -25,7 +25,7 @@ */ #include "qemu-common.h" -#include "monitor.h" +#include "monitor/monitor.h" #include "hw/usb.h" /* usb.h declares these */ @@ -121,7 +121,7 @@ static void usb_host_handle_reset(USBDevice *dev) * -check device states against transfer requests * and return appropriate response */ -static int usb_host_handle_control(USBDevice *dev, +static void usb_host_handle_control(USBDevice *dev, USBPacket *p, int request, int value, @@ -139,7 +139,6 @@ static int usb_host_handle_control(USBDevice *dev, /* specific SET_ADDRESS support */ dev->addr = value; - return 0; } else if ((request >> 8) == UT_WRITE_DEVICE && (request & 0xff) == UR_SET_CONFIG) { @@ -151,10 +150,8 @@ static int usb_host_handle_control(USBDevice *dev, printf("handle_control: failed to set configuration - %s\n", strerror(errno)); #endif - return USB_RET_STALL; + p->status = USB_RET_STALL; } - - return 0; } else if ((request >> 8) == UT_WRITE_INTERFACE && (request & 0xff) == UR_SET_INTERFACE) { @@ -168,10 +165,8 @@ static int usb_host_handle_control(USBDevice *dev, printf("handle_control: failed to set alternate interface - %s\n", strerror(errno)); #endif - return USB_RET_STALL; + p->status = USB_RET_STALL; } - - return 0; } else { req.ucr_request.bmRequestType = request >> 8; req.ucr_request.bRequest = request & 0xff; @@ -201,14 +196,14 @@ static int usb_host_handle_control(USBDevice *dev, printf("handle_control: error after request - %s\n", strerror(errno)); #endif - return USB_RET_NAK; // STALL + p->status = USB_RET_NAK; /* STALL */ } else { - return req.ucr_actlen; + p->actual_length = req.ucr_actlen; } } } -static int usb_host_handle_data(USBDevice *dev, USBPacket *p) +static void usb_host_handle_data(USBDevice *dev, USBPacket *p) { USBHostDevice *s = (USBHostDevice *)dev; int ret, fd, mode; @@ -232,7 +227,8 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p) fd = ensure_ep_open(s, devep, mode); if (fd < 0) { sigprocmask(SIG_SETMASK, &old_mask, NULL); - return USB_RET_NODEV; + p->status = USB_RET_NODEV; + return; } if (ioctl(fd, USB_SET_TIMEOUT, &timeout) < 0) { @@ -267,12 +263,13 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p) switch(errno) { case ETIMEDOUT: case EINTR: - return USB_RET_NAK; + p->status = USB_RET_NAK; + break; default: - return USB_RET_STALL; + p->status = USB_RET_STALL; } } else { - return ret; + p->actual_length = ret; } } @@ -295,6 +292,7 @@ static void usb_host_handle_destroy(USBDevice *opaque) static int usb_host_initfn(USBDevice *dev) { + dev->flags |= (1 << USB_DEV_FLAG_IS_HOST); return 0; } @@ -409,7 +407,7 @@ static void usb_host_class_initfn(ObjectClass *klass, void *data) uc->handle_destroy = usb_host_handle_destroy; } -static TypeInfo usb_host_dev_info = { +static const TypeInfo usb_host_dev_info = { .name = "usb-host", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBHostDevice), @@ -635,13 +633,7 @@ static int usb_host_info_device(void *opaque, return 0; } -void usb_host_info(Monitor *mon) +void usb_host_info(Monitor *mon, const QDict *qdict) { usb_host_scan(mon, usb_host_info_device); } - -/* XXX add this */ -int usb_host_device_close(const char *devname) -{ - return 0; -} diff --git a/hw/usb/host-legacy.c b/hw/usb/host-legacy.c new file mode 100644 index 0000000000..3a5f705721 --- /dev/null +++ b/hw/usb/host-legacy.c @@ -0,0 +1,144 @@ +/* + * 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); + qdev_init_nofail(&dev->qdev); + return dev; + +fail: + qdev_free(&dev->qdev); + 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 0000000000..3a582c526d --- /dev/null +++ b/hw/usb/host-libusb.c @@ -0,0 +1,1522 @@ +/* + * 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 "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; + 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_attach_kernel(USBHostDevice *s); + +/* ------------------------------------------------------------------------ */ + +#define CONTROL_TIMEOUT 10000 /* 10 sec */ +#define BULK_TIMEOUT 0 /* unlimited */ +#define INTR_TIMEOUT 0 /* unlimited */ + +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; + + rc = libusb_get_port_path(ctx, dev, path, 7); + 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 = "?"; + } + fprintf(stderr, "%s: %d [%s]\n", 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); + } + 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 bool usb_host_full_speed_compat(USBHostDevice *s) +{ + struct libusb_config_descriptor *conf; + const struct libusb_interface_descriptor *intf; + const struct libusb_endpoint_descriptor *endp; + 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 */ + return false; + case 0x03: /* INTERRUPT */ + if (endp->wMaxPacketSize > 64) { + return false; + } + break; + } + } + } + } + libusb_free_config_descriptor(conf); + } + return true; +} + +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; + 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); + } + } + + 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; + + 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; + } + + libusb_get_device_descriptor(dev, &s->ddesc); + s->dev = dev; + s->bus_num = bus_num; + s->addr = addr; + 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)]; + udev->speedmask = (1 << udev->speed); + if (udev->speed == USB_SPEED_HIGH && usb_host_full_speed_compat(s)) { + udev->speedmask |= USB_SPEED_MASK_FULL; + } + + 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); + } + + rc = usb_device_attach(udev); + if (rc) { + 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) { + 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 int usb_host_initfn(USBDevice *udev) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + + loglevel = s->loglevel; + 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); + add_boot_device_path(s->bootindex, &udev->qdev, NULL); + usb_host_auto_check(NULL); + return 0; +} + +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; + + if (configuration == 0) { + /* address state - ignore */ + return USB_RET_SUCCESS; + } + + usb_host_detach_kernel(s); + + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + 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); + usb_host_detach_kernel(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); + } + + 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); + 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); + + trace_usb_host_reset(s->bus_num, s->addr); + + if (udev->configuration == 0) { + return; + } + usb_host_release_interfaces(s); + libusb_reset_device(s->dh); + usb_host_claim_interfaces(s, 0); + usb_host_ep_update(s); +} + +/* + * 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_HEX32("vendorid", USBHostDevice, match.vendor_id, 0), + DEFINE_PROP_HEX32("productid", USBHostDevice, match.product_id, 0), + DEFINE_PROP_UINT32("isobufs", USBHostDevice, iso_urb_count, 4), + DEFINE_PROP_UINT32("isobsize", USBHostDevice, iso_urb_frames, 32), + DEFINE_PROP_INT32("bootindex", USBHostDevice, bootindex, -1), + 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->init = usb_host_initfn; + 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; + dc->vmsd = &vmstate_usb_host; + dc->props = usb_host_dev_properties; +} + +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, +}; + +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; + 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) { + qemu_del_timer(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 = qemu_new_timer_ms(rt_clock, usb_host_auto_check, NULL); + if (!usb_auto_timer) { + return; + } + trace_usb_host_auto_scan_enabled(); + } + qemu_mod_timer(usb_auto_timer, qemu_get_clock_ms(rt_clock) + 2000); +} + +void usb_host_info(Monitor *mon, const QDict *qdict) +{ + libusb_device **devs; + 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-linux.c b/hw/usb/host-linux.c index 8df92074d3..ca09a891ee 100644 --- a/hw/usb/host-linux.c +++ b/hw/usb/host-linux.c @@ -31,9 +31,9 @@ */ #include "qemu-common.h" -#include "qemu-timer.h" -#include "monitor.h" -#include "sysemu.h" +#include "qemu/timer.h" +#include "monitor/monitor.h" +#include "sysemu/sysemu.h" #include "trace.h" #include <dirent.h> @@ -43,6 +43,13 @@ #include <linux/version.h> #include "hw/usb.h" #include "hw/usb/desc.h" +#include "hw/usb/host.h" + +#ifdef CONFIG_USB_LIBUSB +# define DEVNAME "usb-host-linux" +#else +# define DEVNAME "usb-host" +#endif /* We redefine it to avoid version problems */ struct usb_ctrltransfer { @@ -87,14 +94,6 @@ struct endp_data { int inflight; }; -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, }; @@ -131,11 +130,10 @@ typedef struct USBHostDevice { static QTAILQ_HEAD(, USBHostDevice) hostdevs = QTAILQ_HEAD_INITIALIZER(hostdevs); static int usb_host_close(USBHostDevice *dev); -static int parse_filter(const char *spec, struct USBAutoFilter *f); static void usb_host_auto_check(void *unused); static int usb_host_read_file(char *line, size_t line_size, const char *device_file, const char *device_name); -static int usb_linux_update_endp_table(USBHostDevice *s); +static void usb_linux_update_endp_table(USBHostDevice *s); static int usb_host_usbfs_type(USBHostDevice *s, USBPacket *p) { @@ -366,28 +364,34 @@ static void async_complete(void *opaque) if (p) { switch (aurb->urb.status) { case 0: - p->result += aurb->urb.actual_length; + p->actual_length += aurb->urb.actual_length; + if (!aurb->more) { + /* Clear previous ASYNC status */ + p->status = USB_RET_SUCCESS; + } break; case -EPIPE: set_halt(s, p->pid, p->ep->nr); - p->result = USB_RET_STALL; + p->status = USB_RET_STALL; break; case -EOVERFLOW: - p->result = USB_RET_BABBLE; + p->status = USB_RET_BABBLE; break; default: - p->result = USB_RET_IOERROR; + p->status = USB_RET_IOERROR; break; } if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) { - trace_usb_host_req_complete(s->bus_num, s->addr, p, p->result); + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, aurb->urb.actual_length); usb_generic_async_ctrl_complete(&s->dev, p); } else if (!aurb->more) { - trace_usb_host_req_complete(s->bus_num, s->addr, p, p->result); + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, aurb->urb.actual_length); usb_packet_complete(&s->dev, p); } } @@ -647,7 +651,7 @@ static void usb_host_handle_reset(USBDevice *dev) trace_usb_host_reset(s->bus_num, s->addr); - usb_host_do_reset(s);; + usb_host_do_reset(s); usb_host_claim_interfaces(s, 0); usb_linux_update_endp_table(s); @@ -733,27 +737,31 @@ static void usb_host_stop_n_free_iso(USBHostDevice *s, int pid, uint8_t ep) clear_iso_started(s, pid, ep); } -static int urb_status_to_usb_ret(int status) +static void urb_status_to_usb_ret(int status, USBPacket *p) { switch (status) { case -EPIPE: - return USB_RET_STALL; + p->status = USB_RET_STALL; + break; case -EOVERFLOW: - return USB_RET_BABBLE; + p->status = USB_RET_BABBLE; + break; default: - return USB_RET_IOERROR; + p->status = USB_RET_IOERROR; } } -static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) +static void usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) { AsyncURB *aurb; - int i, j, ret, max_packet_size, offset, len = 0; + int i, j, max_packet_size, offset, len; uint8_t *buf; max_packet_size = p->ep->max_packet_size; - if (max_packet_size == 0) - return USB_RET_NAK; + if (max_packet_size == 0) { + p->status = USB_RET_NAK; + return; + } aurb = get_iso_urb(s, p->pid, p->ep->nr); if (!aurb) { @@ -766,18 +774,17 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) if (in) { /* Check urb status */ if (aurb[i].urb.status) { - len = urb_status_to_usb_ret(aurb[i].urb.status); + urb_status_to_usb_ret(aurb[i].urb.status, p); /* Move to the next urb */ aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1; /* Check frame status */ } else if (aurb[i].urb.iso_frame_desc[j].status) { - len = urb_status_to_usb_ret( - aurb[i].urb.iso_frame_desc[j].status); + urb_status_to_usb_ret(aurb[i].urb.iso_frame_desc[j].status, p); /* Check the frame fits */ } else if (aurb[i].urb.iso_frame_desc[j].actual_length > p->iov.size) { printf("husb: received iso data is larger then packet\n"); - len = USB_RET_BABBLE; + p->status = USB_RET_BABBLE; /* All good copy data over */ } else { len = aurb[i].urb.iso_frame_desc[j].actual_length; @@ -792,7 +799,8 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) /* Check the frame fits */ if (len > max_packet_size) { printf("husb: send iso data is larger then max packet size\n"); - return USB_RET_NAK; + p->status = USB_RET_NAK; + return; } /* All good copy data over */ @@ -823,17 +831,16 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) /* (Re)-submit all fully consumed / filled urbs */ for (i = 0; i < s->iso_urb_count; i++) { if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) { - ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]); - if (ret < 0) { + if (ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]) < 0) { perror("USBDEVFS_SUBMITURB"); - if (!in || len == 0) { + if (!in || p->status == USB_RET_SUCCESS) { switch(errno) { case ETIMEDOUT: - len = USB_RET_NAK; + p->status = USB_RET_NAK; break; case EPIPE: default: - len = USB_RET_STALL; + p->status = USB_RET_STALL; } } break; @@ -843,11 +850,9 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in) } } } - - return len; } -static int usb_host_handle_data(USBDevice *dev, USBPacket *p) +static void usb_host_handle_data(USBDevice *dev, USBPacket *p) { USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); struct usbdevfs_urb *urb; @@ -861,8 +866,10 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p) p->ep->nr, p->iov.size); if (!is_valid(s, p->pid, p->ep->nr)) { - trace_usb_host_req_complete(s->bus_num, s->addr, p, USB_RET_NAK); - return USB_RET_NAK; + p->status = USB_RET_NAK; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + return; } if (p->pid == USB_TOKEN_IN) { @@ -876,14 +883,17 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p) ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &arg); if (ret < 0) { perror("USBDEVFS_CLEAR_HALT"); - trace_usb_host_req_complete(s->bus_num, s->addr, p, USB_RET_NAK); - return USB_RET_NAK; + p->status = USB_RET_NAK; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + return; } clear_halt(s, p->pid, p->ep->nr); } if (is_isoc(s, p->pid, p->ep->nr)) { - return usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN); + usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN); + return; } v = 0; @@ -931,19 +941,21 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p) switch(errno) { case ETIMEDOUT: + p->status = USB_RET_NAK; trace_usb_host_req_complete(s->bus_num, s->addr, p, - USB_RET_NAK); - return USB_RET_NAK; + p->status, p->actual_length); + break; case EPIPE: default: + p->status = USB_RET_STALL; trace_usb_host_req_complete(s->bus_num, s->addr, p, - USB_RET_STALL); - return USB_RET_STALL; + p->status, p->actual_length); } + return; } } while (rem > 0); - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } static int ctrl_error(void) @@ -955,14 +967,13 @@ static int ctrl_error(void) } } -static int usb_host_set_address(USBHostDevice *s, int addr) +static void usb_host_set_address(USBHostDevice *s, int addr) { trace_usb_host_set_address(s->bus_num, s->addr, addr); s->dev.addr = addr; - return 0; } -static int usb_host_set_config(USBHostDevice *s, int config) +static void usb_host_set_config(USBHostDevice *s, int config, USBPacket *p) { int ret, first = 1; @@ -987,14 +998,15 @@ again: } if (ret < 0) { - return ctrl_error(); + p->status = ctrl_error(); + return; } usb_host_claim_interfaces(s, config); usb_linux_update_endp_table(s); - return 0; } -static int usb_host_set_interface(USBHostDevice *s, int iface, int alt) +static void usb_host_set_interface(USBHostDevice *s, int iface, int alt, + USBPacket *p) { struct usbdevfs_setinterface si; int i, ret; @@ -1011,7 +1023,8 @@ static int usb_host_set_interface(USBHostDevice *s, int iface, int alt) } if (iface >= USB_MAX_INTERFACES) { - return USB_RET_STALL; + p->status = USB_RET_STALL; + return; } si.interface = iface; @@ -1022,15 +1035,15 @@ static int usb_host_set_interface(USBHostDevice *s, int iface, int alt) iface, alt, ret, errno); if (ret < 0) { - return ctrl_error(); + p->status = ctrl_error(); + return; } s->dev.altsetting[iface] = alt; usb_linux_update_endp_table(s); - return 0; } -static int usb_host_handle_control(USBDevice *dev, USBPacket *p, +static void usb_host_handle_control(USBDevice *dev, USBPacket *p, int request, int value, int index, int length, uint8_t *data) { USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); @@ -1045,23 +1058,22 @@ static int usb_host_handle_control(USBDevice *dev, USBPacket *p, /* Note request is (bRequestType << 8) | bRequest */ trace_usb_host_req_control(s->bus_num, s->addr, p, request, value, index); - assert(p->result == 0); switch (request) { case DeviceOutRequest | USB_REQ_SET_ADDRESS: - ret = usb_host_set_address(s, value); - trace_usb_host_req_emulated(s->bus_num, s->addr, p, ret); - return ret; + 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: - ret = usb_host_set_config(s, value & 0xff); - trace_usb_host_req_emulated(s->bus_num, s->addr, p, ret); - return ret; + 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: - ret = usb_host_set_interface(s, index, value); - trace_usb_host_req_emulated(s->bus_num, s->addr, p, ret); - return ret; + 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 */ @@ -1069,16 +1081,16 @@ static int usb_host_handle_control(USBDevice *dev, USBPacket *p, ioctl(s->fd, USBDEVFS_CLEAR_HALT, &index); clear_halt(s, pid, index & 0x0f); trace_usb_host_req_emulated(s->bus_num, s->addr, p, 0); - return 0; + return; } } /* The rest are asynchronous */ - if (length > sizeof(dev->data_buf)) { fprintf(stderr, "husb: ctrl buffer too small (%d > %zu)\n", length, sizeof(dev->data_buf)); - return USB_RET_STALL; + p->status = USB_RET_STALL; + return; } aurb = async_alloc(s); @@ -1112,18 +1124,20 @@ static int usb_host_handle_control(USBDevice *dev, USBPacket *p, switch(errno) { case ETIMEDOUT: - return USB_RET_NAK; + p->status = USB_RET_NAK; + break; case EPIPE: default: - return USB_RET_STALL; + p->status = USB_RET_STALL; + break; } + return; } - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } -/* returns 1 on problem encountered or 0 for success */ -static int usb_linux_update_endp_table(USBHostDevice *s) +static void usb_linux_update_endp_table(USBHostDevice *s) { static const char *tname[] = { [USB_ENDPOINT_XFER_CONTROL] = "control", @@ -1149,23 +1163,23 @@ static int usb_linux_update_endp_table(USBHostDevice *s) if (d->bLength < 2) { trace_usb_host_parse_error(s->bus_num, s->addr, "descriptor too short"); - goto error; + return; } if (i + d->bLength > s->descr_len) { trace_usb_host_parse_error(s->bus_num, s->addr, "descriptor too long"); - goto error; + return; } switch (d->bDescriptorType) { case 0: trace_usb_host_parse_error(s->bus_num, s->addr, "invalid descriptor type"); - goto error; + return; case USB_DT_DEVICE: if (d->bLength < 0x12) { trace_usb_host_parse_error(s->bus_num, s->addr, "device descriptor too short"); - goto error; + return; } v = (d->u.device.idVendor_hi << 8) | d->u.device.idVendor_lo; p = (d->u.device.idProduct_hi << 8) | d->u.device.idProduct_lo; @@ -1175,7 +1189,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) if (d->bLength < 0x09) { trace_usb_host_parse_error(s->bus_num, s->addr, "config descriptor too short"); - goto error; + return; } configuration = d->u.config.bConfigurationValue; active = (configuration == s->dev.configuration); @@ -1186,7 +1200,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) if (d->bLength < 0x09) { trace_usb_host_parse_error(s->bus_num, s->addr, "interface descriptor too short"); - goto error; + return; } interface = d->u.interface.bInterfaceNumber; altsetting = d->u.interface.bAlternateSetting; @@ -1199,7 +1213,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) if (d->bLength < 0x07) { trace_usb_host_parse_error(s->bus_num, s->addr, "endpoint descriptor too short"); - goto error; + return; } devep = d->u.endpoint.bEndpointAddress; pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT; @@ -1207,7 +1221,7 @@ static int usb_linux_update_endp_table(USBHostDevice *s) if (ep == 0) { trace_usb_host_parse_error(s->bus_num, s->addr, "invalid endpoint address"); - goto error; + return; } type = d->u.endpoint.bmAttributes & 0x3; @@ -1224,7 +1238,8 @@ static int usb_linux_update_endp_table(USBHostDevice *s) usb_ep_set_type(&s->dev, pid, ep, type); usb_ep_set_ifnum(&s->dev, pid, ep, interface); if ((s->options & (1 << USB_HOST_OPT_PIPELINE)) && - (type == USB_ENDPOINT_XFER_BULK)) { + (type == USB_ENDPOINT_XFER_BULK) && + (pid == USB_TOKEN_OUT)) { usb_ep_set_pipeline(&s->dev, pid, ep, true); } @@ -1239,11 +1254,6 @@ static int usb_linux_update_endp_table(USBHostDevice *s) break; } } - return 0; - -error: - usb_ep_reset(&s->dev); - return 1; } /* @@ -1302,7 +1312,7 @@ static int usb_host_open(USBHostDevice *dev, int bus_num, dev->bus_num = bus_num; dev->addr = addr; - strcpy(dev->port, port); + pstrcpy(dev->port, sizeof(dev->port), port); dev->fd = fd; /* read the device description */ @@ -1330,10 +1340,7 @@ static int usb_host_open(USBHostDevice *dev, int bus_num, } usb_ep_init(&dev->dev); - ret = usb_linux_update_endp_table(dev); - if (ret) { - goto fail; - } + usb_linux_update_endp_table(dev); if (speed == -1) { struct usbdevfs_connectinfo ci; @@ -1422,7 +1429,7 @@ static void usb_host_exit_notifier(struct Notifier *n, void *data) usb_host_release_port(s); if (s->fd != -1) { - usb_host_do_reset(s);; + usb_host_do_reset(s); } } @@ -1467,6 +1474,7 @@ static int usb_host_initfn(USBDevice *dev) { USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev); + dev->flags |= (1 << USB_DEV_FLAG_IS_HOST); dev->auto_attach = 0; s->fd = -1; s->hub_fd = -1; @@ -1485,7 +1493,7 @@ static int usb_host_initfn(USBDevice *dev) } static const VMStateDescription vmstate_usb_host = { - .name = "usb-host", + .name = DEVNAME, .version_id = 1, .minimum_version_id = 1, .post_load = usb_host_post_load, @@ -1524,8 +1532,8 @@ static void usb_host_class_initfn(ObjectClass *klass, void *data) dc->props = usb_host_dev_properties; } -static TypeInfo usb_host_dev_info = { - .name = "usb-host", +static const TypeInfo usb_host_dev_info = { + .name = DEVNAME, .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBHostDevice), .class_init = usb_host_class_initfn, @@ -1534,75 +1542,10 @@ static TypeInfo usb_host_dev_info = { static void usb_host_register_types(void) { type_register_static(&usb_host_dev_info); - usb_legacy_register("usb-host", "host", usb_host_device_open); } type_init(usb_host_register_types) -USBDevice *usb_host_device_open(USBBus *bus, const char *devname) -{ - struct USBAutoFilter filter; - USBDevice *dev; - char *p; - - dev = usb_create(bus, "usb-host"); - - if (strstr(devname, "auto:")) { - if (parse_filter(devname, &filter) < 0) { - goto fail; - } - } else { - if ((p = strchr(devname, '.'))) { - filter.bus_num = strtoul(devname, NULL, 0); - filter.addr = strtoul(p + 1, NULL, 0); - filter.vendor_id = 0; - filter.product_id = 0; - } else if ((p = strchr(devname, ':'))) { - filter.bus_num = 0; - filter.addr = 0; - filter.vendor_id = strtoul(devname, NULL, 16); - filter.product_id = strtoul(p + 1, NULL, 16); - } else { - goto fail; - } - } - - qdev_prop_set_uint32(&dev->qdev, "hostbus", filter.bus_num); - qdev_prop_set_uint32(&dev->qdev, "hostaddr", filter.addr); - qdev_prop_set_uint32(&dev->qdev, "vendorid", filter.vendor_id); - qdev_prop_set_uint32(&dev->qdev, "productid", filter.product_id); - qdev_init_nofail(&dev->qdev); - return dev; - -fail: - qdev_free(&dev->qdev); - return NULL; -} - -int usb_host_device_close(const char *devname) -{ -#if 0 - char product_name[PRODUCT_NAME_SZ]; - int bus_num, addr; - USBHostDevice *s; - - if (strstr(devname, "auto:")) { - return usb_host_auto_del(devname); - } - if (usb_host_find_device(&bus_num, &addr, product_name, - sizeof(product_name), devname) < 0) { - return -1; - } - s = hostdev_find(bus_num, addr); - if (s) { - usb_device_delete_addr(s->bus_num, s->dev.addr); - return 0; - } -#endif - - return -1; -} - /* * Read sys file-system device file * @@ -1727,6 +1670,7 @@ static int usb_host_scan(void *opaque, USBScanFunc *func) } static QEMUTimer *usb_auto_timer; +static VMChangeStateEntry *usb_vmstate; static int usb_host_auto_scan(void *opaque, int bus_num, int addr, const char *port, @@ -1749,7 +1693,7 @@ static int usb_host_auto_scan(void *opaque, int bus_num, if (f->addr > 0 && f->addr != addr) { continue; } - if (f->port != NULL && (port == NULL || strcmp(f->port, port) != 0)) { + if (f->port != NULL && strcmp(f->port, port) != 0) { continue; } @@ -1781,6 +1725,13 @@ static int usb_host_auto_scan(void *opaque, int bus_num, return 0; } +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; @@ -1809,6 +1760,9 @@ static void usb_host_auto_check(void *unused) } } + if (!usb_vmstate) { + usb_vmstate = qemu_add_vm_change_state_handler(usb_host_vm_state, NULL); + } if (!usb_auto_timer) { usb_auto_timer = qemu_new_timer_ms(rt_clock, usb_host_auto_check, NULL); if (!usb_auto_timer) { @@ -1819,55 +1773,7 @@ static void usb_host_auto_check(void *unused) qemu_mod_timer(usb_auto_timer, qemu_get_clock_ms(rt_clock) + 2000); } -/* - * Autoconnect filter - * Format: - * auto:bus:dev[:vid:pid] - * auto:bus.dev[:vid:pid] - * - * bus - bus number (dec, * means any) - * dev - device number (dec, * means any) - * vid - vendor id (hex, * means any) - * pid - product id (hex, * means any) - * - * See 'lsusb' output. - */ -static int parse_filter(const char *spec, struct USBAutoFilter *f) -{ - enum { BUS, DEV, VID, PID, DONE }; - const char *p = spec; - int i; - - f->bus_num = 0; - f->addr = 0; - f->vendor_id = 0; - f->product_id = 0; - - for (i = BUS; i < DONE; i++) { - p = strpbrk(p, ":."); - if (!p) { - break; - } - p++; - - if (*p == '*') { - continue; - } - switch(i) { - case BUS: f->bus_num = strtol(p, NULL, 10); break; - case DEV: f->addr = strtol(p, NULL, 10); break; - case VID: f->vendor_id = strtol(p, NULL, 16); break; - case PID: f->product_id = strtol(p, NULL, 16); break; - } - } - - if (i < DEV) { - fprintf(stderr, "husb: invalid auto filter spec %s\n", spec); - return -1; - } - - return 0; -} +#ifndef CONFIG_USB_LIBUSB /**********************/ /* USB host device info */ @@ -1977,7 +1883,7 @@ static void hex2str(int val, char *str, size_t size) } } -void usb_host_info(Monitor *mon) +void usb_host_info(Monitor *mon, const QDict *qdict) { struct USBAutoFilter *f; struct USBHostDevice *s; @@ -2000,3 +1906,5 @@ void usb_host_info(Monitor *mon) bus, addr, f->port ? f->port : "*", vid, pid); } } + +#endif diff --git a/hw/usb/host-stub.c b/hw/usb/host-stub.c index b4e10c12ca..28d8032caf 100644 --- a/hw/usb/host-stub.c +++ b/hw/usb/host-stub.c @@ -31,11 +31,11 @@ */ #include "qemu-common.h" -#include "console.h" +#include "ui/console.h" #include "hw/usb.h" -#include "monitor.h" +#include "monitor/monitor.h" -void usb_host_info(Monitor *mon) +void usb_host_info(Monitor *mon, const QDict *qdict) { monitor_printf(mon, "USB host devices not supported\n"); } @@ -45,8 +45,3 @@ USBDevice *usb_host_device_open(USBBus *bus, const char *devname) { return NULL; } - -int usb_host_device_close(const char *devname) -{ - return 0; -} diff --git a/hw/usb/host.h b/hw/usb/host.h new file mode 100644 index 0000000000..048ff3b482 --- /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 index c0de30ea88..d2d4b51b94 100644 --- a/hw/usb/libhw.c +++ b/hw/usb/libhw.c @@ -20,27 +20,33 @@ * THE SOFTWARE. */ #include "qemu-common.h" -#include "cpu-common.h" +#include "hw/hw.h" #include "hw/usb.h" -#include "dma.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; - dma_addr_t len; void *mem; int i; for (i = 0; i < sgl->nsg; i++) { - len = sgl->sg[i].len; - mem = dma_memory_map(sgl->dma, sgl->sg[i].base, &len, dir); - if (!mem) { - goto err; - } - qemu_iovec_add(&p->iov, mem, len); - if (len != sgl->sg[i].len) { - goto err; + 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->dma, 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; diff --git a/hw/usb/quirks-ftdi-ids.h b/hw/usb/quirks-ftdi-ids.h new file mode 100644 index 0000000000..57c12ef662 --- /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 0000000000..8dbdb46ffe --- /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 0000000000..a761a96032 --- /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 0000000000..8dc6065527 --- /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 index 10b4fbb3a7..a594e954e4 100644 --- a/hw/usb/redirect.c +++ b/hw/usb/redirect.c @@ -1,7 +1,7 @@ /* * USB redirector usb-guest * - * Copyright (c) 2011 Red Hat, Inc. + * Copyright (c) 2011-2012 Red Hat, Inc. * * Red Hat Authors: * Hans de Goede <hdegoede@redhat.com> @@ -26,9 +26,11 @@ */ #include "qemu-common.h" -#include "qemu-timer.h" -#include "monitor.h" -#include "sysemu.h" +#include "qemu/timer.h" +#include "monitor/monitor.h" +#include "sysemu/sysemu.h" +#include "qemu/iov.h" +#include "sysemu/char.h" #include <dirent.h> #include <sys/ioctl.h> @@ -42,31 +44,54 @@ #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)) -typedef struct AsyncURB AsyncURB; typedef struct USBRedirDevice USBRedirDevice; -/* Struct to hold buffered packets (iso or int input packets) */ +/* Struct to hold buffered packets */ struct buf_packet { uint8_t *data; - int len; - int status; + 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 !! */ 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; - int bufpq_size; - int bufpq_target_size; + 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 { @@ -79,33 +104,24 @@ struct USBRedirDevice { /* Data passed from chardev the fd_read cb to the usbredirparser read cb */ const uint8_t *read_buf; int read_buf_size; - /* For async handling of open/close */ - QEMUBH *open_close_bh; + /* Active chardev-watch-tag */ + guint watch; + /* For async handling of close */ + QEMUBH *chardev_close_bh; /* To delay the usb attach in case of quick chardev close + open */ QEMUTimer *attach_timer; int64_t next_attach_time; struct usbredirparser *parser; struct endp_data endpoint[MAX_ENDPOINTS]; - uint32_t packet_id; - QTAILQ_HEAD(, AsyncURB) asyncq; + 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; -}; - -struct AsyncURB { - USBRedirDevice *dev; - USBPacket *packet; - uint32_t packet_id; - int get; - union { - struct usb_redir_control_packet_header control_packet; - struct usb_redir_bulk_packet_header bulk_packet; - struct usb_redir_interrupt_packet_header interrupt_packet; - }; - QTAILQ_ENTRY(AsyncURB)next; + int compatible_speedmask; }; static void usbredir_hello(void *priv, struct usb_redir_hello_header *h); @@ -116,32 +132,39 @@ static void usbredir_interface_info(void *priv, struct usb_redir_interface_info_header *interface_info); static void usbredir_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info); -static void usbredir_configuration_status(void *priv, uint32_t id, +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, uint32_t id, +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, uint32_t id, +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, uint32_t id, +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, uint32_t id, +static void usbredir_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status); -static void usbredir_control_packet(void *priv, uint32_t id, +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, uint32_t id, +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, uint32_t id, +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, uint32_t id, +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 int usbredir_handle_status(USBRedirDevice *dev, - int status, int actual_len); +static void usbredir_handle_status(USBRedirDevice *dev, USBPacket *p, + int status); + +#define VERSION "qemu usb-redir guest " QEMU_VERSION /* * Logging stuff @@ -233,74 +256,190 @@ static int usbredir_read(void *priv, uint8_t *data, int count) 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->opened) { + if (!dev->cs->be_open) { return 0; } - return qemu_chr_fe_write(dev->cs, data, count); + /* 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, + usbredir_write_unblocked, dev); + } + if (r < 0) { + r = 0; + } + } + return r; } /* - * Async and buffered packets helpers + * Cancelled and buffered packets helpers */ -static AsyncURB *async_alloc(USBRedirDevice *dev, USBPacket *p) +static void packet_id_queue_init(struct PacketIdQueue *q, + USBRedirDevice *dev, const char *name) { - AsyncURB *aurb = (AsyncURB *) g_malloc0(sizeof(AsyncURB)); - aurb->dev = dev; - aurb->packet = p; - aurb->packet_id = dev->packet_id; - QTAILQ_INSERT_TAIL(&dev->asyncq, aurb, next); - dev->packet_id++; + q->dev = dev; + q->name = name; + QTAILQ_INIT(&q->head); + q->size = 0; +} - return aurb; +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 void async_free(USBRedirDevice *dev, AsyncURB *aurb) +static int packet_id_queue_remove(struct PacketIdQueue *q, uint64_t id) { - QTAILQ_REMOVE(&dev->asyncq, aurb, next); - g_free(aurb); + 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 AsyncURB *async_find(USBRedirDevice *dev, uint32_t packet_id) +static void packet_id_queue_empty(struct PacketIdQueue *q) { - AsyncURB *aurb; + USBRedirDevice *dev = q->dev; + struct PacketIdQueueEntry *e, *next_e; - QTAILQ_FOREACH(aurb, &dev->asyncq, next) { - if (aurb->packet_id == packet_id) { - return aurb; - } + 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); } - DPRINTF("could not find async urb for packet_id %u\n", packet_id); - return NULL; + q->size = 0; } static void usbredir_cancel_packet(USBDevice *udev, USBPacket *p) { USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); - AsyncURB *aurb; + 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; - QTAILQ_FOREACH(aurb, &dev->asyncq, next) { - if (p != aurb->packet) { + /* 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); + } + } +} - DPRINTF("async cancel id %u\n", aurb->packet_id); - usbredirparser_send_cancel_data_packet(dev->parser, aurb->packet_id); - usbredirparser_do_write(dev->parser); +static void usbredir_fill_already_in_flight(USBRedirDevice *dev) +{ + int ep; + struct USBDevice *udev = &dev->dev; - /* Mark it as dead */ - aurb->packet = NULL; - break; + 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, int len, int status, uint8_t ep) +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; @@ -324,7 +463,9 @@ static void bufp_alloc(USBRedirDevice *dev, 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++; } @@ -334,7 +475,7 @@ static void bufp_free(USBRedirDevice *dev, struct buf_packet *bufp, { QTAILQ_REMOVE(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); dev->endpoint[EP2I(ep)].bufpq_size--; - free(bufp->data); + free(bufp->free_on_destroy); g_free(bufp); } @@ -360,7 +501,7 @@ static void usbredir_handle_reset(USBDevice *udev) usbredirparser_do_write(dev->parser); } -static int usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p, +static void usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p, uint8_t ep) { int status, len; @@ -417,7 +558,7 @@ static int usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p, !dev->endpoint[EP2I(ep)].bufpq_prefilled) { if (dev->endpoint[EP2I(ep)].bufpq_size < dev->endpoint[EP2I(ep)].bufpq_target_size) { - return usbredir_handle_status(dev, 0, 0); + return; } dev->endpoint[EP2I(ep)].bufpq_prefilled = 1; } @@ -431,27 +572,23 @@ static int usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p, /* Check iso_error for stream errors, otherwise its an underrun */ status = dev->endpoint[EP2I(ep)].iso_error; dev->endpoint[EP2I(ep)].iso_error = 0; - return status ? USB_RET_IOERROR : 0; + 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; - if (status != usb_redir_success) { - bufp_free(dev, isop, ep); - return USB_RET_IOERROR; - } - len = isop->len; if (len > p->iov.size) { ERROR("received iso data is larger then packet ep %02X (%d > %d)\n", ep, len, (int)p->iov.size); - bufp_free(dev, isop, ep); - return USB_RET_BABBLE; + len = p->iov.size; + status = usb_redir_babble; } usb_packet_copy(p, isop->data, len); bufp_free(dev, isop, ep); - return len; + 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 */ @@ -471,7 +608,7 @@ static int usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p, dev->endpoint[EP2I(ep)].iso_error = 0; DPRINTF2("iso-token-out ep %02X status %d len %zd\n", ep, status, p->iov.size); - return usbredir_handle_status(dev, status, p->iov.size); + usbredir_handle_status(dev, p, status); } } @@ -489,108 +626,260 @@ static void usbredir_stop_iso_stream(USBRedirDevice *dev, uint8_t ep) usbredir_free_bufpq(dev, ep); } -static int usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p, +/* + * 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) { - AsyncURB *aurb = async_alloc(dev, p); 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; - DPRINTF("bulk-out ep %02X len %zd id %u\n", ep, - p->iov.size, aurb->packet_id); + 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 len %zd id %"PRIu64"\n", ep, size, p->id); bulk_packet.endpoint = ep; - bulk_packet.length = p->iov.size; + bulk_packet.length = size; bulk_packet.stream_id = 0; - aurb->bulk_packet = bulk_packet; + 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, aurb->packet_id, + usbredirparser_send_bulk_packet(dev->parser, p->id, &bulk_packet, NULL, 0); } else { - uint8_t buf[p->iov.size]; - usb_packet_copy(p, buf, p->iov.size); - usbredir_log_data(dev, "bulk data out:", buf, p->iov.size); - usbredirparser_send_bulk_packet(dev->parser, aurb->packet_id, - &bulk_packet, buf, p->iov.size); + 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); - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } -static int usbredir_handle_interrupt_data(USBRedirDevice *dev, - USBPacket *p, uint8_t ep) +static void usbredir_handle_interrupt_in_data(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) { - if (ep & USB_DIR_IN) { - /* Input interrupt endpoint, buffered packet input */ - struct buf_packet *intp; - int status, len; - - if (!dev->endpoint[EP2I(ep)].interrupt_started && - !dev->endpoint[EP2I(ep)].interrupt_error) { - struct usb_redir_start_interrupt_receiving_header start_int = { - .endpoint = ep, - }; - /* No id, we look at the ep when receiving a status back */ - usbredirparser_send_start_interrupt_receiving(dev->parser, 0, - &start_int); - usbredirparser_do_write(dev->parser); - DPRINTF("interrupt recv started ep %02X\n", ep); - dev->endpoint[EP2I(ep)].interrupt_started = 1; - /* We don't really want to drop interrupt packets ever, but - having some upper limit to how much we buffer is good. */ - dev->endpoint[EP2I(ep)].bufpq_target_size = 1000; - dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; - } + /* Input interrupt endpoint, buffered packet input */ + struct buf_packet *intp; + int status, len; - intp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq); - if (intp == NULL) { - DPRINTF2("interrupt-token-in ep %02X, no intp\n", ep); - /* Check interrupt_error for stream errors */ - status = dev->endpoint[EP2I(ep)].interrupt_error; - dev->endpoint[EP2I(ep)].interrupt_error = 0; - if (status) { - return usbredir_handle_status(dev, status, 0); - } - return USB_RET_NAK; - } - DPRINTF("interrupt-token-in ep %02X status %d len %d\n", ep, - intp->status, intp->len); + 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; + } - status = intp->status; - if (status != usb_redir_success) { - bufp_free(dev, intp, ep); - return usbredir_handle_status(dev, status, 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); - len = intp->len; - if (len > p->iov.size) { - ERROR("received int data is larger then packet ep %02X\n", ep); - bufp_free(dev, intp, ep); - return USB_RET_BABBLE; - } - usb_packet_copy(p, intp->data, len); - bufp_free(dev, intp, ep); - return len; - } else { - /* Output interrupt endpoint, normal async operation */ - AsyncURB *aurb = async_alloc(dev, p); - struct usb_redir_interrupt_packet_header interrupt_packet; - uint8_t buf[p->iov.size]; - - DPRINTF("interrupt-out ep %02X len %zd id %u\n", ep, p->iov.size, - aurb->packet_id); - - interrupt_packet.endpoint = ep; - interrupt_packet.length = p->iov.size; - aurb->interrupt_packet = interrupt_packet; - - usb_packet_copy(p, buf, p->iov.size); - usbredir_log_data(dev, "interrupt data out:", buf, p->iov.size); - usbredirparser_send_interrupt_packet(dev->parser, aurb->packet_id, - &interrupt_packet, buf, p->iov.size); - usbredirparser_do_write(dev->parser); - return USB_RET_ASYNC; + 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, @@ -609,7 +898,7 @@ static void usbredir_stop_interrupt_receiving(USBRedirDevice *dev, usbredir_free_bufpq(dev, ep); } -static int usbredir_handle_data(USBDevice *udev, USBPacket *p) +static void usbredir_handle_data(USBDevice *udev, USBPacket *p) { USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); uint8_t ep; @@ -622,142 +911,166 @@ static int usbredir_handle_data(USBDevice *udev, USBPacket *p) switch (dev->endpoint[EP2I(ep)].type) { case USB_ENDPOINT_XFER_CONTROL: ERROR("handle_data called for control transfer on ep %02X\n", ep); - return USB_RET_NAK; - case USB_ENDPOINT_XFER_ISOC: - return usbredir_handle_iso_data(dev, p, ep); + p->status = USB_RET_NAK; + break; case USB_ENDPOINT_XFER_BULK: - return usbredir_handle_bulk_data(dev, p, ep); + 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: - return usbredir_handle_interrupt_data(dev, p, ep); + 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); - return USB_RET_NAK; + 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 int usbredir_set_config(USBRedirDevice *dev, USBPacket *p, +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 = DO_UPCAST(USBRedirDevice, dev, 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; - AsyncURB *aurb = async_alloc(dev, p); int i; - DPRINTF("set config %d id %u\n", config, aurb->packet_id); + DPRINTF("set config %d id %"PRIu64"\n", config, p->id); for (i = 0; i < MAX_ENDPOINTS; i++) { - switch (dev->endpoint[i].type) { - case USB_ENDPOINT_XFER_ISOC: - usbredir_stop_iso_stream(dev, I2EP(i)); - break; - case USB_ENDPOINT_XFER_INT: - if (i & 0x10) { - usbredir_stop_interrupt_receiving(dev, I2EP(i)); - } - break; - } - usbredir_free_bufpq(dev, I2EP(i)); + usbredir_stop_ep(dev, i); } set_config.configuration = config; - usbredirparser_send_set_configuration(dev->parser, aurb->packet_id, - &set_config); + usbredirparser_send_set_configuration(dev->parser, p->id, &set_config); usbredirparser_do_write(dev->parser); - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } -static int usbredir_get_config(USBRedirDevice *dev, USBPacket *p) +static void usbredir_get_config(USBRedirDevice *dev, USBPacket *p) { - AsyncURB *aurb = async_alloc(dev, p); - - DPRINTF("get config id %u\n", aurb->packet_id); + DPRINTF("get config id %"PRIu64"\n", p->id); - aurb->get = 1; - usbredirparser_send_get_configuration(dev->parser, aurb->packet_id); + usbredirparser_send_get_configuration(dev->parser, p->id); usbredirparser_do_write(dev->parser); - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } -static int usbredir_set_interface(USBRedirDevice *dev, USBPacket *p, +static void usbredir_set_interface(USBRedirDevice *dev, USBPacket *p, int interface, int alt) { struct usb_redir_set_alt_setting_header set_alt; - AsyncURB *aurb = async_alloc(dev, p); int i; - DPRINTF("set interface %d alt %d id %u\n", interface, alt, - aurb->packet_id); + 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) { - switch (dev->endpoint[i].type) { - case USB_ENDPOINT_XFER_ISOC: - usbredir_stop_iso_stream(dev, I2EP(i)); - break; - case USB_ENDPOINT_XFER_INT: - if (i & 0x10) { - usbredir_stop_interrupt_receiving(dev, I2EP(i)); - } - break; - } - usbredir_free_bufpq(dev, I2EP(i)); + usbredir_stop_ep(dev, i); } } set_alt.interface = interface; set_alt.alt = alt; - usbredirparser_send_set_alt_setting(dev->parser, aurb->packet_id, - &set_alt); + usbredirparser_send_set_alt_setting(dev->parser, p->id, &set_alt); usbredirparser_do_write(dev->parser); - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } -static int usbredir_get_interface(USBRedirDevice *dev, USBPacket *p, +static void usbredir_get_interface(USBRedirDevice *dev, USBPacket *p, int interface) { struct usb_redir_get_alt_setting_header get_alt; - AsyncURB *aurb = async_alloc(dev, p); - DPRINTF("get interface %d id %u\n", interface, aurb->packet_id); + DPRINTF("get interface %d id %"PRIu64"\n", interface, p->id); get_alt.interface = interface; - aurb->get = 1; - usbredirparser_send_get_alt_setting(dev->parser, aurb->packet_id, - &get_alt); + usbredirparser_send_get_alt_setting(dev->parser, p->id, &get_alt); usbredirparser_do_write(dev->parser); - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } -static int usbredir_handle_control(USBDevice *udev, USBPacket *p, +static void usbredir_handle_control(USBDevice *udev, USBPacket *p, int request, int value, int index, int length, uint8_t *data) { USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); struct usb_redir_control_packet_header control_packet; - AsyncURB *aurb; + + 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 0; + return; case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: - return usbredir_set_config(dev, p, value & 0xff); + usbredir_set_config(dev, p, value & 0xff); + return; case DeviceRequest | USB_REQ_GET_CONFIGURATION: - return usbredir_get_config(dev, p); + usbredir_get_config(dev, p); + return; case InterfaceOutRequest | USB_REQ_SET_INTERFACE: - return usbredir_set_interface(dev, p, index, value); + usbredir_set_interface(dev, p, index, value); + return; case InterfaceRequest | USB_REQ_GET_INTERFACE: - return usbredir_get_interface(dev, p, index); + usbredir_get_interface(dev, p, index); + return; } - /* "Normal" ctrl requests */ - aurb = async_alloc(dev, p); - - /* Note request is (bRequestType << 8) | bRequest */ - DPRINTF("ctrl-out type 0x%x req 0x%x val 0x%x index %d len %d id %u\n", - request >> 8, request & 0xff, value, index, length, - aurb->packet_id); + /* 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; @@ -765,18 +1078,17 @@ static int usbredir_handle_control(USBDevice *udev, USBPacket *p, control_packet.value = value; control_packet.index = index; control_packet.length = length; - aurb->control_packet = control_packet; if (control_packet.requesttype & USB_DIR_IN) { - usbredirparser_send_control_packet(dev->parser, aurb->packet_id, + 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, aurb->packet_id, + usbredirparser_send_control_packet(dev->parser, p->id, &control_packet, data, length); } usbredirparser_do_write(dev->parser); - return USB_RET_ASYNC; + p->status = USB_RET_ASYNC; } /* @@ -784,53 +1096,77 @@ static int usbredir_handle_control(USBDevice *udev, USBPacket *p, * from within the USBDevice data / control packet callbacks and doing a * usb_detach from within these callbacks is not a good idea. * - * So we use a bh handler to take care of close events. We also handle - * open events from this callback to make sure that a close directly followed - * by an open gets handled in the right order. + * So we use a bh handler to take care of close events. */ -static void usbredir_open_close_bh(void *opaque) +static void usbredir_chardev_close_bh(void *opaque) { USBRedirDevice *dev = opaque; - uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; - char version[32]; - - strcpy(version, "qemu usb-redir guest "); - pstrcat(version, sizeof(version), qemu_get_version()); 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; + } +} - if (dev->cs->opened) { - dev->parser = qemu_oom_check(usbredirparser_create()); - dev->parser->priv = dev; - dev->parser->log_func = usbredir_log; - dev->parser->read_func = usbredir_read; - dev->parser->write_func = usbredir_write; - dev->parser->hello_func = usbredir_hello; - dev->parser->device_connect_func = usbredir_device_connect; - dev->parser->device_disconnect_func = usbredir_device_disconnect; - dev->parser->interface_info_func = usbredir_interface_info; - dev->parser->ep_info_func = usbredir_ep_info; - dev->parser->configuration_status_func = usbredir_configuration_status; - dev->parser->alt_setting_status_func = usbredir_alt_setting_status; - dev->parser->iso_stream_status_func = usbredir_iso_stream_status; - dev->parser->interrupt_receiving_status_func = - usbredir_interrupt_receiving_status; - dev->parser->bulk_streams_status_func = usbredir_bulk_streams_status; - dev->parser->control_packet_func = usbredir_control_packet; - dev->parser->bulk_packet_func = usbredir_bulk_packet; - dev->parser->iso_packet_func = usbredir_iso_packet; - dev->parser->interrupt_packet_func = usbredir_interrupt_packet; - dev->read_buf = NULL; - dev->read_buf_size = 0; +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 (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); +} - usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); - usbredirparser_caps_set_cap(caps, usb_redir_cap_filter); - usbredirparser_init(dev->parser, version, caps, USB_REDIR_CAPS_SIZE, 0); +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); } } @@ -839,12 +1175,22 @@ static void usbredir_do_attach(void *opaque) { USBRedirDevice *dev = opaque; + /* 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; + } + if (usb_device_attach(&dev->dev) != 0) { - 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); - } + WARNING("rejecting device due to speed mismatch\n"); + usbredir_reject_device(dev); } } @@ -856,13 +1202,18 @@ static int usbredir_chardev_can_read(void *opaque) { USBRedirDevice *dev = opaque; - if (dev->parser) { - /* usbredir_parser_do_read will consume *all* data we give it */ - return 1024 * 1024; - } else { - /* usbredir_open_close_bh hasn't handled the open event yet */ + 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) @@ -886,8 +1237,15 @@ static void usbredir_chardev_event(void *opaque, int event) 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: - qemu_bh_schedule(dev->open_close_bh); + DPRINTF("chardev close\n"); + qemu_bh_schedule(dev->chardev_close_bh); break; } } @@ -896,6 +1254,27 @@ static void usbredir_chardev_event(void *opaque, int event) * 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 int usbredir_initfn(USBDevice *udev) { USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); @@ -917,34 +1296,34 @@ static int usbredir_initfn(USBDevice *udev) } } - dev->open_close_bh = qemu_bh_new(usbredir_open_close_bh, dev); + dev->chardev_close_bh = qemu_bh_new(usbredir_chardev_close_bh, dev); dev->attach_timer = qemu_new_timer_ms(vm_clock, usbredir_do_attach, dev); - QTAILQ_INIT(&dev->asyncq); - for (i = 0; i < MAX_ENDPOINTS; i++) { - QTAILQ_INIT(&dev->endpoint[i].bufpq); - } + packet_id_queue_init(&dev->cancelled, dev, "cancelled"); + packet_id_queue_init(&dev->already_in_flight, dev, "already-in-flight"); + usbredir_init_endpoints(dev); /* We'll do the attach once we receive the speed from the usb-host */ udev->auto_attach = 0; + /* Will be cleared during setup when we find conflicts */ + dev->compatible_speedmask = USB_SPEED_MASK_FULL | USB_SPEED_MASK_HIGH; + /* Let the backend know we are ready */ - qemu_chr_fe_open(dev->cs); 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); add_boot_device_path(dev->bootindex, &udev->qdev, NULL); return 0; } static void usbredir_cleanup_device_queues(USBRedirDevice *dev) { - AsyncURB *aurb, *next_aurb; int i; - QTAILQ_FOREACH_SAFE(aurb, &dev->asyncq, next, next_aurb) { - async_free(dev, aurb); - } + 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)); } @@ -954,10 +1333,9 @@ static void usbredir_handle_destroy(USBDevice *udev) { USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); - qemu_chr_fe_close(dev->cs); qemu_chr_delete(dev->cs); /* Note must be done after qemu_chr_close, as that causes a close event */ - qemu_bh_delete(dev->open_close_bh); + qemu_bh_delete(dev->chardev_close_bh); qemu_del_timer(dev->attach_timer); qemu_free_timer(dev->attach_timer); @@ -967,6 +1345,9 @@ static void usbredir_handle_destroy(USBDevice *udev) if (dev->parser) { usbredirparser_destroy(dev->parser); } + if (dev->watch) { + g_source_remove(dev->watch); + } free(dev->filter_rules); } @@ -1007,38 +1388,88 @@ static int usbredir_check_filter(USBRedirDevice *dev) return 0; error: - usbredir_device_disconnect(dev); - if (usbredirparser_peer_has_cap(dev->parser, usb_redir_cap_filter)) { - usbredirparser_send_filter_reject(dev->parser); - usbredirparser_do_write(dev->parser); - } + 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 int usbredir_handle_status(USBRedirDevice *dev, - int status, int actual_len) +static void usbredir_handle_status(USBRedirDevice *dev, USBPacket *p, + int status) { switch (status) { case usb_redir_success: - return actual_len; + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + break; case usb_redir_stall: - return USB_RET_STALL; + p->status = USB_RET_STALL; + break; case usb_redir_cancelled: - WARNING("returning cancelled packet to HC?\n"); - return USB_RET_NAK; + /* + * 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"); - return USB_RET_NAK; + p->status = USB_RET_IOERROR; + break; case usb_redir_babble: - return USB_RET_BABBLE; + p->status = USB_RET_BABBLE; + break; case usb_redir_ioerror: case usb_redir_timeout: default: - return USB_RET_IOERROR; + p->status = USB_RET_IOERROR; } } @@ -1070,10 +1501,13 @@ static void usbredir_device_connect(void *priv, 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"; @@ -1103,7 +1537,7 @@ static void usbredir_device_connect(void *priv, device_connect->device_class); } - dev->dev.speedmask = (1 << dev->dev.speed); + dev->dev.speedmask = (1 << dev->dev.speed) | dev->compatible_speedmask; dev->device_info = *device_connect; if (usbredir_check_filter(dev)) { @@ -1112,18 +1546,19 @@ static void usbredir_device_connect(void *priv, return; } + usbredir_check_bulk_receiving(dev); qemu_mod_timer(dev->attach_timer, dev->next_attach_time); } static void usbredir_device_disconnect(void *priv) { USBRedirDevice *dev = priv; - int i; /* Stop any pending attaches */ qemu_del_timer(dev->attach_timer); if (dev->dev.attached) { + DPRINTF("detaching device\n"); usb_device_detach(&dev->dev); /* * Delay next usb device attach to give the guest a chance to see @@ -1134,14 +1569,11 @@ static void usbredir_device_disconnect(void *priv) /* Reset state so that the next dev connected starts with a clean slate */ usbredir_cleanup_device_queues(dev); - memset(dev->endpoint, 0, sizeof(dev->endpoint)); - for (i = 0; i < MAX_ENDPOINTS; i++) { - QTAILQ_INIT(&dev->endpoint[i].bufpq); - } - usb_ep_init(&dev->dev); + 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, @@ -1153,9 +1585,10 @@ static void usbredir_interface_info(void *priv, /* * If we receive interface info after the device has already been - * connected (ie on a set_config), re-check the filter. + * connected (ie on a set_config), re-check interface dependent things. */ if (qemu_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"); @@ -1163,25 +1596,77 @@ static void usbredir_interface_info(void *priv, } } +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; + usbredir_set_pipeline(dev, usb_ep); + } +} + static void usbredir_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info) { USBRedirDevice *dev = priv; - struct USBEndpoint *usb_ep; int i; for (i = 0; i < MAX_ENDPOINTS; i++) { dev->endpoint[i].type = ep_info->type[i]; dev->endpoint[i].interval = ep_info->interval[i]; dev->endpoint[i].interface = ep_info->interface[i]; + 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]; + } 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_device_disconnect(dev); + usbredir_reject_device(dev); + return; } /* Fall through */ case usb_redir_type_control: @@ -1191,78 +1676,70 @@ static void usbredir_ep_info(void *priv, break; default: ERROR("Received invalid endpoint type\n"); - usbredir_device_disconnect(dev); + usbredir_reject_device(dev); return; } - usb_ep = usb_ep_get(&dev->dev, - (i & 0x10) ? USB_TOKEN_IN : USB_TOKEN_OUT, - i & 0x0f); - usb_ep->type = dev->endpoint[i].type; - usb_ep->ifnum = dev->endpoint[i].interface; } + /* 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, uint32_t id, +static void usbredir_configuration_status(void *priv, uint64_t id, struct usb_redir_configuration_status_header *config_status) { USBRedirDevice *dev = priv; - AsyncURB *aurb; - int len = 0; + USBPacket *p; - DPRINTF("set config status %d config %d id %u\n", config_status->status, - config_status->configuration, id); + DPRINTF("set config status %d config %d id %"PRIu64"\n", + config_status->status, config_status->configuration, id); - aurb = async_find(dev, id); - if (!aurb) { - return; - } - if (aurb->packet) { - if (aurb->get) { + 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; - len = 1; + p->actual_length = 1; } - aurb->packet->result = - usbredir_handle_status(dev, config_status->status, len); - usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + usbredir_handle_status(dev, p, config_status->status); + usb_generic_async_ctrl_complete(&dev->dev, p); } - async_free(dev, aurb); } -static void usbredir_alt_setting_status(void *priv, uint32_t id, +static void usbredir_alt_setting_status(void *priv, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status) { USBRedirDevice *dev = priv; - AsyncURB *aurb; - int len = 0; + USBPacket *p; - DPRINTF("alt status %d intf %d alt %d id: %u\n", - alt_setting_status->status, - alt_setting_status->interface, + DPRINTF("alt status %d intf %d alt %d id: %"PRIu64"\n", + alt_setting_status->status, alt_setting_status->interface, alt_setting_status->alt, id); - aurb = async_find(dev, id); - if (!aurb) { - return; - } - if (aurb->packet) { - if (aurb->get) { + 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; - len = 1; + p->actual_length = 1; } - aurb->packet->result = - usbredir_handle_status(dev, alt_setting_status->status, len); - usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + usbredir_handle_status(dev, p, alt_setting_status->status); + usb_generic_async_ctrl_complete(&dev->dev, p); } - async_free(dev, aurb); } -static void usbredir_iso_stream_status(void *priv, uint32_t id, +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 %u\n", iso_stream_status->status, + 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) { @@ -1276,14 +1753,14 @@ static void usbredir_iso_stream_status(void *priv, uint32_t id, } } -static void usbredir_interrupt_receiving_status(void *priv, uint32_t id, +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 %u\n", + 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) { @@ -1298,107 +1775,116 @@ static void usbredir_interrupt_receiving_status(void *priv, uint32_t id, } } -static void usbredir_bulk_streams_status(void *priv, uint32_t id, +static void usbredir_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status) { } -static void usbredir_control_packet(void *priv, uint32_t id, - struct usb_redir_control_packet_header *control_packet, - uint8_t *data, int data_len) +static void usbredir_bulk_receiving_status(void *priv, uint64_t id, + struct usb_redir_bulk_receiving_status_header *bulk_receiving_status) { USBRedirDevice *dev = priv; - int len = control_packet->length; - AsyncURB *aurb; + uint8_t ep = bulk_receiving_status->endpoint; - DPRINTF("ctrl-in status %d len %d id %u\n", control_packet->status, - len, id); + DPRINTF("bulk recv status %d ep %02X id %"PRIu64"\n", + bulk_receiving_status->status, ep, id); - aurb = async_find(dev, id); - if (!aurb) { - free(data); + if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].bulk_receiving_started) { return; } - aurb->control_packet.status = control_packet->status; - aurb->control_packet.length = control_packet->length; - if (memcmp(&aurb->control_packet, control_packet, - sizeof(*control_packet))) { - ERROR("return control packet mismatch, please report this!\n"); - len = USB_RET_NAK; + if (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; } +} - if (aurb->packet) { - len = usbredir_handle_status(dev, control_packet->status, len); - if (len > 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)) { - memcpy(dev->dev.data_buf, data, data_len); - } else { + if (data_len > sizeof(dev->dev.data_buf)) { ERROR("ctrl buffer too small (%d > %zu)\n", data_len, sizeof(dev->dev.data_buf)); - len = USB_RET_STALL; + p->status = USB_RET_STALL; + data_len = len = sizeof(dev->dev.data_buf); } + memcpy(dev->dev.data_buf, data, data_len); } - aurb->packet->result = len; - usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + p->actual_length = len; + usb_generic_async_ctrl_complete(&dev->dev, p); } - async_free(dev, aurb); free(data); } -static void usbredir_bulk_packet(void *priv, uint32_t id, +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; - AsyncURB *aurb; - - DPRINTF("bulk-in status %d ep %02X len %d id %u\n", bulk_packet->status, - ep, len, id); + int len = (bulk_packet->length_high << 16) | bulk_packet->length; + USBPacket *p; - aurb = async_find(dev, id); - if (!aurb) { - free(data); - return; - } + DPRINTF("bulk-in status %d ep %02X len %d id %"PRIu64"\n", + bulk_packet->status, ep, len, id); - if (aurb->bulk_packet.endpoint != bulk_packet->endpoint || - aurb->bulk_packet.stream_id != bulk_packet->stream_id) { - ERROR("return bulk packet mismatch, please report this!\n"); - len = USB_RET_NAK; - } - - if (aurb->packet) { - len = usbredir_handle_status(dev, bulk_packet->status, len); - if (len > 0) { + 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 <= aurb->packet->iov.size) { - usb_packet_copy(aurb->packet, data, data_len); - } else { - ERROR("bulk buffer too small (%d > %zd)\n", data_len, - aurb->packet->iov.size); - len = USB_RET_STALL; + 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); } - aurb->packet->result = len; - usb_packet_complete(&dev->dev, aurb->packet); } - async_free(dev, aurb); free(data); } -static void usbredir_iso_packet(void *priv, uint32_t id, +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 %u\n", iso_packet->status, ep, - data_len, id); + 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); @@ -1413,17 +1899,17 @@ static void usbredir_iso_packet(void *priv, uint32_t id, } /* bufp_alloc also adds the packet to the ep queue */ - bufp_alloc(dev, data, data_len, iso_packet->status, ep); + bufp_alloc(dev, data, data_len, iso_packet->status, ep, data); } -static void usbredir_interrupt_packet(void *priv, uint32_t id, +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 %u\n", + 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) { @@ -1439,33 +1925,422 @@ static void usbredir_interrupt_packet(void *priv, uint32_t id, 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); + bufp_alloc(dev, data, data_len, interrupt_packet->status, ep, data); } else { - int len = interrupt_packet->length; - - AsyncURB *aurb = async_find(dev, id); - if (!aurb) { - return; + /* + * 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; - if (aurb->interrupt_packet.endpoint != interrupt_packet->endpoint) { - ERROR("return int packet mismatch, please report this!\n"); - len = USB_RET_NAK; + 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); +} - if (aurb->packet) { - aurb->packet->result = usbredir_handle_status(dev, - interrupt_packet->status, len); - usb_packet_complete(&dev->dev, aurb->packet); +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 const VMStateDescription usbredir_bulk_receiving_vmstate = { + .name = "usb-redir-ep/bulk-receiving", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(bulk_receiving_started, struct endp_data), + VMSTATE_END_OF_LIST() + } +}; + +static bool usbredir_bulk_receiving_needed(void *priv) +{ + struct endp_data *endp = priv; + + return endp->bulk_receiving_started; +} + +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 = (VMStateSubsection[]) { + { + .vmsd = &usbredir_bulk_receiving_vmstate, + .needed = usbredir_bulk_receiving_needed, + }, { + /* empty */ } - async_free(dev, aurb); } +}; + + +/* 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(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, 0), + DEFINE_PROP_UINT8("debug", USBRedirDevice, debug, usbredirparser_warning), DEFINE_PROP_STRING("filter", USBRedirDevice, filter_str), DEFINE_PROP_INT32("bootindex", USBRedirDevice, bootindex, -1), DEFINE_PROP_END_OF_LIST(), @@ -1483,10 +2358,13 @@ static void usbredir_class_initfn(ObjectClass *klass, void *data) 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; + dc->vmsd = &usbredir_vmstate; dc->props = usbredir_properties; } -static TypeInfo usbredir_dev_info = { +static const TypeInfo usbredir_dev_info = { .name = "usb-redir", .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBRedirDevice), |