summaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2012-11-01 14:34:13 -0500
committerAnthony Liguori <aliguori@us.ibm.com>2012-11-01 14:34:13 -0500
commitde0a36cd01dab524ee6507c1bd644f0775d341fe (patch)
tree63d866091c8a69c8bb12dbb34132cd07f9ade7a3 /hw
parent4ba79505f43bd0ace35c3fe42197eb02e7e0478e (diff)
parent95a59dc03988d190bbc72d980a149be3a79592fb (diff)
downloadqemu-de0a36cd01dab524ee6507c1bd644f0775d341fe.zip
Merge remote-tracking branch 'kraxel/usb.69' into staging
* kraxel/usb.69: (31 commits) usb-redir: Allow redirecting super speed devices to high speed controllers usb-redir: Allow to attach USB 2.0 devices to 1.1 host controller usb-redir: Use reject rather the disconnect on bad ep info usb-redir: Add an usbredir_setup_usb_eps() helper function usb-redir: Add support for input pipelining usb-redir: Add support for 32 bits bulk packet length combined-packet: Add a workaround for Linux usbfs + live migration usb: Add packet combining functions uhci: Don't crash on device disconnect uhci: Add a uhci_handle_td_error() helper function usb/ehci-pci: add helper to create ich9 usb controllers usb/ehci-pci: add ich9 00:1a.* variant usb/ehci-pci: dynamic type generation uhci: add ich9 00:1a.* variants uhci: stick irq routing info into UHCIInfo too. uhci: dynamic type generation xilinx_zynq: add USB controllers usb/ehci: add sysbus variant usb/ehci: split into multiple source files usb/ehci: Guard definition of EHCI_DEBUG ... Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'hw')
-rw-r--r--hw/usb.h15
-rw-r--r--hw/usb/Makefile.objs4
-rw-r--r--hw/usb/combined-packet.c182
-rw-r--r--hw/usb/core.c1
-rw-r--r--hw/usb/hcd-ehci-pci.c200
-rw-r--r--hw/usb/hcd-ehci-sysbus.c77
-rw-r--r--hw/usb/hcd-ehci.c465
-rw-r--r--hw/usb/hcd-ehci.h320
-rw-r--r--hw/usb/hcd-uhci.c344
-rw-r--r--hw/usb/hcd-xhci.c134
-rw-r--r--hw/usb/redirect.c167
-rw-r--r--hw/xilinx_zynq.c3
12 files changed, 1232 insertions, 680 deletions
diff --git a/hw/usb.h b/hw/usb.h
index 3a6cc8444a..aef07f4a07 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -160,6 +160,7 @@ typedef struct USBBusOps USBBusOps;
typedef struct USBPort USBPort;
typedef struct USBDevice USBDevice;
typedef struct USBPacket USBPacket;
+typedef struct USBCombinedPacket USBCombinedPacket;
typedef struct USBEndpoint USBEndpoint;
typedef struct USBDesc USBDesc;
@@ -356,7 +357,15 @@ struct USBPacket {
int result; /* transfer length or USB_RET_* status code */
/* Internal use by the USB layer. */
USBPacketState state;
+ USBCombinedPacket *combined;
QTAILQ_ENTRY(USBPacket) queue;
+ QTAILQ_ENTRY(USBPacket) combined_entry;
+};
+
+struct USBCombinedPacket {
+ USBPacket *first;
+ QTAILQ_HEAD(packets_head, USBPacket) packets;
+ QEMUIOVector iov;
};
void usb_packet_init(USBPacket *p);
@@ -399,6 +408,10 @@ void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled);
USBPacket *usb_ep_find_packet_by_id(USBDevice *dev, int pid, int ep,
uint64_t id);
+void usb_ep_combine_input_packets(USBEndpoint *ep);
+void usb_combined_input_packet_complete(USBDevice *dev, USBPacket *p);
+void usb_combined_packet_cancel(USBDevice *dev, USBPacket *p);
+
void usb_attach(USBPort *port);
void usb_detach(USBPort *port);
void usb_port_reset(USBPort *port);
@@ -524,5 +537,7 @@ const char *usb_device_get_product_desc(USBDevice *dev);
const USBDesc *usb_device_get_usb_desc(USBDevice *dev);
+int ehci_create_ich9_with_companions(PCIBus *bus, int slot);
+
#endif
diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs
index 6425c1ff73..5a4eeb6d13 100644
--- a/hw/usb/Makefile.objs
+++ b/hw/usb/Makefile.objs
@@ -1,13 +1,13 @@
common-obj-$(CONFIG_USB_UHCI) += hcd-uhci.o
common-obj-$(CONFIG_USB_OHCI) += hcd-ohci.o
-common-obj-$(CONFIG_USB_EHCI) += hcd-ehci.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-y += libhw.o
common-obj-$(CONFIG_SMARTCARD) += dev-smartcard-reader.o
common-obj-$(CONFIG_USB_REDIR) += redirect.o
-common-obj-y += core.o bus.o desc.o dev-hub.o
+common-obj-y += core.o combined-packet.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
diff --git a/hw/usb/combined-packet.c b/hw/usb/combined-packet.c
new file mode 100644
index 0000000000..3904e71634
--- /dev/null
+++ b/hw/usb/combined-packet.c
@@ -0,0 +1,182 @@
+/*
+ * 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 "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;
+}
+
+static void usb_combined_packet_remove(USBCombinedPacket *combined,
+ USBPacket *p)
+{
+ assert(p->combined == combined);
+ p->combined = NULL;
+ QTAILQ_REMOVE(&combined->packets, p, combined_entry);
+}
+
+/* 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;
+ enum { completing, complete, leftover };
+ int result, state = completing;
+ bool short_not_ok;
+
+ if (combined == NULL) {
+ usb_packet_complete_one(dev, p);
+ goto leave;
+ }
+
+ assert(combined->first == p && p == QTAILQ_FIRST(&combined->packets));
+
+ result = combined->first->result;
+ short_not_ok = QTAILQ_LAST(&combined->packets, packets_head)->short_not_ok;
+
+ QTAILQ_FOREACH_SAFE(p, &combined->packets, combined_entry, next) {
+ if (state == completing) {
+ /* Distribute data over uncombined packets */
+ if (result >= p->iov.size) {
+ p->result = p->iov.size;
+ } else {
+ /* Send short or error packet to complete the transfer */
+ p->result = result;
+ state = complete;
+ }
+ p->short_not_ok = short_not_ok;
+ usb_combined_packet_remove(combined, p);
+ usb_packet_complete_one(dev, p);
+ result -= p->result;
+ } else {
+ /* Remove any leftover packets from the queue */
+ state = leftover;
+ p->result = USB_RET_REMOVE_FROM_QUEUE;
+ dev->port->ops->complete(dev->port, p);
+ }
+ }
+ /*
+ * If we had leftover packets the hcd driver will have cancelled them
+ * and usb_combined_packet_cancel has already freed combined!
+ */
+ if (state != leftover) {
+ g_free(combined);
+ }
+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);
+
+ usb_combined_packet_remove(combined, p);
+ if (p == combined->first) {
+ usb_device_cancel_packet(dev, p);
+ }
+ if (QTAILQ_EMPTY(&combined->packets)) {
+ g_free(combined);
+ }
+}
+
+/*
+ * 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 ret, 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->result = 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)) {
+ ret = usb_device_handle_data(ep->dev, first);
+ assert(ret == 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 632a8efe47..ab37f6f7b8 100644
--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -545,6 +545,7 @@ void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep, uint64_t id,
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);
}
diff --git a/hw/usb/hcd-ehci-pci.c b/hw/usb/hcd-ehci-pci.c
new file mode 100644
index 0000000000..d5c7d462d0
--- /dev/null
+++ b/hw/usb/hcd-ehci-pci.c
@@ -0,0 +1,200 @@
+/*
+ * 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 "hw/pci.h"
+
+typedef struct EHCIPCIState {
+ PCIDevice pcidev;
+ EHCIState ehci;
+} EHCIPCIState;
+
+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 = DO_UPCAST(EHCIPCIState, pcidev, 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 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),
+ }
+};
+
+static void ehci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ EHCIPCIInfo *i = data;
+
+ k->init = usb_ehci_pci_initfn;
+ k->vendor_id = i->vendor_id;
+ k->device_id = i->device_id;
+ k->revision = i->revision;
+ k->class_id = PCI_CLASS_SERIAL_USB;
+ dc->vmsd = &vmstate_ehci;
+ dc->props = ehci_pci_properties;
+}
+
+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_DEVICE,
+ .instance_size = sizeof(EHCIPCIState),
+ .class_init = ehci_class_init,
+ };
+ int i;
+
+ 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..1584079796
--- /dev/null
+++ b/hw/usb/hcd-ehci-sysbus.c
@@ -0,0 +1,77 @@
+/*
+ * 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 "hw/sysbus.h"
+
+typedef struct EHCISysBusState {
+ SysBusDevice busdev;
+ EHCIState ehci;
+} EHCISysBusState;
+
+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 = FROM_SYSBUS(EHCISysBusState, dev);
+ EHCIState *s = &i->ehci;
+
+ s->capsbase = 0x100;
+ s->opregbase = 0x140;
+
+ 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;
+}
+
+TypeInfo ehci_xlnx_type_info = {
+ .name = "xlnx,ps7-usb",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(EHCISysBusState),
+ .class_init = ehci_sysbus_class_init,
+};
+
+static void ehci_sysbus_register_types(void)
+{
+ type_register_static(&ehci_xlnx_type_info);
+}
+
+type_init(ehci_sysbus_register_types)
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index f14f9d7018..d9dc576e7c 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -27,41 +27,21 @@
* 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"
-#include "sysemu.h"
-
-#define EHCI_DEBUG 0
-
-#if EHCI_DEBUG
-#define DPRINTF printf
-#else
-#define DPRINTF(...)
-#endif
+#include "hw/usb/hcd-ehci.h"
/* internal processing - reset HC to try and recover */
#define USB_RET_PROCERR (-99)
-#define MMIO_SIZE 0x1000
-
/* Capability Registers Base Address - section 2.2 */
-#define CAPREGBASE 0x0000
-#define CAPLENGTH CAPREGBASE + 0x0000 // 1-byte, 0x0001 reserved
-#define HCIVERSION CAPREGBASE + 0x0002 // 2-bytes, i/f version #
-#define HCSPARAMS CAPREGBASE + 0x0004 // 4-bytes, structural params
-#define HCCPARAMS CAPREGBASE + 0x0008 // 4-bytes, capability params
+#define 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 OPREGBASE 0x0020 // Operational Registers Base Address
+#define HCSPPORTROUTE1 0x000c
+#define HCSPPORTROUTE2 0x0010
-#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
@@ -75,7 +55,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
@@ -92,20 +72,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
@@ -137,7 +114,6 @@
#define FRAME_TIMER_NS (1000000000 / FRAME_TIMER_FREQ)
#define NB_MAXINTRATE 8 // Max rate at which controller issues ints
-#define NB_PORTS 6 // Number of downstream ports
#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
#define MAX_QH 100 // Max allowable queue heads in a chain
#define MIN_FR_PER_TICK 3 // Min frames to process when catching up
@@ -174,278 +150,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_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;
- int usb_status;
-};
-
-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 */
- USBDevice *dev;
- QTAILQ_HEAD(pkts_head, EHCIPacket) packets;
-};
-
-typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead;
-
-struct EHCIState {
- PCIDevice dev;
- USBBus bus;
- qemu_irq irq;
- MemoryRegion mem;
- MemoryRegion mem_caps;
- MemoryRegion mem_opreg;
- MemoryRegion mem_ports;
- int companion_count;
-
- /* properties */
- uint32_t maxframes;
-
- /*
- * EHCI spec version 1.0 Section 2.3
- * Host Controller Operational Registers
- */
- uint8_t caps[OPREGBASE];
- union {
- uint32_t opreg[(PORTSC_BEGIN-OPREGBASE)/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;
- bool int_req_by_async;
-};
-
#define SET_LAST_RUN_CLOCK(s) \
(s)->last_run_ns = qemu_get_clock_ns(vm_clock);
@@ -506,8 +210,7 @@ static const char *state2str(uint32_t state)
static const char *addr2str(hwaddr addr)
{
- return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names),
- addr + OPREGBASE);
+ return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr);
}
static void ehci_trace_usbsts(uint32_t mask, int state)
@@ -1115,7 +818,7 @@ static uint64_t ehci_opreg_read(void *ptr, hwaddr addr,
uint32_t val;
val = s->opreg[addr >> 2];
- trace_usb_ehci_opreg_read(addr + OPREGBASE, addr2str(addr), val);
+ trace_usb_ehci_opreg_read(addr + s->opregbase, addr2str(addr), val);
return val;
}
@@ -1211,9 +914,9 @@ static void ehci_opreg_write(void *ptr, hwaddr addr,
uint32_t old = *mmio;
int i;
- trace_usb_ehci_opreg_write(addr + OPREGBASE, addr2str(addr), val);
+ trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val);
- switch (addr + OPREGBASE) {
+ switch (addr) {
case USBCMD:
if (val & USBCMD_HCRESET) {
ehci_reset(s);
@@ -1291,7 +994,8 @@ static void ehci_opreg_write(void *ptr, hwaddr addr,
}
*mmio = val;
- trace_usb_ehci_opreg_change(addr + OPREGBASE, addr2str(addr), *mmio, old);
+ trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr),
+ *mmio, old);
}
@@ -1304,7 +1008,7 @@ static inline int get_dwords(EHCIState *ehci, uint32_t addr,
int i;
for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
- pci_dma_read(&ehci->dev, addr, buf, sizeof(*buf));
+ dma_memory_read(ehci->dma, addr, buf, sizeof(*buf));
*buf = le32_to_cpu(*buf);
}
@@ -1319,7 +1023,7 @@ static inline int put_dwords(EHCIState *ehci, uint32_t addr,
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));
+ dma_memory_write(ehci->dma, addr, &tmp, sizeof(tmp));
}
return 1;
@@ -1402,7 +1106,7 @@ 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) {
@@ -1647,7 +1351,7 @@ static int ehci_process_itd(EHCIState *ehci,
return USB_RET_PROCERR;
}
- 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;
@@ -2402,7 +2106,7 @@ static void ehci_advance_periodic_state(EHCIState *ehci)
}
list |= ((ehci->frindex & 0x1ff8) >> 1);
- pci_dma_read(&ehci->dev, list, &entry, sizeof entry);
+ dma_memory_read(ehci->dma, list, &entry, sizeof entry);
entry = le32_to_cpu(entry);
DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
@@ -2552,8 +2256,6 @@ static const MemoryRegionOps ehci_mmio_port_ops = {
.endianness = DEVICE_LITTLE_ENDIAN,
};
-static int usb_ehci_initfn(PCIDevice *dev);
-
static USBPortOps ehci_port_ops = {
.attach = ehci_attach,
.detach = ehci_detach,
@@ -2612,13 +2314,12 @@ static void usb_ehci_vm_state_change(void *opaque, int running, RunState state)
}
}
-static const VMStateDescription vmstate_ehci = {
- .name = "ehci",
+const VMStateDescription vmstate_ehci = {
+ .name = "ehci-core",
.version_id = 2,
.minimum_version_id = 1,
.post_load = usb_ehci_post_load,
.fields = (VMStateField[]) {
- VMSTATE_PCI_DEVICE(dev, EHCIState),
/* mmio registers */
VMSTATE_UINT32(usbcmd, EHCIState),
VMSTATE_UINT32(usbsts, EHCIState),
@@ -2649,90 +2350,12 @@ 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)
+void usb_ehci_initfn(EHCIState *s, DeviceState *dev)
{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_ehci_initfn;
- k->vendor_id = PCI_VENDOR_ID_INTEL;
- k->device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI1;
- k->revision = 0x03;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_ehci;
- dc->props = ehci_properties;
-}
-
-static TypeInfo ich9_ehci_info = {
- .name = "ich9-usb-ehci1",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(EHCIState),
- .class_init = ich9_ehci_class_init,
-};
-
-static int usb_ehci_initfn(PCIDevice *dev)
-{
- EHCIState *s = DO_UPCAST(EHCIState, dev, dev);
- uint8_t *pci_conf = s->dev.config;
int i;
- pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20);
-
- /* capabilities pointer */
- pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x00);
- //pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x50);
-
- pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); /* interrupt pin D */
- pci_set_byte(&pci_conf[PCI_MIN_GNT], 0);
- pci_set_byte(&pci_conf[PCI_MAX_LAT], 0);
-
- // pci_conf[0x50] = 0x01; // power management caps
-
- pci_set_byte(&pci_conf[USB_SBRN], USB_RELEASE_2); // release number (2.1.4)
- pci_set_byte(&pci_conf[0x61], 0x20); // frame length adjustment (2.1.5)
- pci_set_word(&pci_conf[0x62], 0x00); // port wake up capability (2.1.6)
-
- pci_conf[0x64] = 0x00;
- pci_conf[0x65] = 0x00;
- pci_conf[0x66] = 0x00;
- pci_conf[0x67] = 0x00;
- pci_conf[0x68] = 0x01;
- pci_conf[0x69] = 0x00;
- pci_conf[0x6a] = 0x00;
- pci_conf[0x6b] = 0x00; // USBLEGSUP
- pci_conf[0x6c] = 0x00;
- pci_conf[0x6d] = 0x00;
- pci_conf[0x6e] = 0x00;
- pci_conf[0x6f] = 0xc0; // USBLEFCTLSTS
-
/* 2.2 host controller interface version */
- s->caps[0x00] = (uint8_t) OPREGBASE;
+ s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase);
s->caps[0x01] = 0x00;
s->caps[0x02] = 0x00;
s->caps[0x03] = 0x01; /* HC version */
@@ -2741,13 +2364,10 @@ static int usb_ehci_initfn(PCIDevice *dev)
s->caps[0x06] = 0x00;
s->caps[0x07] = 0x00;
s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */
- s->caps[0x09] = 0x68; /* EECP */
s->caps[0x0a] = 0x00;
s->caps[0x0b] = 0x00;
- s->irq = s->dev.irq[3];
-
- usb_bus_new(&s->bus, &ehci_bus_ops, &s->dev.qdev);
+ 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);
@@ -2765,29 +2385,18 @@ static int usb_ehci_initfn(PCIDevice *dev)
memory_region_init(&s->mem, "ehci", MMIO_SIZE);
memory_region_init_io(&s->mem_caps, &ehci_mmio_caps_ops, s,
- "capabilities", OPREGBASE);
+ "capabilities", CAPA_SIZE);
memory_region_init_io(&s->mem_opreg, &ehci_mmio_opreg_ops, s,
- "operational", PORTSC_BEGIN - OPREGBASE);
+ "operational", PORTSC_BEGIN);
memory_region_init_io(&s->mem_ports, &ehci_mmio_port_ops, s,
"ports", PORTSC_END - PORTSC_BEGIN);
- memory_region_add_subregion(&s->mem, 0, &s->mem_caps);
- memory_region_add_subregion(&s->mem, OPREGBASE, &s->mem_opreg);
- memory_region_add_subregion(&s->mem, PORTSC_BEGIN, &s->mem_ports);
-
- pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);
-
- return 0;
+ 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);
}
-static void ehci_register_types(void)
-{
- type_register_static(&ehci_info);
- type_register_static(&ich9_ehci_info);
-}
-
-type_init(ehci_register_types)
-
/*
* vim: expandtab ts=4
*/
diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
new file mode 100644
index 0000000000..0ec675c352
--- /dev/null
+++ b/hw/usb/hcd-ehci.h
@@ -0,0 +1,320 @@
+/*
+ * 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/hw.h"
+#include "qemu-timer.h"
+#include "hw/usb.h"
+#include "monitor.h"
+#include "trace.h"
+#include "dma.h"
+#include "sysemu.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;
+ int usb_status;
+};
+
+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 */
+ 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;
+ bool int_req_by_async;
+};
+
+extern const VMStateDescription vmstate_ehci;
+
+void usb_ehci_initfn(EHCIState *s, DeviceState *dev);
diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c
index b6b972fa92..f4b555addc 100644
--- a/hw/usb/hcd-uhci.c
+++ b/hw/usb/hcd-uhci.c
@@ -88,6 +88,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.
@@ -718,9 +735,52 @@ static void uhci_read_td(UHCIState *s, UHCI_TD *td, uint32_t link)
le32_to_cpus(&td->buffer);
}
+static int uhci_handle_td_error(UHCIState *s, UHCI_TD *td, uint32_t td_addr,
+ int status, uint32_t *int_mask)
+{
+ uint32_t queue_token = uhci_queue_token(td);
+ int ret;
+
+ switch (status) {
+ case USB_RET_NAK:
+ td->ctrl |= TD_CTRL_NAK;
+ return TD_RESULT_NEXT_QH;
+
+ case USB_RET_STALL:
+ td->ctrl |= TD_CTRL_STALL;
+ trace_usb_uhci_packet_complete_stall(queue_token, td_addr);
+ ret = TD_RESULT_NEXT_QH;
+ break;
+
+ case USB_RET_BABBLE:
+ td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL;
+ /* frame interrupted */
+ trace_usb_uhci_packet_complete_babble(queue_token, td_addr);
+ ret = TD_RESULT_STOP_FRAME;
+ break;
+
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ default:
+ td->ctrl |= TD_CTRL_TIMEOUT;
+ td->ctrl &= ~(3 << TD_CTRL_ERROR_SHIFT);
+ trace_usb_uhci_packet_complete_error(queue_token, td_addr);
+ ret = TD_RESULT_NEXT_QH;
+ break;
+ }
+
+ td->ctrl &= ~TD_CTRL_ACTIVE;
+ s->status |= UHCI_STS_USBERR;
+ if (td->ctrl & TD_CTRL_IOC) {
+ *int_mask |= 0x01;
+ }
+ uhci_update_irq(s);
+ return ret;
+}
+
static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_t *int_mask)
{
- int len = 0, max_len, err, ret;
+ int len = 0, max_len, ret;
uint8_t pid;
max_len = ((td->token >> 21) + 1) & 0x7ff;
@@ -731,8 +791,9 @@ static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_
if (td->ctrl & TD_CTRL_IOS)
td->ctrl &= ~TD_CTRL_ACTIVE;
- if (ret < 0)
- goto out;
+ if (ret < 0) {
+ return uhci_handle_td_error(s, td, async->td_addr, ret, int_mask);
+ }
len = async->packet.result;
td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff);
@@ -758,46 +819,6 @@ static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_
trace_usb_uhci_packet_complete_success(async->queue->token,
async->td_addr);
return TD_RESULT_COMPLETE;
-
-out:
- switch(ret) {
- 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(async->queue->token,
- async->td_addr);
- err = 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(async->queue->token,
- async->td_addr);
- err = 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(async->queue->token,
- async->td_addr);
- err = 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 err;
}
static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr,
@@ -875,6 +896,11 @@ static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_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);
+
+ 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);
@@ -1208,6 +1234,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;
@@ -1216,20 +1243,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) {
@@ -1293,143 +1307,107 @@ static Property uhci_properties[] = {
DEFINE_PROP_END_OF_LIST(),
};
-static void piix3_uhci_class_init(ObjectClass *klass, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_uhci_common_initfn;
- k->exit = usb_uhci_exit;
- k->vendor_id = PCI_VENDOR_ID_INTEL;
- k->device_id = PCI_DEVICE_ID_INTEL_82371SB_2;
- k->revision = 0x01;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_uhci;
- dc->props = uhci_properties;
-}
-
-static TypeInfo piix3_uhci_info = {
- .name = "piix3-usb-uhci",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(UHCIState),
- .class_init = piix3_uhci_class_init,
-};
-
-static void piix4_uhci_class_init(ObjectClass *klass, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_uhci_common_initfn;
- k->exit = usb_uhci_exit;
- k->vendor_id = PCI_VENDOR_ID_INTEL;
- k->device_id = PCI_DEVICE_ID_INTEL_82371AB_2;
- k->revision = 0x01;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_uhci;
- dc->props = uhci_properties;
-}
-
-static TypeInfo piix4_uhci_info = {
- .name = "piix4-usb-uhci",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(UHCIState),
- .class_init = piix4_uhci_class_init,
-};
-
-static void vt82c686b_uhci_class_init(ObjectClass *klass, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_uhci_vt82c686b_initfn;
- k->exit = usb_uhci_exit;
- k->vendor_id = PCI_VENDOR_ID_VIA;
- k->device_id = PCI_DEVICE_ID_VIA_UHCI;
- k->revision = 0x01;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_uhci;
- dc->props = uhci_properties;
-}
-
-static TypeInfo vt82c686b_uhci_info = {
- .name = "vt82c686b-usb-uhci",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(UHCIState),
- .class_init = vt82c686b_uhci_class_init,
-};
-
-static void ich9_uhci1_class_init(ObjectClass *klass, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_uhci_common_initfn;
- k->vendor_id = PCI_VENDOR_ID_INTEL;
- k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI1;
- k->revision = 0x03;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_uhci;
- dc->props = uhci_properties;
-}
-
-static TypeInfo ich9_uhci1_info = {
- .name = "ich9-usb-uhci1",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(UHCIState),
- .class_init = ich9_uhci1_class_init,
-};
-
-static void ich9_uhci2_class_init(ObjectClass *klass, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_uhci_common_initfn;
- k->vendor_id = PCI_VENDOR_ID_INTEL;
- k->device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI2;
- k->revision = 0x03;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_uhci;
- dc->props = uhci_properties;
-}
-
-static TypeInfo ich9_uhci2_info = {
- .name = "ich9-usb-uhci2",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(UHCIState),
- .class_init = ich9_uhci2_class_init,
-};
-
-static void ich9_uhci3_class_init(ObjectClass *klass, void *data)
+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->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;
+ 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;
dc->vmsd = &vmstate_uhci;
dc->props = uhci_properties;
+ u->info = *info;
}
-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 7b65741dd5..900abf5566 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -146,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,
@@ -287,6 +302,16 @@ typedef enum TRBCCode {
typedef struct XHCIState XHCIState;
+#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,
@@ -458,6 +483,8 @@ enum xhci_flags {
static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
unsigned int epid);
+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);
@@ -1050,8 +1077,7 @@ static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid,
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));
@@ -1894,6 +1920,9 @@ static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid,
}
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);
@@ -1911,6 +1940,7 @@ static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid,
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_reset(dev);
usb_device_handle_control(dev, NULL,
DeviceOutRequest | USB_REQ_SET_ADDRESS,
slot->devaddr, 0, 0, NULL);
@@ -2312,35 +2342,86 @@ static void xhci_process_commands(XHCIState *xhci)
}
}
-static void xhci_update_port(XHCIState *xhci, XHCIPort *port, int is_detach)
+static bool xhci_port_have_device(XHCIPort *port)
+{
+ if (!port->uport->dev || !port->uport->dev->attached) {
+ return false; /* no device present */
+ }
+ if (!((1 << port->uport->dev->speed) & port->speedmask)) {
+ return false; /* speed mismatch */
+ }
+ return true;
+}
+
+static void xhci_port_notify(XHCIPort *port, uint32_t bits)
{
+ XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS,
+ port->portnr << 24 };
+
+ if ((port->portsc & bits) == bits) {
+ return;
+ }
+ 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->uport->dev && port->uport->dev->attached && !is_detach &&
- (1 << port->uport->dev->speed) & port->speedmask) {
+ if (!is_detach && xhci_port_have_device(port)) {
port->portsc |= PORTSC_CCS;
switch (port->uport->dev->speed) {
case USB_SPEED_LOW:
port->portsc |= PORTSC_SPEED_LOW;
+ pls = PLS_POLLING;
break;
case USB_SPEED_FULL:
port->portsc |= PORTSC_SPEED_FULL;
+ pls = PLS_POLLING;
break;
case USB_SPEED_HIGH:
port->portsc |= PORTSC_SPEED_HIGH;
+ pls = PLS_POLLING;
break;
case USB_SPEED_SUPER:
port->portsc |= PORTSC_SPEED_SUPER;
+ port->portsc |= PORTSC_PED;
+ pls = PLS_U0;
break;
}
}
+ set_field(&port->portsc, pls, PORTSC_PLS);
+ trace_usb_xhci_port_link(port->portnr, pls);
+ xhci_port_notify(port, PORTSC_CSC);
+}
+
+static void xhci_port_reset(XHCIPort *port)
+{
+ trace_usb_xhci_port_reset(port->portnr);
- if (xhci_running(xhci)) {
- port->portsc |= PORTSC_CSC;
- XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS,
- port->portnr << 24};
- xhci_event(xhci, &ev, 0);
- DPRINTF("xhci: port change event for port %d\n", port->portnr);
+ 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)
@@ -2368,7 +2449,7 @@ static void xhci_reset(DeviceState *dev)
}
for (i = 0; i < xhci->numports; i++) {
- xhci_update_port(xhci, xhci->ports + i, 0);
+ xhci_port_update(xhci->ports + i, 0);
}
for (i = 0; i < xhci->numintrs; i++) {
@@ -2499,19 +2580,18 @@ static void xhci_port_write(void *ptr, hwaddr reg,
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 pls = get_field(val, PORTSC_PLS);
+ set_field(&portsc, pls, PORTSC_PLS);
+ trace_usb_xhci_port_link(port->portnr, pls);
}
/* read/write bits */
portsc &= ~(PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE);
portsc |= (val & (PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE));
+ port->portsc = portsc;
/* write-1-to-start bits */
if (val & PORTSC_PR) {
- DPRINTF("xhci: port %d reset\n", port);
- usb_device_reset(port->uport->dev);
- portsc |= PORTSC_PRC | PORTSC_PED;
+ xhci_port_reset(port);
}
- port->portsc = portsc;
break;
case 0x04: /* PORTPMSC */
case 0x08: /* PORTLI */
@@ -2815,7 +2895,7 @@ static void xhci_attach(USBPort *usbport)
XHCIState *xhci = usbport->opaque;
XHCIPort *port = xhci_lookup_port(xhci, usbport);
- xhci_update_port(xhci, port, 0);
+ xhci_port_update(port, 0);
}
static void xhci_detach(USBPort *usbport)
@@ -2823,27 +2903,19 @@ static void xhci_detach(USBPort *usbport)
XHCIState *xhci = usbport->opaque;
XHCIPort *port = xhci_lookup_port(xhci, usbport);
- xhci_update_port(xhci, port, 1);
+ xhci_port_update(port, 1);
}
static void xhci_wakeup(USBPort *usbport)
{
XHCIState *xhci = usbport->opaque;
XHCIPort *port = xhci_lookup_port(xhci, usbport);
- XHCIEvent ev = { ER_PORT_STATUS_CHANGE, CC_SUCCESS,
- port->portnr << 24};
- uint32_t pls;
- pls = (port->portsc >> PORTSC_PLS_SHIFT) & PORTSC_PLS_MASK;
- if (pls != 3) {
- return;
- }
- port->portsc |= 0xf << PORTSC_PLS_SHIFT;
- if (port->portsc & PORTSC_PLC) {
+ if (get_field(port->portsc, PORTSC_PLS) != PLS_U3) {
return;
}
- port->portsc |= PORTSC_PLC;
- xhci_event(xhci, &ev, 0);
+ set_field(&port->portsc, PLS_RESUME, PORTSC_PLS);
+ xhci_port_notify(port, PORTSC_PLC);
}
static void xhci_complete(USBPort *port, USBPacket *packet)
diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c
index 22f671b3b1..c5cfe0b313 100644
--- a/hw/usb/redirect.c
+++ b/hw/usb/redirect.c
@@ -29,6 +29,7 @@
#include "qemu-timer.h"
#include "monitor.h"
#include "sysemu.h"
+#include "iov.h"
#include <dirent.h>
#include <sys/ioctl.h>
@@ -105,6 +106,7 @@ struct USBRedirDevice {
struct usb_redir_interface_info_header interface_info;
struct usbredirfilter_rule *filter_rules;
int filter_rules_count;
+ int compatible_speedmask;
};
static void usbredir_hello(void *priv, struct usb_redir_hello_header *h);
@@ -312,6 +314,11 @@ static void usbredir_cancel_packet(USBDevice *udev, USBPacket *p)
{
USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev);
+ if (p->combined) {
+ usb_combined_packet_cancel(udev, p);
+ return;
+ }
+
packet_id_queue_add(&dev->cancelled, p->id);
usbredirparser_send_cancel_data_packet(dev->parser, p->id);
usbredirparser_do_write(dev->parser);
@@ -331,6 +338,10 @@ static void usbredir_fill_already_in_flight_from_ep(USBRedirDevice *dev,
static USBPacket *p;
QTAILQ_FOREACH(p, &ep->queue, queue) {
+ /* Skip combined packets, except for the first */
+ if (p->combined && p != p->combined->first) {
+ continue;
+ }
packet_id_queue_add(&dev->already_in_flight, p->id);
}
}
@@ -565,26 +576,36 @@ static int usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p,
uint8_t ep)
{
struct usb_redir_bulk_packet_header bulk_packet;
+ size_t size = (p->combined) ? p->combined->iov.size : p->iov.size;
- DPRINTF("bulk-out ep %02X len %zd id %"PRIu64"\n", ep, p->iov.size, p->id);
+ DPRINTF("bulk-out ep %02X len %zd id %"PRIu64"\n", ep, size, p->id);
if (usbredir_already_in_flight(dev, p->id)) {
return USB_RET_ASYNC;
}
bulk_packet.endpoint = ep;
- bulk_packet.length = p->iov.size;
+ bulk_packet.length = size;
bulk_packet.stream_id = 0;
+ bulk_packet.length_high = size >> 16;
+ assert(bulk_packet.length_high == 0 ||
+ usbredirparser_peer_has_cap(dev->parser,
+ usb_redir_cap_32bits_bulk_length));
if (ep & USB_DIR_IN) {
usbredirparser_send_bulk_packet(dev->parser, p->id,
&bulk_packet, NULL, 0);
} else {
- uint8_t buf[p->iov.size];
- usb_packet_copy(p, buf, p->iov.size);
- usbredir_log_data(dev, "bulk data out:", buf, p->iov.size);
+ uint8_t buf[size];
+ if (p->combined) {
+ iov_to_buf(p->combined->iov.iov, p->combined->iov.niov,
+ 0, buf, size);
+ } else {
+ 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, p->iov.size);
+ &bulk_packet, buf, size);
}
usbredirparser_do_write(dev->parser);
return USB_RET_ASYNC;
@@ -701,6 +722,10 @@ static int usbredir_handle_data(USBDevice *udev, USBPacket *p)
case USB_ENDPOINT_XFER_ISOC:
return usbredir_handle_iso_data(dev, p, ep);
case USB_ENDPOINT_XFER_BULK:
+ if (p->state == USB_PACKET_SETUP && p->pid == USB_TOKEN_IN &&
+ p->ep->pipeline) {
+ return USB_RET_ADD_TO_QUEUE;
+ }
return usbredir_handle_bulk_data(dev, p, ep);
case USB_ENDPOINT_XFER_INT:
return usbredir_handle_interrupt_data(dev, p, ep);
@@ -711,6 +736,13 @@ static int usbredir_handle_data(USBDevice *udev, USBPacket *p)
}
}
+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,
int config)
{
@@ -896,6 +928,7 @@ static void usbredir_create_parser(USBRedirDevice *dev)
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);
if (runstate_check(RUN_STATE_INMIGRATE)) {
flags |= usbredirparser_fl_no_hello;
@@ -930,6 +963,7 @@ static void usbredir_do_attach(void *opaque)
}
if (usb_device_attach(&dev->dev) != 0) {
+ WARNING("rejecting device due to speed mismatch\n");
usbredir_reject_device(dev);
}
}
@@ -1036,6 +1070,9 @@ static int usbredir_initfn(USBDevice *udev)
/* 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,
@@ -1176,10 +1213,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";
@@ -1209,7 +1249,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)) {
@@ -1249,6 +1289,7 @@ static void usbredir_device_disconnect(void *priv)
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,
@@ -1270,6 +1311,12 @@ 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) {
@@ -1278,27 +1325,64 @@ static void usbredir_set_pipeline(USBRedirDevice *dev, struct USBEndpoint *uep)
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, pid;
+
+ for (i = 0; i < MAX_ENDPOINTS; i++) {
+ pid = (i & 0x10) ? USB_TOKEN_IN : USB_TOKEN_OUT;
+ usb_ep = usb_ep_get(&dev->dev, pid, i & 0x0f);
+ 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:
@@ -1308,21 +1392,19 @@ 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;
- if (usbredirparser_peer_has_cap(dev->parser,
- usb_redir_cap_ep_info_max_packet_size)) {
- dev->endpoint[i].max_packet_size =
- usb_ep->max_packet_size = ep_info->max_packet_size[i];
- }
- usbredir_set_pipeline(dev, usb_ep);
}
+ /* 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);
}
static void usbredir_configuration_status(void *priv, uint64_t id,
@@ -1427,6 +1509,17 @@ static void usbredir_control_packet(void *priv, uint64_t id,
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) {
len = usbredir_handle_status(dev, control_packet->status, len);
@@ -1452,7 +1545,7 @@ static void usbredir_bulk_packet(void *priv, uint64_t id,
{
USBRedirDevice *dev = priv;
uint8_t ep = bulk_packet->endpoint;
- int len = bulk_packet->length;
+ int len = (bulk_packet->length_high << 16) | bulk_packet->length;
USBPacket *p;
DPRINTF("bulk-in status %d ep %02X len %d id %"PRIu64"\n",
@@ -1460,11 +1553,17 @@ static void usbredir_bulk_packet(void *priv, uint64_t id,
p = usbredir_find_packet_by_id(dev, ep, id);
if (p) {
+ size_t size = (p->combined) ? p->combined->iov.size : p->iov.size;
len = usbredir_handle_status(dev, bulk_packet->status, len);
if (len > 0) {
usbredir_log_data(dev, "bulk data in:", data, data_len);
- if (data_len <= p->iov.size) {
- usb_packet_copy(p, data, data_len);
+ if (data_len <= size) {
+ if (p->combined) {
+ iov_from_buf(p->combined->iov.iov, p->combined->iov.niov,
+ 0, data, data_len);
+ } else {
+ usb_packet_copy(p, data, data_len);
+ }
} else {
ERROR("bulk got more data then requested (%d > %zd)\n",
data_len, p->iov.size);
@@ -1472,7 +1571,11 @@ static void usbredir_bulk_packet(void *priv, uint64_t id,
}
}
p->result = len;
- usb_packet_complete(&dev->dev, p);
+ if (p->pid == USB_TOKEN_IN && p->ep->pipeline) {
+ usb_combined_input_packet_complete(&dev->dev, p);
+ } else {
+ usb_packet_complete(&dev->dev, p);
+ }
}
free(data);
}
@@ -1554,8 +1657,6 @@ static void usbredir_pre_save(void *priv)
static int usbredir_post_load(void *priv, int version_id)
{
USBRedirDevice *dev = priv;
- struct USBEndpoint *usb_ep;
- int i;
switch (dev->device_info.speed) {
case usb_redir_speed_low:
@@ -1575,15 +1676,8 @@ static int usbredir_post_load(void *priv, int version_id)
}
dev->dev.speedmask = (1 << dev->dev.speed);
- for (i = 0; i < MAX_ENDPOINTS; i++) {
- 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;
- usb_ep->max_packet_size = dev->endpoint[i].max_packet_size;
- usbredir_set_pipeline(dev, usb_ep);
- }
+ usbredir_setup_usb_eps(dev);
+
return 0;
}
@@ -1879,6 +1973,7 @@ 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;
dc->vmsd = &usbredir_vmstate;
dc->props = usbredir_properties;
}
diff --git a/hw/xilinx_zynq.c b/hw/xilinx_zynq.c
index 00262350b6..1f12a3d1ad 100644
--- a/hw/xilinx_zynq.c
+++ b/hw/xilinx_zynq.c
@@ -166,6 +166,9 @@ static void zynq_init(QEMUMachineInitArgs *args)
zynq_init_spi_flashes(0xE0007000, pic[81-IRQ_OFFSET], false);
zynq_init_spi_flashes(0xE000D000, pic[51-IRQ_OFFSET], true);
+ sysbus_create_simple("xlnx,ps7-usb", 0xE0002000, pic[53-IRQ_OFFSET]);
+ sysbus_create_simple("xlnx,ps7-usb", 0xE0003000, pic[75-IRQ_OFFSET]);
+
sysbus_create_simple("cadence_uart", 0xE0000000, pic[59-IRQ_OFFSET]);
sysbus_create_simple("cadence_uart", 0xE0001000, pic[82-IRQ_OFFSET]);