* USB Scanner
@ 2011-01-03 8:41 Matthias Henze (L)
2011-01-03 15:43 ` Erik Rull
0 siblings, 1 reply; 6+ messages in thread
From: Matthias Henze (L) @ 2011-01-03 8:41 UTC (permalink / raw)
To: kvm
Hi,
finally I've tried to connect a USB scanner to a KVM VM. I've done so by
adding
hostusb: 04a9:1606
to the VMs conf. The scanner is detected by Windows XP and at first it
seemed to work. It scanns but then "CapturePerfect" hangs for ever.
lsusb reports:
Bus 002 Device 005: ID 04a9:1606 Canon, Inc.
and "info usb":
Device 0.1, Speed 12 Mb/s, Product QEMU USB Tablet
Device 0.3, Speed 12 Mb/s, Product QEMU USB Hub
Device 0.2, Speed 480 Mb/s, Product CANON DR-5010C
and "info usbhost":
Device 1.1, speed 480 Mb/s
Hub: USB device 1d6b:0002, EHCI Host Controller
Device 2.1, speed 480 Mb/s
Hub: USB device 1d6b:0002, EHCI Host Controller
Device 3.1, speed 12 Mb/s
Hub: USB device 1d6b:0001, UHCI Host Controller
Device 4.1, speed 12 Mb/s
Hub: USB device 1d6b:0001, UHCI Host Controller
Device 5.1, speed 12 Mb/s
Hub: USB device 1d6b:0001, UHCI Host Controller
Device 6.1, speed 12 Mb/s
Hub: USB device 1d6b:0001, UHCI Host Controller
Device 7.1, speed 12 Mb/s
Hub: USB device 1d6b:0001, UHCI Host Controller
Device 8.1, speed 12 Mb/s
Hub: USB device 1d6b:0001, UHCI Host Controller
Device 1.2, speed 480 Mb/s
Hub: USB device 04b4:6560
Device 6.2, speed 12 Mb/s
Class 00: USB device 0000:0000, SE USB Device
Device 8.2, speed 1.5 Mb/s
Class 00: USB device 046a:0101, Wireless Desktop
Device 1.3, speed 480 Mb/s
Class 00: USB device 0bc2:2101, FreeAgent Go
Device 1.4, speed 480 Mb/s
Class 00: USB device 0bc2:2101, FreeAgent Go
Device 2.5, speed 480 Mb/s
Class 00: USB device 04a9:1606, CANON DR-5010C
Auto filters:
Device *.* ID 04a9:1606
Can some one pelase help ? This ist a show stopper for this project ...
TIA
Matthias
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: USB Scanner
2011-01-03 8:41 USB Scanner Matthias Henze (L)
@ 2011-01-03 15:43 ` Erik Rull
2011-01-03 22:59 ` David Ahern
0 siblings, 1 reply; 6+ messages in thread
From: Erik Rull @ 2011-01-03 15:43 UTC (permalink / raw)
To: Matthias Henze (L); +Cc: kvm
Matthias Henze (L) wrote:
> Hi,
>
> finally I've tried to connect a USB scanner to a KVM VM. I've done so by
> adding
>
> hostusb: 04a9:1606
>
> to the VMs conf. The scanner is detected by Windows XP and at first it
> seemed to work. It scanns but then "CapturePerfect" hangs for ever.
>
> lsusb reports:
>
> Bus 002 Device 005: ID 04a9:1606 Canon, Inc.
>
> and "info usb":
>
> Device 0.1, Speed 12 Mb/s, Product QEMU USB Tablet
> Device 0.3, Speed 12 Mb/s, Product QEMU USB Hub
> Device 0.2, Speed 480 Mb/s, Product CANON DR-5010C
>
> and "info usbhost":
>
> Device 1.1, speed 480 Mb/s
> Hub: USB device 1d6b:0002, EHCI Host Controller
> Device 2.1, speed 480 Mb/s
> Hub: USB device 1d6b:0002, EHCI Host Controller
> Device 3.1, speed 12 Mb/s
> Hub: USB device 1d6b:0001, UHCI Host Controller
> Device 4.1, speed 12 Mb/s
> Hub: USB device 1d6b:0001, UHCI Host Controller
> Device 5.1, speed 12 Mb/s
> Hub: USB device 1d6b:0001, UHCI Host Controller
> Device 6.1, speed 12 Mb/s
> Hub: USB device 1d6b:0001, UHCI Host Controller
> Device 7.1, speed 12 Mb/s
> Hub: USB device 1d6b:0001, UHCI Host Controller
> Device 8.1, speed 12 Mb/s
> Hub: USB device 1d6b:0001, UHCI Host Controller
> Device 1.2, speed 480 Mb/s
> Hub: USB device 04b4:6560
> Device 6.2, speed 12 Mb/s
> Class 00: USB device 0000:0000, SE USB Device
> Device 8.2, speed 1.5 Mb/s
> Class 00: USB device 046a:0101, Wireless Desktop
> Device 1.3, speed 480 Mb/s
> Class 00: USB device 0bc2:2101, FreeAgent Go
> Device 1.4, speed 480 Mb/s
> Class 00: USB device 0bc2:2101, FreeAgent Go
> Device 2.5, speed 480 Mb/s
> Class 00: USB device 04a9:1606, CANON DR-5010C
> Auto filters:
> Device *.* ID 04a9:1606
>
> Can some one pelase help ? This ist a show stopper for this project ...
>
> TIA
> Matthias
>
Hi Matthias,
I experienced a similar behaviour with Epson and Canon USB Printers. there
the ink fill status readout causes a complete hang of the software (Windows
still operational). It seems to be related with USB 2.0 devices. An older
USB 1.1 HP Printer works slow but fine.
Please check dmesg of the host system, there you should see a lot of USB
info messages after plugging in the device.
If you get a BSOD in XP when shutting down Windows or when removing the USB
Scanner then it is exactly the same issue that I have. It seems (from my
point of view) to be related with the KVM-USB virtualization. I could
reduce some of the BSOD's by forcing the host USB system to fall back to
USB 1.x (switch in BIOS), but printing with the 2.0 devices was still not
possible!
I have currently no real idea how to solve the issue - additionally the
transfer rates of USB are horribly - I do not get more than ~ 64K/sec.
If there are improvements on that somewhere "uncomitted" I would like to
try them out.
Best regards,
Erik
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: USB Scanner
2011-01-03 15:43 ` Erik Rull
@ 2011-01-03 22:59 ` David Ahern
2011-01-03 23:35 ` Erik Rull
0 siblings, 1 reply; 6+ messages in thread
From: David Ahern @ 2011-01-03 22:59 UTC (permalink / raw)
To: Erik Rull, Matthias Henze (L); +Cc: kvm
[-- Attachment #1: Type: text/plain, Size: 2058 bytes --]
On 01/03/11 08:43, Erik Rull wrote:
> Matthias Henze (L) wrote:
>> Hi,
>>
>> finally I've tried to connect a USB scanner to a KVM VM. I've done so by
>> adding
>>
<snip>
>> Can some one pelase help ? This ist a show stopper for this project ...
>>
>> TIA
>> Matthias
>>
>
> Hi Matthias,
>
> I experienced a similar behaviour with Epson and Canon USB Printers.
> there the ink fill status readout causes a complete hang of the software
> (Windows still operational). It seems to be related with USB 2.0
> devices. An older USB 1.1 HP Printer works slow but fine.
>
> Please check dmesg of the host system, there you should see a lot of USB
> info messages after plugging in the device.
>
> If you get a BSOD in XP when shutting down Windows or when removing the
> USB Scanner then it is exactly the same issue that I have. It seems
> (from my point of view) to be related with the KVM-USB virtualization. I
> could reduce some of the BSOD's by forcing the host USB system to fall
> back to USB 1.x (switch in BIOS), but printing with the 2.0 devices was
> still not possible!
>
> I have currently no real idea how to solve the issue - additionally the
> transfer rates of USB are horribly - I do not get more than ~ 64K/sec.
>
> If there are improvements on that somewhere "uncomitted" I would like to
> try them out.
The attached work for me with an Epson CX7800. I was able to print and
scan with Vista; driver setup for XP was more of a pain - I only got the
printer setup working.
It's the EHCI code from
git://git.kiszka.org/qemu.git ehci
with http://www.mail-archive.com/qemu-devel@nongnu.org/msg38795.html and
a couple of local additions. Applies cleanly to qemu-kvm.git as of today
(b724fa4bb46b82c54b5f0a2cf9177194020433efO).
EHCI is not ready for prime time, but works for use cases like this one.
David
>
> Best regards,
>
> Erik
> --
> To unsubscribe from this list: send the line "unsubscribe kvm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
[-- Attachment #2: qemu-kvm-ehci-01032011.patch --]
[-- Type: text/plain, Size: 83460 bytes --]
diff --git a/Makefile.objs b/Makefile.objs
index 5170cc2..92bee46 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -181,6 +181,7 @@ hw-obj-$(CONFIG_PARALLEL) += parallel.o
#hw-obj-$(CONFIG_PCSPK) += pcspk.o
hw-obj-$(CONFIG_PCKBD) += pckbd.o
hw-obj-$(CONFIG_USB_UHCI) += usb-uhci.o
+hw-obj-$(CONFIG_USB_EHCI) += usb-ehci.o
hw-obj-$(CONFIG_FDC) += fdc.o
# needs fixes for cpu hotplug, so moved to Makefile.target:
# hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o
diff --git a/Makefile.target b/Makefile.target
index 6a4d1ac..762620c 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -246,7 +246,7 @@ obj-i386-$(CONFIG_KVM_DEVICE_ASSIGNMENT) += device-assignment.o
obj-ia64-y += ide.o pckbd.o vga.o $(SOUND_HW) dma.o $(AUDIODRV)
obj-ia64-y += fdc.o mc146818rtc.o serial.o i8259.o ipf.o
obj-ia64-y += cirrus_vga.o parallel.o acpi.o piix_pci.o
-obj-ia64-y += usb-uhci.o
+obj-ia64-y += usb-uhci.o usb-ehci.o
obj-ia64-$(CONFIG_KVM_DEVICE_ASSIGNMENT) += device-assignment.o
# shared objects
diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak
index ed00471..fdb7dc1 100644
--- a/default-configs/i386-softmmu.mak
+++ b/default-configs/i386-softmmu.mak
@@ -10,6 +10,7 @@ CONFIG_I8254=y
CONFIG_PCSPK=y
CONFIG_PCKBD=y
CONFIG_USB_UHCI=y
+CONFIG_USB_EHCI=y
CONFIG_FDC=y
CONFIG_ACPI=y
CONFIG_APM=y
diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak
index 5183203..cf88d9e 100644
--- a/default-configs/x86_64-softmmu.mak
+++ b/default-configs/x86_64-softmmu.mak
@@ -10,6 +10,7 @@ CONFIG_I8254=y
CONFIG_PCSPK=y
CONFIG_PCKBD=y
CONFIG_USB_UHCI=y
+CONFIG_USB_EHCI=y
CONFIG_FDC=y
CONFIG_ACPI=y
CONFIG_APM=y
diff --git a/hw/pc_piix.c b/hw/pc_piix.c
index fbe2766..7464528 100644
--- a/hw/pc_piix.c
+++ b/hw/pc_piix.c
@@ -28,6 +28,7 @@
#include "pci.h"
#include "usb-uhci.h"
#include "usb-ohci.h"
+#include "usb-ehci.h"
#include "net.h"
#include "boards.h"
#include "ide.h"
@@ -171,8 +172,11 @@ static void pc_init1(ram_addr_t ram_size,
pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,
idebus[0], idebus[1], floppy_controller, rtc_state);
+ // TODO the piix3 does not have EHCI - looks like ICH4
+ // was first southbridge to support it.
if (pci_enabled && usb_enabled) {
usb_uhci_piix3_init(pci_bus, piix3_devfn + 2);
+ usb_ehci_init(pci_bus, piix3_devfn + 3);
}
if (pci_enabled && acpi_enabled) {
@@ -182,7 +186,7 @@ static void pc_init1(ram_addr_t ram_size,
cmos_s3 = qemu_allocate_irqs(pc_cmos_set_s3_resume, rtc_state, 1);
smi_irq = qemu_allocate_irqs(pc_acpi_smi_interrupt, first_cpu, 1);
/* TODO: Populate SPD eeprom data. */
- smbus = piix4_pm_init(pci_bus, piix3_devfn + 3, 0xb100,
+ smbus = piix4_pm_init(pci_bus, piix3_devfn + 4, 0xb100,
isa_reserve_irq(9), *cmos_s3, *smi_irq,
kvm_enabled());
for (i = 0; i < 8; i++) {
diff --git a/hw/pci_ids.h b/hw/pci_ids.h
index 82cba7e..0bb4a1a 100644
--- a/hw/pci_ids.h
+++ b/hw/pci_ids.h
@@ -99,6 +99,7 @@
#define PCI_VENDOR_ID_INTEL 0x8086
#define PCI_DEVICE_ID_INTEL_82441 0x1237
#define PCI_DEVICE_ID_INTEL_82801AA_5 0x2415
+#define PCI_DEVICE_ID_INTEL_82801D 0x24CD
#define PCI_DEVICE_ID_INTEL_ESB_9 0x25ab
#define PCI_DEVICE_ID_INTEL_82371SB_0 0x7000
#define PCI_DEVICE_ID_INTEL_82371SB_1 0x7010
diff --git a/hw/usb-bus.c b/hw/usb-bus.c
index b692503..29c7d2a 100644
--- a/hw/usb-bus.c
+++ b/hw/usb-bus.c
@@ -4,6 +4,13 @@
#include "sysemu.h"
#include "monitor.h"
+enum {
+ USB_VERSION_NONE,
+ USB_VERSION_1_1,
+ USB_VERSION_2_0,
+};
+
+
static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent);
static struct BusInfo usb_bus_info = {
@@ -14,27 +21,51 @@ static struct BusInfo usb_bus_info = {
static int next_usb_bus = 0;
static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses);
-void usb_bus_new(USBBus *bus, DeviceState *host)
+static void usb_bus_new(USBBus *bus, int version, DeviceState *host)
{
qbus_create_inplace(&bus->qbus, &usb_bus_info, host, NULL);
bus->busnr = next_usb_bus++;
bus->qbus.allow_hotplug = 1; /* Yes, we can */
+ bus->version = version;
QTAILQ_INIT(&bus->free);
QTAILQ_INIT(&bus->used);
QTAILQ_INSERT_TAIL(&busses, bus, next);
}
-USBBus *usb_bus_find(int busnr)
+void usb_bus_new_v1(USBBus *bus, DeviceState *host)
+{
+ usb_bus_new(bus, USB_VERSION_1_1, host);
+}
+
+void usb_bus_new_v2(USBBus *bus, DeviceState *host)
+{
+ usb_bus_new(bus, USB_VERSION_2_0, host);
+}
+
+static USBBus *usb_bus_find(int busnr)
{
USBBus *bus;
- if (-1 == busnr)
- return QTAILQ_FIRST(&busses);
QTAILQ_FOREACH(bus, &busses, next) {
- if (bus->busnr == busnr)
- return bus;
+ if (bus->busnr == busnr) {
+ break;
+ }
}
- return NULL;
+
+ return bus;
+}
+
+/* device creation should be using this one */
+USBBus *usb_bus_find_version(int version)
+{
+ USBBus *bus;
+
+ QTAILQ_FOREACH(bus, &busses, next) {
+ if (bus->version == version) {
+ break;
+ }
+ }
+ return bus;
}
static int usb_qdev_init(DeviceState *qdev, DeviceInfo *base)
@@ -80,20 +111,15 @@ void usb_qdev_register_many(USBDeviceInfo *info)
}
}
-USBDevice *usb_create(USBBus *bus, const char *name)
+static USBDevice *usb_create(USBBus *bus, const char *name)
{
DeviceState *dev;
-#if 1
- /* temporary stopgap until all usb is properly qdev-ified */
if (!bus) {
- bus = usb_bus_find(-1);
- if (!bus)
- return NULL;
- fprintf(stderr, "%s: no bus specified, using \"%s\" for \"%s\"\n",
- __FUNCTION__, bus->qbus.name, name);
+ fprintf(stderr, "%s: no bus specified for \"%s\"\n",
+ __FUNCTION__, name);
+ return NULL;
}
-#endif
dev = qdev_create(&bus->qbus, name);
return DO_UPCAST(USBDevice, qdev, dev);
@@ -101,7 +127,13 @@ USBDevice *usb_create(USBBus *bus, const char *name)
USBDevice *usb_create_simple(USBBus *bus, const char *name)
{
- USBDevice *dev = usb_create(bus, name);
+ USBDevice *dev;
+
+ /* if bus not given default to USB 1.1 */
+ if (!bus)
+ bus = usb_bus_find_version(USB_VERSION_1_1);
+
+ dev = usb_create(bus, name);
if (!dev) {
hw_error("Failed to create USB device '%s'\n", name);
}
@@ -109,6 +141,50 @@ USBDevice *usb_create_simple(USBBus *bus, const char *name)
return dev;
}
+/* create USB device attached to USB 1.1 controller */
+USBDevice *usb_create_v1(const char *name)
+{
+ USBBus *bus = usb_bus_find_version(USB_VERSION_1_1);
+ return usb_create(bus, name);
+}
+
+/* create USB device attached to USB 2.0 controller */
+USBDevice *usb_create_v2(const char *name)
+{
+ USBBus *bus = usb_bus_find_version(USB_VERSION_2_0);
+ return usb_create(bus, name);
+}
+
+int usb_bus_migrate_v1(USBDevice *dev)
+{
+ USBBus *obus = usb_bus_from_device(dev);
+ BusState *bus_state;
+ int attached = dev->attached;
+ int rc = 0;
+
+ USBBus *nbus = usb_bus_find_version(USB_VERSION_1_1);
+ if (!nbus) {
+ return -1;
+ }
+
+ if (nbus == obus) {
+ return 0;
+ }
+
+ if (attached && (usb_device_detach(dev) != 0)) {
+ return -1;
+ }
+
+ bus_state = &nbus->qbus;
+ dev->qdev.parent_bus = bus_state;
+
+ if (attached) {
+ rc = usb_device_attach(dev);
+ }
+
+ return 0;
+}
+
void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index,
usb_attachfn attach)
{
@@ -260,7 +336,6 @@ void usb_info(Monitor *mon)
/* handle legacy -usbdevice cmd line option */
USBDevice *usbdevice_create(const char *cmdline)
{
- USBBus *bus = usb_bus_find(-1 /* any */);
DeviceInfo *info;
USBDeviceInfo *usb;
char driver[32];
@@ -302,7 +377,7 @@ USBDevice *usbdevice_create(const char *cmdline)
error_report("usbdevice %s accepts no params", driver);
return NULL;
}
- return usb_create_simple(bus, usb->qdev.name);
+ return usb_create_simple(NULL, usb->qdev.name);
}
return usb->usbdevice_init(params);
}
diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c
new file mode 100644
index 0000000..03f58be
--- /dev/null
+++ b/hw/usb-ehci.c
@@ -0,0 +1,2088 @@
+/*
+ * QEMU USB EHCI Emulation
+ *
+ * Copyright(c) 2008 Emutex Ltd. (address@hidden)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or(at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * TODO:
+ * o Downstream port handoff
+ */
+
+#include "hw.h"
+#include "qemu-timer.h"
+#include "usb.h"
+#include "pci.h"
+#include "usb-ehci.h"
+#include "monitor.h"
+
+#define EHCI_DEBUG 0
+#define STATE_DEBUG 0 /* state transitions */
+
+#if EHCI_DEBUG || STATE_DEBUG
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+#if STATE_DEBUG
+#define DPRINTF_ST DPRINTF
+#else
+#define DPRINTF_ST(...)
+#endif
+
+/* 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 EECP HCCPARAMS + 1
+#define HCSPPORTROUTE1 CAPREGBASE + 0x000c
+#define HCSPPORTROUTE2 CAPREGBASE + 0x0010
+
+#define OPREGBASE 0x0020 // Operational Registers Base Address
+
+#define USBCMD OPREGBASE + 0x0000
+#define USBCMD_RUNSTOP (1 << 0) // run / Stop
+#define USBCMD_HCRESET (1 << 1) // HC Reset
+#define USBCMD_FLS (3 << 2) // Frame List Size
+#define USBCMD_FLS_SH 2 // Frame List Size Shift
+#define USBCMD_PSE (1 << 4) // Periodic Schedule Enable
+#define USBCMD_ASE (1 << 5) // Asynch Schedule Enable
+#define USBCMD_IAAD (1 << 6) // Int Asynch Advance Doorbell
+#define USBCMD_LHCR (1 << 7) // Light Host Controller Reset
+#define USBCMD_ASPMC (3 << 8) // Async Sched Park Mode Count
+#define USBCMD_ASPME (1 << 11) // Async Sched Park Mode Enable
+#define USBCMD_ITC (0x7f << 16) // Int Threshold Control
+#define USBCMD_ITC_SH 16 // Int Threshold Control Shift
+
+#define USBSTS OPREGBASE + 0x0004
+#define USBSTS_RO_MASK 0x0000003f
+#define USBSTS_INT (1 << 0) // USB Interrupt
+#define USBSTS_ERRINT (1 << 1) // Error Interrupt
+#define USBSTS_PCD (1 << 2) // Port Change Detect
+#define USBSTS_FLR (1 << 3) // Frame List Rollover
+#define USBSTS_HSE (1 << 4) // Host System Error
+#define USBSTS_IAA (1 << 5) // Interrupt on Async Advance
+#define USBSTS_HALT (1 << 12) // HC Halted
+#define USBSTS_REC (1 << 13) // Reclamation
+#define USBSTS_PSS (1 << 14) // Periodic Schedule Status
+#define USBSTS_ASS (1 << 15) // Asynchronous Schedule Status
+
+/*
+ * Interrupt enable bits correspond to the interrupt active bits in USBSTS
+ * so no need to redefine here.
+ */
+#define USBINTR OPREGBASE + 0x0008
+#define USBINTR_MASK 0x0000003f
+
+#define FRINDEX OPREGBASE + 0x000c
+#define CTRLDSSEGMENT OPREGBASE + 0x0010
+#define PERIODICLISTBASE OPREGBASE + 0x0014
+#define ASYNCLISTADDR OPREGBASE + 0x0018
+#define ASYNCLISTADDR_MASK 0xffffffe0
+
+#define CONFIGFLAG OPREGBASE + 0x0040
+
+#define PORTSC (OPREGBASE + 0x0044)
+#define PORTSC_BEGIN PORTSC
+#define PORTSC_END (PORTSC + 4 * NB_PORTS)
+/*
+ * Bits that are reserverd or are read-only are masked out of values
+ * written to us by software
+ */
+#define PORTSC_RO_MASK 0x007021c5
+#define PORTSC_RWC_MASK 0x0000002a
+#define PORTSC_WKOC_E (1 << 22) // Wake on Over Current Enable
+#define PORTSC_WKDS_E (1 << 21) // Wake on Disconnect Enable
+#define PORTSC_WKCN_E (1 << 20) // Wake on Connect Enable
+#define PORTSC_PTC (15 << 16) // Port Test Control
+#define PORTSC_PTC_SH 16 // Port Test Control shift
+#define PORTSC_PIC (3 << 14) // Port Indicator Control
+#define PORTSC_PIC_SH 14 // Port Indicator Control Shift
+#define PORTSC_POWNER (1 << 13) // Port Owner
+#define PORTSC_PPOWER (1 << 12) // Port Power
+#define PORTSC_LINESTAT (3 << 10) // Port Line Status
+#define PORTSC_LINESTAT_SH 10 // Port Line Status Shift
+#define PORTSC_PRESET (1 << 8) // Port Reset
+#define PORTSC_SUSPEND (1 << 7) // Port Suspend
+#define PORTSC_FPRES (1 << 6) // Force Port Resume
+#define PORTSC_OCC (1 << 5) // Over Current Change
+#define PORTSC_OCA (1 << 4) // Over Current Active
+#define PORTSC_PEDC (1 << 3) // Port Enable/Disable Change
+#define PORTSC_PED (1 << 2) // Port Enable/Disable
+#define PORTSC_CSC (1 << 1) // Connect Status Change
+#define PORTSC_CONNECT (1 << 0) // Current Connect Status
+
+//#define EHCI_NOMICROFRAMES
+
+#ifdef EHCI_NOMICROFRAMES
+#define FRAME_TIMER_FREQ 1000
+#else
+#define FRAME_TIMER_FREQ 8000
+#endif
+#define FRAME_TIMER_USEC (1000000 / FRAME_TIMER_FREQ)
+
+#define NB_MAXINTRATE 8 // Max rate at which controller issues ints
+#define NB_PORTS 4 // Number of downstream ports
+#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
+#define MAX_ITERATIONS 20 // Max number of QH before we break the loop
+#define MAX_QH 100 // Max allowable queue heads in a chain
+
+/* Internal periodic / asynchronous schedule state machine states
+ */
+typedef enum {
+ EST_INACTIVE = 1000,
+ EST_ACTIVE,
+ EST_EXECUTING,
+ EST_SLEEPING,
+ /* The following states are internal to the state machine function
+ */
+ EST_WAITLISTHEAD,
+ EST_FETCHENTRY,
+ EST_FETCHQH,
+ EST_FETCHITD,
+ EST_ADVANCEQUEUE,
+ EST_FETCHQTD,
+ EST_EXECUTE,
+ EST_WRITEBACK,
+ EST_HORIZONTALQH
+} EHCI_STATES;
+
+/* macros for accessing fields within next link pointer entry */
+#define NLPTR_GET(x) ((x) & 0xffffffe0)
+#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
+#define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid
+
+/* link pointer types */
+#define NLPTR_TYPE_ITD 0 // isoc xfer descriptor
+#define NLPTR_TYPE_QH 1 // queue head
+#define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor
+#define NLPTR_TYPE_FSTN 3 // frame span traversal node
+
+
+/* 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
+} 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
+} 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 {
+ PCIDevice dev;
+ qemu_irq irq;
+ target_phys_addr_t mem_base;
+ int mem;
+ int num_ports;
+ /*
+ * 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
+ */
+ uint32_t sofv;
+ QEMUTimer *frame_timer;
+ int attach_poll_counter;
+ int astate; // Current state in asynchronous schedule
+ int pstate; // Current state in periodic schedule
+ USBPort ports[NB_PORTS];
+ uint8_t buffer[BUFF_SIZE];
+
+ /* 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
+
+ EHCIqtd qtd; // copy of current QTD (being worked on)
+ uint32_t qtdaddr; // address QTD read from
+
+ uint32_t itdaddr; // current ITD
+
+ uint32_t fetch_addr; // which address to look at next
+
+ USBBus bus;
+ USBPacket usb_packet;
+ int async_port_in_progress;
+ int async_complete;
+ uint32_t tbytes;
+ int pid;
+ int exec_status;
+ int isoch_pause;
+ uint32_t last_run_usec;
+ uint32_t frame_end_usec;
+} EHCIState;
+
+#define SET_LAST_RUN_CLOCK(s) \
+ (s)->last_run_usec = qemu_get_clock(vm_clock) / 1000;
+
+/* nifty macros from Arnon's EHCI version */
+#define get_field(data, field) \
+ (((data) & field##_MASK) >> field##_SH)
+
+#define set_field(data, newval, field) do { \
+ uint32_t val = *data; \
+ val &= ~ field##_MASK; \
+ val |= ((newval) << field##_SH) & field##_MASK; \
+ *data = val; \
+ } while(0)
+
+
+#if EHCI_DEBUG
+static const char *addr2str(unsigned addr)
+{
+ const char *r = " unknown";
+
+ switch(addr) {
+ case CAPLENGTH:
+ r = " CAPLENGTH";
+ break;
+
+ case HCIVERSION:
+ r = "HCIVERSION";
+ break;
+
+ case HCSPARAMS:
+ r = " HCSPARAMS";
+ break;
+
+ case HCCPARAMS:
+ r = " HCCPARAMS";
+ break;
+
+ case USBCMD:
+ r = " COMMAND";
+ break;
+
+ case USBSTS:
+ r = " STATUS";
+ break;
+
+ case USBINTR:
+ r = " INTERRUPT";
+ break;
+
+ case FRINDEX:
+ r = " FRAME IDX";
+ break;
+
+ case PERIODICLISTBASE:
+ r = "P-LIST BASE";
+ break;
+
+ case ASYNCLISTADDR:
+ r = "A-LIST ADDR";
+ break;
+
+ case PORTSC_BEGIN ... PORTSC_END:
+ r = "PORT STATUS";
+ break;
+
+ case CONFIGFLAG:
+ r = "CONFIG FLAG";
+ break;
+ }
+
+ return r;
+}
+#endif
+
+
+static inline void ehci_set_interrupt(EHCIState *s, int intr)
+{
+ int level = 0;
+
+ // TODO honour interrupt threshold requests
+
+ s->usbsts |= intr;
+
+ if ((s->usbsts & USBINTR_MASK) & s->usbintr)
+ level = 1;
+
+ qemu_set_irq(s->irq, level);
+}
+
+/* Attach or detach a device on root hub */
+
+static void ehci_attach(USBPort *port, USBDevice *dev)
+{
+ EHCIState *s = port->opaque;
+ uint32_t *portsc = &s->portsc[port->index];
+
+ DPRINTF("ehci_attach invoked for index %d, portsc 0x%x, desc %s\n",
+ port->index, *portsc, dev ? dev->product_desc : "undefined");
+
+ if (dev) {
+ if (port->dev) {
+ usb_attach(port, NULL);
+ }
+
+ *portsc |= PORTSC_CONNECT;
+
+ usb_send_msg(dev, USB_MSG_ATTACH);
+ port->dev = dev;
+ } else {
+ *portsc &= ~PORTSC_CONNECT;
+
+ if (port->dev) {
+ dev = port->dev;
+ usb_send_msg(dev, USB_MSG_DETACH);
+ }
+
+ port->dev = NULL;
+ }
+
+ *portsc |= PORTSC_CSC;
+
+ /*
+ * If a high speed device is attached then we own this port(indicated
+ * by zero in the PORTSC_POWNER bit field) so set the status bit
+ * and set an interrupt if enabled.
+ */
+ if ( !(*portsc & PORTSC_POWNER)) {
+ ehci_set_interrupt(s, USBSTS_PCD);
+ }
+}
+
+/* 4.1 host controller initialization */
+static void ehci_reset(void *opaque)
+{
+ EHCIState *s = opaque;
+ uint8_t *pci_conf;
+ int i;
+
+ pci_conf = s->dev.config;
+
+ memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE);
+
+ s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
+ s->usbsts = USBSTS_HALT;
+
+ s->astate = EST_INACTIVE;
+ s->pstate = EST_INACTIVE;
+ s->async_port_in_progress = -1;
+ s->async_complete = 0;
+ s->isoch_pause = -1;
+ s->attach_poll_counter = 0;
+
+ for(i = 0; i < NB_PORTS; i++) {
+ s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER;
+
+ if (s->ports[i].dev)
+ ehci_attach(&s->ports[i], s->ports[i].dev);
+ }
+}
+
+static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr)
+{
+ EHCIState *s = ptr;
+ uint32_t val;
+
+ val = s->mmio[addr];
+
+ return val;
+}
+
+static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr)
+{
+ EHCIState *s = ptr;
+ uint32_t val;
+
+ val = s->mmio[addr] | (s->mmio[addr+1] << 8);
+
+ return val;
+}
+
+static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr)
+{
+ 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);
+
+ 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_status_write(EHCIState *s, int port, uint32_t val)
+{
+ uint32_t *portsc = &s->portsc[port];
+ int rwc;
+ USBDevice *dev = s->ports[port].dev;
+
+ DPRINTF("port_status_write: "
+ "PORTSC (port %d) curr %08X new %08X rw-clear %08X rw %08X\n",
+ port, *portsc, val, (val & PORTSC_RWC_MASK), val & PORTSC_RO_MASK);
+
+ rwc = val & PORTSC_RWC_MASK;
+ val &= PORTSC_RO_MASK;
+
+ // handle_read_write_clear(&val, portsc, PORTSC_PEDC | PORTSC_CSC);
+
+ *portsc &= ~rwc;
+
+ if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
+ DPRINTF("port_status_write: USBTRAN Port %d reset begin\n", port);
+ }
+
+ if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
+ DPRINTF("port_status_write: USBTRAN Port %d reset done\n", port);
+ ehci_attach(&s->ports[port], dev);
+
+ // TODO how to handle reset of ports with no device
+ if (dev)
+ usb_send_msg(dev, USB_MSG_RESET);
+
+ if (s->ports[port].dev) {
+ DPRINTF("port_status_write: "
+ "Device was connected before reset, clearing CSC bit\n");
+ *portsc &= ~PORTSC_CSC;
+ }
+
+ /* Table 2.16 Set the enable bit(and enable bit change) to indicate
+ * to SW that this port has a high speed device attached
+ *
+ * TODO - when to disable?
+ */
+ val |= PORTSC_PED;
+ val |= PORTSC_PEDC;
+ }
+
+ *portsc &= ~PORTSC_RO_MASK;
+ *portsc |= val;
+ DPRINTF("port_status_write: Port %d status set to 0x%08x\n", port, *portsc);
+}
+
+static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
+{
+ EHCIState *s = ptr;
+ int i;
+#if EHCI_DEBUG
+ const char *str;
+#endif
+
+ /* 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);
+ return;
+ }
+
+ if (addr < OPREGBASE) {
+ fprintf(stderr, "usb-ehci: write attempt to read-only register"
+ TARGET_FMT_plx "\n", addr);
+ return;
+ }
+
+
+ /* Do any register specific pre-write processing here. */
+#if EHCI_DEBUG
+ str = addr2str((unsigned) addr);
+#endif
+ switch(addr)
+ {
+ case USBCMD:
+ DPRINTF("ehci_mem_writel: USBCMD val=0x%08X, current cmd=0x%08X\n",
+ val, s->usbcmd);
+
+ if ((val & USBCMD_RUNSTOP) && !(s->usbcmd & USBCMD_RUNSTOP)) {
+ DPRINTF("ehci_mem_writel: %s run, clear halt\n", str);
+ qemu_mod_timer(s->frame_timer, qemu_get_clock(vm_clock));
+ SET_LAST_RUN_CLOCK(s);
+ s->usbsts &= ~USBSTS_HALT;
+ }
+
+ if (!(val & USBCMD_RUNSTOP) && (s->usbcmd & USBCMD_RUNSTOP)) {
+ DPRINTF(" ** STOP **\n");
+ qemu_del_timer(s->frame_timer);
+ // TODO - should finish out some stuff before setting halt
+ s->usbsts |= USBSTS_HALT;
+ }
+
+ if (val & USBCMD_HCRESET) {
+ DPRINTF("ehci_mem_writel: %s run, resetting\n", str);
+ ehci_reset(s);
+ val &= ~USBCMD_HCRESET;
+ }
+
+ /* 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);
+ val &= ~USBCMD_FLS;
+ }
+#if EHCI_DEBUG
+ if ((val & USBCMD_PSE) && !(s->usbcmd & USBCMD_PSE)) {
+ DPRINTF("periodic scheduling enabled\n");
+ }
+ if (!(val & USBCMD_PSE) && (s->usbcmd & USBCMD_PSE)) {
+ DPRINTF("periodic scheduling disabled\n");
+ }
+ if ((val & USBCMD_ASE) && !(s->usbcmd & USBCMD_ASE)) {
+ DPRINTF("asynchronous scheduling enabled\n");
+ }
+ if (!(val & USBCMD_ASE) && (s->usbcmd & USBCMD_ASE)) {
+ DPRINTF("asynchronous scheduling disabled\n");
+ }
+ if ((val & USBCMD_IAAD) && !(s->usbcmd & USBCMD_IAAD)) {
+ DPRINTF("doorbell request received\n");
+ }
+ if ((val & USBCMD_LHCR) && !(s->usbcmd & USBCMD_LHCR)) {
+ DPRINTF("light host controller reset received\n");
+ }
+ if ((val & USBCMD_ITC) != (s->usbcmd & USBCMD_ITC)) {
+ DPRINTF("interrupt threshold control set to %x\n",
+ (val & USBCMD_ITC)>>USBCMD_ITC_SH);
+ }
+#endif
+ break;
+
+
+ case USBSTS:
+ val &= USBSTS_RO_MASK; // bits 6 thru 31 are RO
+ DPRINTF("ehci_mem_writel: %s RWC set to 0x%08X\n", str, val);
+
+ val = (s->usbsts &= ~val); // bits 0 thru 5 are R/WC
+
+ DPRINTF("ehci_mem_writel: %s updating interrupt condition\n", str);
+ ehci_set_interrupt(s, 0);
+ break;
+
+
+ case USBINTR:
+ val &= USBINTR_MASK;
+ DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
+ break;
+
+ case FRINDEX:
+ s->sofv = val >> 3;
+ DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
+ break;
+
+ case CONFIGFLAG:
+ DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
+ val &= 0x1;
+ if (val) {
+ for(i = 0; i < NB_PORTS; i++)
+ s->portsc[i] &= ~PORTSC_POWNER;
+ }
+ break;
+
+ case PERIODICLISTBASE:
+ if ((s->usbcmd & USBCMD_PSE) && (s->usbcmd & USBCMD_RUNSTOP)) {
+ fprintf(stderr,
+ "ehci: PERIODIC list base register set while periodic schedule\n"
+ " is enabled and HC is enabled\n");
+ }
+ DPRINTF("ehci_mem_writel: P-LIST BASE set to 0x%08X\n", val);
+ break;
+
+ case ASYNCLISTADDR:
+ if ((s->usbcmd & USBCMD_ASE) && (s->usbcmd & USBCMD_RUNSTOP)) {
+ fprintf(stderr,
+ "ehci: ASYNC list address register set while async schedule\n"
+ " is enabled and HC is enabled\n");
+ }
+ DPRINTF("ehci_mem_writel: A-LIST ADDR set to 0x%08X\n", val);
+ break;
+ }
+
+ *(uint32_t *)(&s->mmio[addr]) = val;
+}
+
+
+// TODO : Put in common header file, duplication from usb-ohci.c
+
+/* Get an array of dwords from main memory */
+static inline int get_dwords(uint32_t addr, uint32_t *buf, int num)
+{
+ int i;
+
+ for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ cpu_physical_memory_rw(addr,(uint8_t *)buf, sizeof(*buf), 0);
+ *buf = le32_to_cpu(*buf);
+ }
+
+ return 1;
+}
+
+/* Put an array of dwords in to main memory */
+static inline int put_dwords(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);
+ cpu_physical_memory_rw(addr,(uint8_t *)&tmp, sizeof(tmp), 1);
+ }
+
+ return 1;
+}
+
+// 4.10.2
+
+static int ehci_qh_do_overlay(EHCIState *ehci, EHCIqh *qh, EHCIqtd *qtd)
+{
+ int i;
+ int dtoggle;
+ int ping;
+ int eps;
+ int reload;
+
+ // remember values in fields to preserve in qh after overlay
+
+ dtoggle = qh->token & QTD_TOKEN_DTOGGLE;
+ ping = qh->token & QTD_TOKEN_PING;
+
+ DPRINTF("setting qh.current from %08X to 0x%08X\n", qh->current_qtd,
+ ehci->qtdaddr);
+ qh->current_qtd = ehci->qtdaddr;
+ qh->next_qtd = qtd->next;
+ qh->altnext_qtd = qtd->altnext;
+ qh->token = qtd->token;
+
+
+ eps = get_field(qh->epchar, QH_EPCHAR_EPS);
+ if (eps == EHCI_QH_EPS_HIGH) {
+ qh->token &= ~QTD_TOKEN_PING;
+ qh->token |= ping;
+ }
+
+ reload = get_field(qh->epchar, QH_EPCHAR_RL);
+ set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
+
+ for (i = 0; i < 5; i++) {
+ qh->bufptr[i] = qtd->bufptr[i];
+ }
+
+ if (!(qh->epchar & QH_EPCHAR_DTC)) {
+ // preserve QH DT bit
+ qh->token &= ~QTD_TOKEN_DTOGGLE;
+ qh->token |= dtoggle;
+ }
+
+ qh->bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
+ qh->bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
+
+ put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
+
+ return 0;
+}
+
+static int ehci_buffer_rw(uint8_t *buffer, EHCIqh *qh, int bytes, int rw)
+{
+ int bufpos = 0;
+ int cpage, offset;
+ uint32_t head;
+ uint32_t tail;
+
+
+ if (!bytes)
+ return 0;
+
+ cpage = get_field(qh->token, QTD_TOKEN_CPAGE);
+ if (cpage > 4) {
+ fprintf(stderr, "cpage out of range (%d)\n", cpage);
+ return USB_RET_PROCERR;
+ }
+
+ offset = qh->bufptr[0] & ~QTD_BUFPTR_MASK;
+ DPRINTF("ehci_buffer_rw: %sing %d bytes %08x cpage %d offset %d\n",
+ rw ? "writ" : "read", bytes, qh->bufptr[0], cpage, offset);
+
+ do {
+ /* start and end of this page */
+ head = qh->bufptr[cpage] & QTD_BUFPTR_MASK;
+ tail = head + ~QTD_BUFPTR_MASK + 1;
+ /* add offset into page */
+ head |= offset;
+
+ if (bytes <= (tail - head)) {
+ tail = head + bytes;
+ }
+
+ DPRINTF("DATA %s cpage:%d head:%08X tail:%08X target:%08X\n",
+ rw ? "WRITE" : "READ ", cpage, head, tail, bufpos);
+
+ cpu_physical_memory_rw(head, &buffer[bufpos], tail - head, rw);
+
+ bufpos += (tail - head);
+ bytes -= (tail - head);
+
+ if (bytes > 0) {
+ cpage++;
+ offset = 0;
+ }
+ } while (bytes > 0);
+
+ /* save cpage */
+ set_field(&qh->token, cpage, QTD_TOKEN_CPAGE);
+
+ /* save offset into cpage */
+ offset = tail - head;
+ qh->bufptr[0] &= ~QTD_BUFPTR_MASK;
+ qh->bufptr[0] |= offset;
+
+ return 0;
+}
+
+static void ehci_async_complete_packet(USBPacket *packet, void *opaque)
+{
+ EHCIState *ehci = opaque;
+
+ DPRINTF("Async packet complete\n");
+ ehci->async_complete = 1;
+ ehci->exec_status = packet->len;
+}
+
+static int ehci_execute_complete(EHCIState *ehci,
+ EHCIqh *qh,
+ int ret)
+{
+ int i, c_err, reload;
+
+ if (ret == USB_RET_ASYNC && !ehci->async_complete) {
+ DPRINTF("not done yet\n");
+ return ret;
+ }
+
+ ehci->async_complete = 0;
+ i = ehci->async_port_in_progress;
+ ehci->async_port_in_progress = -1;
+
+ DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n",
+ ehci->qhaddr, qh->next, ehci->qtdaddr, ret);
+
+ if (ret < 0) {
+err:
+ /* TO-DO: put this is in a function that can be invoked below as well */
+ c_err = get_field(qh->token, QTD_TOKEN_CERR);
+ c_err--;
+ set_field(&qh->token, c_err, QTD_TOKEN_CERR);
+
+ switch(ret) {
+ case USB_RET_NODEV:
+ fprintf(stderr, "USB no device\n");
+ break;
+ case USB_RET_STALL:
+ fprintf(stderr, "USB stall\n");
+ qh->token |= QTD_TOKEN_HALT;
+ break;
+ case USB_RET_NAK:
+ /* 4.10.3 */
+ reload = get_field(qh->epchar, QH_EPCHAR_RL);
+ if ((ehci->pid == USB_TOKEN_IN) && reload) {
+ int nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
+ nakcnt--;
+ set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
+ } else if (!reload) {
+ return USB_RET_NAK;
+ }
+ break;
+ case USB_RET_BABBLE:
+ fprintf(stderr, "USB babble TODO\n");
+ qh->token |= QTD_TOKEN_BABBLE;
+ break;
+ default:
+ fprintf(stderr, "USB invalid response %d to handle\n", ret);
+ /* TO-DO: transaction error */
+ ret = USB_RET_PROCERR;
+ break;
+ }
+ } else {
+ // DPRINTF("Short packet condition\n");
+ // TODO check 4.12 for splits
+
+ if ((ret > ehci->tbytes) && (ehci->pid == USB_TOKEN_IN)) {
+ ret = USB_RET_BABBLE;
+ goto err;
+ }
+
+ if (ehci->tbytes && ehci->pid == USB_TOKEN_IN) {
+ if (ehci_buffer_rw(ehci->buffer, qh, ret, 1) != 0) {
+ return USB_RET_PROCERR;
+ }
+ ehci->tbytes -= ret;
+ } else {
+ ehci->tbytes = 0;
+ }
+
+ DPRINTF("updating tbytes to %d\n", ehci->tbytes);
+ set_field(&qh->token, ehci->tbytes, QTD_TOKEN_TBYTES);
+ }
+
+ qh->token ^= QTD_TOKEN_DTOGGLE;
+ qh->token &= ~QTD_TOKEN_ACTIVE;
+
+ if ((ret >= 0) && (qh->token & QTD_TOKEN_IOC)) {
+ // TODO should do this after writeback to memory
+ ehci_set_interrupt(ehci, USBSTS_INT);
+ }
+
+ return ret;
+}
+
+// 4.10.3
+
+static int ehci_execute(EHCIState *ehci, EHCIqh *qh)
+{
+ USBPort *port;
+ USBDevice *dev;
+ int ret;
+ int i;
+ int endp;
+ int devadr;
+
+ if ( !(qh->token & QTD_TOKEN_ACTIVE)) {
+ fprintf(stderr, "Attempting to execute inactive QH\n");
+ return USB_RET_PROCERR;
+ }
+
+ ehci->tbytes = (qh->token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH;
+ if (ehci->tbytes > BUFF_SIZE) {
+ fprintf(stderr, "Request for more bytes than allowed\n");
+ return USB_RET_PROCERR;
+ }
+
+ ehci->pid = (qh->token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH;
+ switch(ehci->pid) {
+ case 0: ehci->pid = USB_TOKEN_OUT; break;
+ case 1: ehci->pid = USB_TOKEN_IN; break;
+ case 2: ehci->pid = USB_TOKEN_SETUP; break;
+ default: fprintf(stderr, "bad token\n"); break;
+ }
+
+ if ((ehci->tbytes && ehci->pid != USB_TOKEN_IN) &&
+ (ehci_buffer_rw(ehci->buffer, qh, ehci->tbytes, 0) != 0)) {
+ return USB_RET_PROCERR;
+ }
+
+ endp = get_field(qh->epchar, QH_EPCHAR_EP);
+ devadr = get_field(qh->epchar, QH_EPCHAR_DEVADDR);
+
+ ret = USB_RET_NODEV;
+
+ // TO-DO: associating device with ehci port
+ for(i = 0; i < NB_PORTS; i++) {
+ port = &ehci->ports[i];
+ dev = port->dev;
+
+ // TODO sometime we will also need to check if we are the port owner
+
+ if (!(ehci->portsc[i] &(PORTSC_CONNECT))) {
+ DPRINTF("Port %d, no exec, not connected(%08X)\n",
+ i, ehci->portsc[i]);
+ continue;
+ }
+
+ ehci->usb_packet.pid = ehci->pid;
+ ehci->usb_packet.devaddr = devadr;
+ ehci->usb_packet.devep = endp;
+ ehci->usb_packet.data = ehci->buffer;
+ ehci->usb_packet.len = ehci->tbytes;
+ ehci->usb_packet.complete_cb = ehci_async_complete_packet;
+ ehci->usb_packet.complete_opaque = ehci;
+
+ ret = dev->info->handle_packet(dev, &ehci->usb_packet);
+
+ DPRINTF("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d\n",
+ ehci->qhaddr, qh->next, ehci->qtdaddr, ehci->pid,
+ ehci->usb_packet.len, ehci->tbytes, endp, ret);
+
+ if (ret != USB_RET_NODEV)
+ break;
+ }
+
+ if (ret > BUFF_SIZE) {
+ fprintf(stderr, "ret from handle packet > BUFF_SIZE\n");
+ return USB_RET_PROCERR;
+ }
+
+ if (ret == USB_RET_ASYNC) {
+ ehci->async_port_in_progress = i;
+ ehci->async_complete = 0;
+ }
+
+ return ret;
+}
+
+/* 4.7.2
+ */
+
+static int ehci_process_itd(EHCIState *ehci,
+ EHCIitd *itd)
+{
+ USBPort *port;
+ USBDevice *dev;
+ int ret;
+ int i, j;
+ int ptr;
+ int pid;
+ int pg;
+ int len;
+ int dir;
+ int devadr;
+ int endp;
+ int maxpkt;
+
+ dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
+ devadr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
+ endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP);
+ maxpkt = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT);
+
+#ifdef EHCI_NOMICROFRAMES
+ for(i = 0; i < 8; i++) {
+#else
+ i =(ehci->frindex & 7);
+#endif
+
+ if (itd->transact[i] & ITD_XACT_ACTIVE) {
+ DPRINTF("ISOCHRONOUS active for frame %d, interval %d\n",
+ ehci->frindex >> 3, i);
+
+ pg = get_field(itd->transact[i], ITD_XACT_PGSEL);
+ ptr = (itd->bufptr[pg] & ITD_BUFPTR_MASK) |
+ (itd->transact[i] & ITD_XACT_OFFSET_MASK);
+ len = get_field(itd->transact[i], ITD_XACT_LENGTH);
+
+ if (len > BUFF_SIZE) {
+ return USB_RET_PROCERR;
+ }
+
+ DPRINTF("ISOCH: buffer %08X len %d\n", ptr, len);
+
+ if (!dir) {
+ cpu_physical_memory_rw(ptr, &ehci->buffer[0], len, 0);
+ pid = USB_TOKEN_OUT;
+ } else
+ pid = USB_TOKEN_IN;
+
+ ret = USB_RET_NODEV;
+
+ for(j = 0; j < NB_PORTS; j++) {
+ port = &ehci->ports[j];
+ dev = port->dev;
+
+ // TODO sometime we will also need to check if we are the port owner
+
+ if (!(ehci->portsc[j] &(PORTSC_CONNECT))) {
+ DPRINTF("Port %d, no exec, not connected(%08X)\n",
+ j, ehci->portsc[j]);
+ continue;
+ }
+
+ ehci->usb_packet.pid = ehci->pid;
+ ehci->usb_packet.devaddr = devadr;
+ ehci->usb_packet.devep = endp;
+ ehci->usb_packet.data = ehci->buffer;
+ ehci->usb_packet.len = len;
+ ehci->usb_packet.complete_cb = ehci_async_complete_packet;
+ ehci->usb_packet.complete_opaque = ehci;
+
+ DPRINTF("calling dev->info->handle_packet\n");
+ ret = dev->info->handle_packet(dev, &ehci->usb_packet);
+
+ if (ret != USB_RET_NODEV)
+ break;
+ }
+
+ /* In isoch, there is no facility to indicate a NAK so let's
+ * instead just complete a zero-byte transaction. Setting
+ * DBERR seems too draconian.
+ */
+
+ if (ret == USB_RET_NAK) {
+ if (ehci->isoch_pause > 0) {
+ DPRINTF("ISOCH: received a NAK but paused so returning\n");
+ ehci->isoch_pause--;
+ return 0;
+ } else if (ehci->isoch_pause == -1) {
+ DPRINTF("ISOCH: recv NAK & isoch pause inactive, setting\n");
+ // Pause frindex for up to 50 msec waiting for data from
+ // remote
+ ehci->isoch_pause = 50;
+ return 0;
+ } else {
+ DPRINTF("ISOCH: isoch pause timeout! return 0\n");
+ ret = 0;
+ }
+ } else {
+ DPRINTF("ISOCH: received ACK, clearing pause\n");
+ ehci->isoch_pause = -1;
+ }
+
+ if (ret >= 0) {
+ itd->transact[i] &= ~ITD_XACT_ACTIVE;
+
+ if (itd->transact[i] & ITD_XACT_IOC) {
+ // TODO should do this after writeback to memory
+ ehci_set_interrupt(ehci, USBSTS_INT);
+ }
+ }
+
+ if (ret >= 0 && dir) {
+ cpu_physical_memory_rw(ptr, &ehci->buffer[0], len, 1);
+
+ if (ret != len) {
+ DPRINTF("ISOCH IN expected %d, got %d\n",
+ len, ret);
+ set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
+ }
+ }
+ }
+
+#ifdef EHCI_NOMICROFRAMES
+ }
+#endif
+ return 0;
+}
+
+/* This state is the entry point for asynchronous schedule
+ * processing. Entry here consitutes a EHCI start event state (4.8.5)
+ */
+static int ehci_state_waitlisthead(EHCIState *ehci, int async, int *state)
+{
+ EHCIqh *qh = &ehci->qh;
+ int i = 0;
+ int again = 0;
+ uint32_t entry = ehci->asynclistaddr;
+
+ /* set reclamation flag at start event (4.8.6) */
+ if (async) {
+ ehci->usbsts |= USBSTS_REC;
+ }
+
+ /* Find the head of the list (4.9.1.1) */
+ for(i = 0; i < MAX_QH; i++) {
+ get_dwords(NLPTR_GET(entry), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
+
+ if (qh->epchar & QH_EPCHAR_H) {
+ DPRINTF_ST("WAITLISTHEAD: QH %08X is the HEAD of the list\n",
+ entry);
+ if (async)
+ entry |= (NLPTR_TYPE_QH << 1);
+
+ ehci->fetch_addr = entry;
+ *state = EST_FETCHENTRY;
+ again = 1;
+ goto out;
+ }
+
+ DPRINTF_ST("WAITLISTHEAD: QH %08X is NOT the HEAD of the list\n",
+ entry);
+ entry = qh->next;
+ if (entry == ehci->asynclistaddr) {
+ DPRINTF("WAITLISTHEAD: reached beginning of QH list\n");
+ break;
+ }
+ }
+
+ /* no head found for list. */
+
+ *state = EST_ACTIVE;
+
+out:
+ return again;
+}
+
+
+/* This state is the entry point for periodic schedule processing as
+ * well as being a continuation state for async processing.
+ */
+static int ehci_state_fetchentry(EHCIState *ehci, int async, int *state)
+{
+ int again = 0;
+ uint32_t entry = ehci->fetch_addr;
+
+#if EHCI_DEBUG == 0
+ if (qemu_get_clock(vm_clock) / 1000 >= ehci->frame_end_usec) {
+ if (async) {
+ DPRINTF("FETCHENTRY: FRAME timer elapsed, exit state machine\n");
+ goto out;
+ } else {
+ DPRINTF("FETCHENTRY: WARNING "
+ "- frame timer elapsed during periodic\n");
+ }
+ }
+#endif
+ if (entry < 0x1000) {
+ DPRINTF("fetchentry: entry invalid (0x%08x)\n", entry);
+ *state = EST_ACTIVE;
+ goto out;
+ }
+
+ /* section 4.8, only QH in async schedule */
+ if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
+ fprintf(stderr, "non queue head request in async schedule\n");
+ return -1;
+ }
+
+ switch (NLPTR_TYPE_GET(entry)) {
+ case NLPTR_TYPE_QH:
+ DPRINTF_ST("FETCHENTRY: entry %X is a Queue Head\n", entry);
+ *state = EST_FETCHQH;
+ ehci->qhaddr = entry;
+ again = 1;
+ break;
+
+ case NLPTR_TYPE_ITD:
+ DPRINTF_ST("FETCHENTRY: entry %X is an ITD\n", entry);
+ *state = EST_FETCHITD;
+ ehci->itdaddr = entry;
+ again = 1;
+ break;
+
+ default:
+ // TODO: handle siTD and FSTN types
+ fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
+ "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry));
+ return -1;
+ }
+
+out:
+ return again;
+}
+
+static int ehci_state_fetchqh(EHCIState *ehci, int async, int *state)
+{
+ EHCIqh *qh = &ehci->qh;
+ int reload;
+ int again = 0;
+
+ get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
+
+ if (async && (qh->epchar & QH_EPCHAR_H)) {
+
+ /* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
+ if (ehci->usbsts & USBSTS_REC) {
+ ehci->usbsts &= ~USBSTS_REC;
+ } else {
+ DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset"
+ " - done processing\n", ehci->qhaddr);
+ *state = EST_ACTIVE;
+ goto out;
+ }
+ }
+
+#if EHCI_DEBUG
+ if (ehci->qhaddr != qh->next) {
+ DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n",
+ ehci->qhaddr,
+ qh->epchar & QH_EPCHAR_H,
+ qh->token & QTD_TOKEN_HALT,
+ qh->token & QTD_TOKEN_ACTIVE,
+ qh->next);
+ }
+#endif
+
+ reload = get_field(qh->epchar, QH_EPCHAR_RL);
+ if (reload) {
+ DPRINTF_ST("FETCHQH: reloading nakcnt to %d\n", reload);
+ set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
+ }
+
+ if (qh->token & QTD_TOKEN_HALT) {
+ DPRINTF_ST("FETCHQH: QH Halted, go horizontal\n");
+ *state = EST_HORIZONTALQH;
+ again = 1;
+
+ } else if ((qh->token & QTD_TOKEN_ACTIVE) && (qh->current_qtd > 0x1000)) {
+ DPRINTF_ST("FETCHQH: Active, !Halt, execute - fetch qTD\n");
+ ehci->qtdaddr = qh->current_qtd;
+ *state = EST_FETCHQTD;
+ again = 1;
+
+ } else {
+ /* EHCI spec version 1.0 Section 4.10.2 */
+ DPRINTF_ST("FETCHQH: !Active, !Halt, advance queue\n");
+ *state = EST_ADVANCEQUEUE;
+ again = 1;
+ }
+
+out:
+ return again;
+}
+
+static int ehci_state_fetchitd(EHCIState *ehci, int async, int *state)
+{
+ EHCIitd itd;
+
+ get_dwords(NLPTR_GET(ehci->itdaddr),(uint32_t *) &itd,
+ sizeof(EHCIitd) >> 2);
+ DPRINTF_ST("FETCHITD: Fetched ITD at address %08X " "(next is %08X)\n",
+ ehci->itdaddr, itd.next);
+
+ if (ehci_process_itd(ehci, &itd) != 0)
+ return -1;
+
+ put_dwords(NLPTR_GET(ehci->itdaddr), (uint32_t *) &itd,
+ sizeof(EHCIitd) >> 2);
+ ehci->itdaddr = itd.next;
+ *state = EST_FETCHITD;
+
+ return 1;
+}
+
+/* Section 4.10.2 - paragraph 3 */
+static int ehci_state_advqueue(EHCIState *ehci, int async, int *state)
+{
+#if 0
+ /* TO-DO: 4.10.2 - paragraph 2
+ * if I-bit is set to 1 and QH is not active
+ * go to horizontal QH
+ */
+ if (I-bit set) {
+ *state = EST_HORIZONTALQH;
+ goto out;
+ }
+#endif
+
+ /*
+ * want data and alt-next qTD is valid
+ */
+ if (((ehci->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
+ (ehci->qh.altnext_qtd > 0x1000) &&
+ (NLPTR_TBIT(ehci->qh.altnext_qtd) == 0)) {
+ DPRINTF_ST("ADVQUEUE: goto alt next qTD. "
+ "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n",
+ ehci->qh.current_qtd, ehci->qh.altnext_qtd,
+ ehci->qh.next_qtd, ehci->qh.next);
+ ehci->qtdaddr = ehci->qh.altnext_qtd;
+ *state = EST_FETCHQTD;
+
+ /*
+ * next qTD is valid
+ */
+ } else if ((ehci->qh.next_qtd > 0x1000) &&
+ (NLPTR_TBIT(ehci->qh.next_qtd) == 0)) {
+ DPRINTF_ST("ADVQUEUE: next qTD. "
+ "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n",
+ ehci->qh.current_qtd, ehci->qh.altnext_qtd,
+ ehci->qh.next_qtd, ehci->qh.next);
+ ehci->qtdaddr = ehci->qh.next_qtd;
+ *state = EST_FETCHQTD;
+
+ /*
+ * no valid qTD, try next QH
+ */
+ } else {
+ DPRINTF_ST("ADVQUEUE: go to horizontal QH\n");
+ *state = EST_HORIZONTALQH;
+ }
+
+ return 1;
+}
+
+/* Section 4.10.2 - paragraph 4 */
+static int ehci_state_fetchqtd(EHCIState *ehci, int async, int *state)
+{
+ EHCIqtd *qtd = &ehci->qtd;
+ int again = 0;
+
+ get_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) qtd, sizeof(EHCIqtd) >> 2);
+
+ if (qtd->token & QTD_TOKEN_ACTIVE) {
+ *state = EST_EXECUTE;
+ again = 1;
+ } else {
+ *state = EST_HORIZONTALQH;
+ again = 1;
+ }
+
+ return again;
+}
+
+static int ehci_state_horizqh(EHCIState *ehci, int async, int *state)
+{
+ int again = 0;
+
+ if (ehci->fetch_addr != ehci->qh.next) {
+ ehci->fetch_addr = ehci->qh.next;
+ *state = EST_FETCHENTRY;
+ again = 1;
+ } else {
+ *state = EST_ACTIVE;
+ }
+
+ return again;
+}
+
+static int ehci_state_execute(EHCIState *ehci, int async, int *state)
+{
+ EHCIqh *qh = &ehci->qh;
+ EHCIqtd *qtd = &ehci->qtd;
+ int again = 0;
+ int reload, nakcnt;
+ int smask;
+
+ if (async) {
+ DPRINTF_ST(">>>>> ASYNC STATE MACHINE execute QH 0x%08x, QTD 0x%08x\n",
+ ehci->qhaddr, ehci->qtdaddr);
+ } else {
+ DPRINTF_ST(">>>>> PERIODIC STATE MACHINE execute\n");
+ }
+
+ if (ehci_qh_do_overlay(ehci, qh, qtd) != 0)
+ return -1;
+
+ smask = get_field(qh->epcap, QH_EPCAP_SMASK);
+#ifndef EHCI_NOMICROFRAMES
+ if (smask && (smask & (1 << (ehci->frindex & 7))) == 0) {
+ DPRINTF_ST("PERIODIC active not interval: mask %x, frindex %d,%d\n",
+ smask, (ehci->frindex >> 3),(ehci->frindex & 7));
+
+ *state = EST_HORIZONTALQH;
+ again = 1;
+ goto out;
+ }
+#endif
+
+ if (!smask) {
+ reload = get_field(qh->epchar, QH_EPCHAR_RL);
+ nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
+ if (reload && !nakcnt) {
+ DPRINTF_ST("EXECUTE: RL != 0 but NakCnt == 0 -- no execute\n");
+ *state = EST_HORIZONTALQH;
+ again = 1;
+ goto out;
+ }
+ }
+
+ // 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 (!async)
+ {
+ int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT);
+ if (!transactCtr) {
+ DPRINTF("ZERO transactctr for int qh, go HORIZ\n");
+ *state = EST_HORIZONTALQH;
+ again = 1;
+ goto out;
+ }
+ }
+
+
+ if (async)
+ ehci->usbsts |= USBSTS_REC;
+
+ ehci->exec_status = ehci_execute(ehci, qh);
+ if (ehci->exec_status == USB_RET_PROCERR) {
+ again = -1;
+ goto out;
+ }
+ *state = EST_EXECUTING;
+
+ if (ehci->exec_status != USB_RET_ASYNC)
+ again = 1;
+
+out:
+ return again;
+}
+
+static int ehci_state_executing(EHCIState *ehci, int async, int *state)
+{
+ EHCIqh *qh = &ehci->qh;
+ int again = 0;
+ int reload, nakcnt;
+
+ ehci->exec_status = ehci_execute_complete(ehci, qh, ehci->exec_status);
+ if (ehci->exec_status == USB_RET_ASYNC) {
+ goto out;
+ }
+ if (ehci->exec_status == USB_RET_PROCERR) {
+ again = -1;
+ goto out;
+ }
+
+ // 4.10.3
+ if (!async) {
+ int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT);
+ transactCtr--;
+ set_field(&qh->epcap, transactCtr, QH_EPCAP_MULT);
+ // 4.10.3, bottom of page 82, should exit this state when transaction
+ // counter decrements to 0
+ }
+
+
+ reload = get_field(qh->epchar, QH_EPCHAR_RL);
+ if (reload) {
+ nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
+ if (ehci->exec_status == USB_RET_NAK) {
+ if (nakcnt) {
+ nakcnt--;
+ }
+ DPRINTF_ST("EXECUTING: Nak occured and RL != 0, dec NakCnt to %d\n",
+ nakcnt);
+ } else {
+ nakcnt = reload;
+ DPRINTF_ST("EXECUTING: Nak didn't occur, reloading to %d\n",
+ nakcnt);
+ }
+ set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
+ }
+
+ /*
+ * Write the qh back to guest physical memory. This step isn't
+ * in the EHCI spec but we need to do it since we don't share
+ * physical memory with our guest VM.
+ */
+
+ DPRINTF("EXECUTING: write QH to VM memory: qhaddr 0x%x, next 0x%x\n",
+ ehci->qhaddr, qh->next);
+ put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
+
+ /* 4.10.5 */
+ if ((ehci->exec_status == USB_RET_NAK) || (qh->token & QTD_TOKEN_ACTIVE)) {
+ *state = EST_HORIZONTALQH;
+ } else {
+ *state = EST_WRITEBACK;
+ }
+
+ again = 1;
+
+out:
+ return again;
+}
+
+
+static int ehci_state_writeback(EHCIState *ehci, int async, int *state)
+{
+ EHCIqh *qh = &ehci->qh;
+ int again = 0;
+
+ /* Write back the QTD from the QH area */
+ DPRINTF_ST("WRITEBACK: write QTD to VM memory\n");
+ put_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) &qh->next_qtd,
+ sizeof(EHCIqtd) >> 2);
+
+ /* TODO confirm next state. For now, keep going if async
+ * but stop after one qtd if periodic
+ */
+ //if (async) {
+ *state = EST_ADVANCEQUEUE;
+ again = 1;
+ //} else {
+ // *state = EST_ACTIVE;
+ //}
+ return again;
+}
+
+/*
+ * This is the state machine that is common to both async and periodic
+ */
+
+static int ehci_advance_state(EHCIState *ehci,
+ int async,
+ int state)
+{
+ int again;
+ int iter = 0;
+
+ do {
+ if (state == EST_FETCHQH) {
+ iter++;
+ /* if we are roaming a lot of QH without executing a qTD
+ * something is wrong with the linked list. TO-DO: why is
+ * this hack needed?
+ */
+ if (iter > MAX_ITERATIONS) {
+ DPRINTF("\n*** advance_state: bailing on MAX ITERATIONS***\n");
+ state = EST_ACTIVE;
+ break;
+ }
+ }
+ switch(state) {
+ case EST_WAITLISTHEAD:
+ again = ehci_state_waitlisthead(ehci, async, &state);
+ break;
+
+ case EST_FETCHENTRY:
+ again = ehci_state_fetchentry(ehci, async, &state);
+ break;
+
+ case EST_FETCHQH:
+ again = ehci_state_fetchqh(ehci, async, &state);
+ break;
+
+ case EST_FETCHITD:
+ again = ehci_state_fetchitd(ehci, async, &state);
+ break;
+
+ case EST_ADVANCEQUEUE:
+ again = ehci_state_advqueue(ehci, async, &state);
+ break;
+
+ case EST_FETCHQTD:
+ again = ehci_state_fetchqtd(ehci, async, &state);
+ break;
+
+ case EST_HORIZONTALQH:
+ again = ehci_state_horizqh(ehci, async, &state);
+ break;
+
+ case EST_EXECUTE:
+ iter = 0;
+ again = ehci_state_execute(ehci, async, &state);
+ break;
+
+ case EST_EXECUTING:
+ again = ehci_state_executing(ehci, async, &state);
+ break;
+
+ case EST_WRITEBACK:
+ again = ehci_state_writeback(ehci, async, &state);
+ break;
+
+ default:
+ fprintf(stderr, "Bad state!\n");
+ again = -1;
+ break;
+ }
+
+ if (again < 0) {
+ fprintf(stderr, "processing error - resetting ehci HC\n");
+ ehci_reset(ehci);
+ again = 0;
+ }
+ }
+ while (again);
+
+ return state;
+}
+
+static void ehci_advance_async_state(EHCIState *ehci)
+{
+ EHCIqh qh;
+ int state = ehci->astate;
+
+ switch(state) {
+ case EST_INACTIVE:
+ if (!(ehci->usbcmd & USBCMD_ASE)) {
+ break;
+ }
+ ehci->usbsts |= USBSTS_ASS;
+ ehci->astate = EST_ACTIVE;
+ // No break, fall through to ACTIVE
+
+ case EST_ACTIVE:
+ if ( !(ehci->usbcmd & USBCMD_ASE)) {
+ ehci->usbsts &= ~USBSTS_ASS;
+ ehci->astate = EST_INACTIVE;
+ break;
+ }
+
+ /* If the doorbell is set, the guest wants to make a change to the
+ * schedule. The host controller needs to release cached data.
+ * (section 4.8.2)
+ */
+ if (ehci->usbcmd & USBCMD_IAAD) {
+ DPRINTF("ASYNC: doorbell request acknowledged\n");
+ ehci->usbcmd &= ~USBCMD_IAAD;
+ ehci_set_interrupt(ehci, USBSTS_IAA);
+ break;
+ }
+
+ /* make sure guest has acknowledged */
+ /* TO-DO: is this really needed? */
+ if (ehci->usbsts & USBSTS_IAA) {
+ DPRINTF("IAA status bit still set.\n");
+ break;
+ }
+
+ DPRINTF_ST("ASYNC: waiting for listhead, starting at %08x\n",
+ ehci->asynclistaddr);
+ /* check that address register has been set */
+ if (ehci->asynclistaddr == 0) {
+ break;
+ }
+
+ state = EST_WAITLISTHEAD;
+ /* fall through */
+
+ case EST_FETCHENTRY:
+ /* fall through */
+
+ case EST_EXECUTING:
+ get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) &qh,
+ sizeof(EHCIqh) >> 2);
+ ehci->astate = ehci_advance_state(ehci, 1, state);
+ break;
+
+ default:
+ /* this should only be due to a developer mistake */
+ fprintf(stderr, "ehci: Bad asynchronous state %d. "
+ "Resetting to active\n", ehci->astate);
+ ehci->astate = EST_ACTIVE;
+ }
+}
+
+static void ehci_advance_periodic_state(EHCIState *ehci)
+{
+ uint32_t entry;
+ uint32_t list;
+
+ // 4.6
+
+ switch(ehci->pstate) {
+ case EST_INACTIVE:
+ if ( !(ehci->frindex & 7) && (ehci->usbcmd & USBCMD_PSE)) {
+ DPRINTF("PERIODIC going active\n");
+ ehci->usbsts |= USBSTS_PSS;
+ ehci->pstate = EST_ACTIVE;
+ // No break, fall through to ACTIVE
+ } else
+ break;
+
+ case EST_ACTIVE:
+ if ( !(ehci->frindex & 7) && !(ehci->usbcmd & USBCMD_PSE)) {
+ DPRINTF("PERIODIC going inactive\n");
+ ehci->usbsts &= ~USBSTS_PSS;
+ ehci->pstate = EST_INACTIVE;
+ break;
+ }
+
+ list = ehci->periodiclistbase & 0xfffff000;
+ /* check that register has been set */
+ if (list == 0) {
+ break;
+ }
+ list |= ((ehci->frindex & 0x1ff8) >> 1);
+
+ cpu_physical_memory_rw(list, (uint8_t *) &entry, sizeof entry, 0);
+ entry = le32_to_cpu(entry);
+
+ DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
+ ehci->frindex / 8, list, entry);
+ ehci->fetch_addr = entry;
+ ehci->pstate = ehci_advance_state(ehci, 0, EST_FETCHENTRY);
+ break;
+
+ case EST_EXECUTING:
+ DPRINTF("PERIODIC state adv for executing\n");
+ ehci->pstate = ehci_advance_state(ehci, 0, EST_EXECUTING);
+ break;
+
+ default:
+ /* this should only be due to a developer mistake */
+ fprintf(stderr, "ehci: Bad periodic state %d. "
+ "Resetting to active\n", ehci->pstate);
+ ehci->pstate = EST_ACTIVE;
+ }
+}
+
+static void ehci_frame_timer(void *opaque)
+{
+ EHCIState *ehci = opaque;
+ int64_t expire_time, t_now;
+ int usec_elapsed;
+ int frames;
+ int usec_now;
+ int i;
+ int skipped_frames = 0;
+
+
+ t_now = qemu_get_clock(vm_clock);
+ expire_time = t_now + (get_ticks_per_sec() / FRAME_TIMER_FREQ);
+ if (expire_time == t_now)
+ expire_time++;
+
+ usec_now = t_now / 1000;
+ usec_elapsed = usec_now - ehci->last_run_usec;
+ frames = usec_elapsed / FRAME_TIMER_USEC;
+ ehci->frame_end_usec = usec_now + FRAME_TIMER_USEC - 10;
+
+ for(i = 0; i < frames; i++) {
+ if ( !(ehci->usbsts & USBSTS_HALT)) {
+ if (ehci->isoch_pause <= 0) {
+#ifdef EHCI_NOMICROFRAMES
+ ehci->frindex += 8;
+#else
+ ehci->frindex++;
+#endif
+ }
+
+ if (ehci->frindex > 0x00001fff) {
+ ehci->frindex = 0;
+ ehci_set_interrupt(ehci, USBSTS_FLR);
+ }
+
+ ehci->sofv = (ehci->frindex - 1) >> 3;
+ ehci->sofv &= 0x000003ff;
+ }
+
+ if (frames - i > 10)
+ skipped_frames++;
+ else {
+ // TODO could this cause periodic frames to get skipped if async
+ // active?
+ if (ehci->astate != EST_EXECUTING)
+ ehci_advance_periodic_state(ehci);
+ }
+
+ ehci->last_run_usec += FRAME_TIMER_USEC;
+ }
+
+#if 0
+ if (skipped_frames)
+ DPRINTF("WARNING - EHCI skipped %d frames\n", skipped_frames);
+#endif
+
+ /* Async is not inside loop since it executes everything it can once
+ * called
+ */
+ if (ehci->pstate != EST_EXECUTING)
+ ehci_advance_async_state(ehci);
+
+ qemu_mod_timer(ehci->frame_timer, expire_time);
+}
+
+static CPUReadMemoryFunc *ehci_readfn[3]={
+ ehci_mem_readb,
+ ehci_mem_readw,
+ ehci_mem_readl
+};
+
+static CPUWriteMemoryFunc *ehci_writefn[3]={
+ ehci_mem_writeb,
+ ehci_mem_writew,
+ ehci_mem_writel
+};
+
+static void ehci_map(PCIDevice *pci_dev, int region_num,
+ pcibus_t addr, pcibus_t size, int type)
+{
+ EHCIState *s =(EHCIState *)pci_dev;
+
+ DPRINTF("ehci_map: region %d, addr %08llX, size %lld, s->mem %08X\n",
+ region_num, addr, size, s->mem);
+ s->mem_base = addr;
+ cpu_register_physical_memory(addr, size, s->mem);
+}
+
+static int usb_ehci_initfn(PCIDevice *dev);
+
+static PCIDeviceInfo ehci_info[] = {
+ {
+ .qdev.name = "usb-ehci",
+ .qdev.size = sizeof(EHCIState),
+ .init = usb_ehci_initfn,
+ },{
+ /* end of list */
+ }
+};
+
+static int usb_ehci_initfn(PCIDevice *dev)
+{
+ EHCIState *s = DO_UPCAST(EHCIState, dev, dev);
+ uint8_t *pci_conf = s->dev.config;
+ int i;
+
+ pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
+ pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82801D);
+ pci_set_byte(&pci_conf[PCI_REVISION_ID], 0x10);
+ pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20);
+ pci_config_set_class(pci_conf, PCI_CLASS_SERIAL_USB);
+ pci_set_byte(&pci_conf[PCI_HEADER_TYPE], PCI_HEADER_TYPE_NORMAL);
+
+ /* 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 3
+ 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[0x60], 0x20); // spec 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];
+
+ // TODO - port registration is going to need an overhaul since ports
+ // can be low, full or high speed and are not tied to UHCI or EHCI.
+ // This works for now since we register last so are top of the free
+ // list but really all ports need to be owned by EHCI and it should
+ // hand off to companion controllers if device is full or low speed.
+
+ DPRINTF("ehci_init : registering USB ports with no device attached\n");
+
+ // TODO come up with a better port allocation scheme
+ // added ehci->bus, need to find ehci->DeviceState
+ usb_bus_new_v2(&s->bus, &s->dev.qdev);
+ for(i = 0; i < NB_PORTS; i++) {
+ usb_register_port(&s->bus, &s->ports[i], s, i, ehci_attach);
+ s->ports[i].dev = 0;
+ }
+
+ s->frame_timer = qemu_new_timer(vm_clock, ehci_frame_timer, s);
+
+ qemu_register_reset(ehci_reset, s);
+
+ s->mem = cpu_register_io_memory(ehci_readfn, ehci_writefn, s);
+
+ DPRINTF("ehci_init: registering MMIO size %d\n", MMIO_SIZE);
+ pci_register_bar(&s->dev, 0, MMIO_SIZE, PCI_BASE_ADDRESS_SPACE_MEMORY,
+ ehci_map);
+
+ fprintf(stderr, "\n\n*** EHCI support is under development *** \n\n");
+
+ return 0;
+}
+
+static void ehci_register(void)
+{
+ pci_qdev_register_many(ehci_info);
+}
+device_init(ehci_register);
+
+void usb_ehci_init(PCIBus *bus, int devfn)
+{
+ pci_create_simple(bus, devfn, "usb-ehci");
+}
+
+/*
+ * vim: expandtab ts=4
+ */
diff --git a/hw/usb-ehci.h b/hw/usb-ehci.h
new file mode 100644
index 0000000..ba087a0
--- /dev/null
+++ b/hw/usb-ehci.h
@@ -0,0 +1,8 @@
+#ifndef QEMU_USB_EHCI_H
+#define QEMU_USB_EHCI_H
+
+#include "qemu-common.h"
+
+void usb_ehci_init(PCIBus *bus, int devfn);
+
+#endif
diff --git a/hw/usb-msd.c b/hw/usb-msd.c
index 0a95d8d..5ef9836 100644
--- a/hw/usb-msd.c
+++ b/hw/usb-msd.c
@@ -607,7 +607,7 @@ static USBDevice *usb_msd_init(const char *filename)
}
/* create guest device */
- dev = usb_create(NULL /* FIXME */, "usb-storage");
+ dev = usb_create_v2("usb-storage");
if (!dev) {
return NULL;
}
diff --git a/hw/usb-net.c b/hw/usb-net.c
index 58c672f..9d95379 100644
--- a/hw/usb-net.c
+++ b/hw/usb-net.c
@@ -1484,7 +1484,7 @@ static USBDevice *usb_net_init(const char *cmdline)
return NULL;
}
- dev = usb_create(NULL /* FIXME */, "usb-net");
+ dev = usb_create_v1("usb-net");
if (!dev) {
return NULL;
}
diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c
index c60fd8d..d38befc 100644
--- a/hw/usb-ohci.c
+++ b/hw/usb-ohci.c
@@ -1702,7 +1702,7 @@ static void usb_ohci_init(OHCIState *ohci, DeviceState *dev,
ohci->name = dev->info->name;
- usb_bus_new(&ohci->bus, dev);
+ usb_bus_new_v1(&ohci->bus, dev);
ohci->num_ports = num_ports;
for (i = 0; i < num_ports; i++) {
usb_register_port(&ohci->bus, &ohci->rhport[i].port, ohci, i, ohci_attach);
diff --git a/hw/usb-serial.c b/hw/usb-serial.c
index c19580f..f1c47b9 100644
--- a/hw/usb-serial.c
+++ b/hw/usb-serial.c
@@ -599,7 +599,7 @@ static USBDevice *usb_serial_init(const char *filename)
if (!cdrv)
return NULL;
- dev = usb_create(NULL /* FIXME */, "usb-serial");
+ dev = usb_create_v1("usb-serial");
if (!dev) {
return NULL;
}
@@ -622,7 +622,7 @@ static USBDevice *usb_braille_init(const char *unused)
if (!cdrv)
return NULL;
- dev = usb_create(NULL /* FIXME */, "usb-braille");
+ dev = usb_create_v1("usb-braille");
qdev_prop_set_chr(&dev->qdev, "chardev", cdrv);
qdev_init_nofail(&dev->qdev);
diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c
index 1d83400..0528757 100644
--- a/hw/usb-uhci.c
+++ b/hw/usb-uhci.c
@@ -1113,7 +1113,7 @@ static int usb_uhci_common_initfn(UHCIState *s)
pci_conf[PCI_INTERRUPT_PIN] = 4; // interrupt pin 3
pci_conf[0x60] = 0x10; // release number
- usb_bus_new(&s->bus, &s->dev.qdev);
+ usb_bus_new_v1(&s->bus, &s->dev.qdev);
for(i = 0; i < NB_PORTS; i++) {
usb_register_port(&s->bus, &s->ports[i].port, s, i, uhci_attach);
}
diff --git a/hw/usb.h b/hw/usb.h
index 00d2802..5040682 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -299,18 +299,22 @@ struct USBBus {
int busnr;
int nfree;
int nused;
+ int version;
QTAILQ_HEAD(, USBPort) free;
QTAILQ_HEAD(, USBPort) used;
QTAILQ_ENTRY(USBBus) next;
};
-void usb_bus_new(USBBus *bus, DeviceState *host);
-USBBus *usb_bus_find(int busnr);
+void usb_bus_new_v1(USBBus *bus, DeviceState *host);
+void usb_bus_new_v2(USBBus *bus, DeviceState *host);
+USBBus *usb_bus_find_version(int version);
void usb_qdev_register(USBDeviceInfo *info);
void usb_qdev_register_many(USBDeviceInfo *info);
-USBDevice *usb_create(USBBus *bus, const char *name);
+USBDevice *usb_create_v1(const char *name);
+USBDevice *usb_create_v2(const char *name);
USBDevice *usb_create_simple(USBBus *bus, const char *name);
USBDevice *usbdevice_create(const char *cmdline);
+int usb_bus_migrate_v1(USBDevice *dev);
void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index,
usb_attachfn attach);
void usb_unregister_port(USBBus *bus, USBPort *port);
diff --git a/monitor.c b/monitor.c
index ba45fb5..1050ca7 100644
--- a/monitor.c
+++ b/monitor.c
@@ -25,6 +25,7 @@
#include "hw/hw.h"
#include "hw/qdev.h"
#include "hw/usb.h"
+#include "hw/usb-ehci.h"
#include "hw/pcmcia.h"
#include "hw/pc.h"
#include "hw/pci.h"
diff --git a/usb-bsd.c b/usb-bsd.c
index 48567a3..b2c3777 100644
--- a/usb-bsd.c
+++ b/usb-bsd.c
@@ -361,7 +361,7 @@ USBDevice *usb_host_device_open(const char *devname)
goto fail;
}
- d = usb_create(NULL /* FIXME */, "usb-host");
+ d = usb_create_v1("usb-host");
dev = DO_UPCAST(USBHostDevice, dev, d);
if (dev_info.udi_speed == 1)
diff --git a/usb-linux.c b/usb-linux.c
index ccf7073..6d58e5a 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -187,6 +187,8 @@ typedef struct AsyncURB
USBPacket *packet;
USBHostDevice *hdev;
+
+ int more; /* packet required multiple URBs */
} AsyncURB;
static AsyncURB *async_alloc(void)
@@ -252,7 +254,7 @@ static void async_complete(void *opaque)
if (p) {
switch (aurb->urb.status) {
case 0:
- p->len = aurb->urb.actual_length;
+ p->len += aurb->urb.actual_length;
if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL) {
async_complete_ctrl(s, p);
}
@@ -268,7 +270,10 @@ static void async_complete(void *opaque)
break;
}
- usb_packet_complete(p);
+ if (!aurb->more) {
+ DPRINTF("invoking packet_complete. plen = %d\n", p->len);
+ usb_packet_complete(p);
+ }
}
async_free(aurb);
@@ -414,69 +419,89 @@ static void usb_host_handle_destroy(USBDevice *dev)
static int usb_linux_update_endp_table(USBHostDevice *s);
+/* devio.c limits single requests to 16k */
+#define MAX_USBFS_BUFFER_SIZE 16384
+
static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
{
struct usbdevfs_urb *urb;
AsyncURB *aurb;
- int ret;
+ int ret, len;
+ int rem = p->len;
+ uint8_t *pbuf = p->data;
- aurb = async_alloc();
- aurb->hdev = s;
- aurb->packet = p;
+ p->len = 0;
+ while (rem) {
+ aurb = async_alloc();
+ aurb->hdev = s;
+ aurb->packet = p;
- urb = &aurb->urb;
+ urb = &aurb->urb;
- if (p->pid == USB_TOKEN_IN) {
- urb->endpoint = p->devep | 0x80;
- } else {
- urb->endpoint = p->devep;
- }
+ if (p->pid == USB_TOKEN_IN) {
+ urb->endpoint = p->devep | 0x80;
+ } else {
+ urb->endpoint = p->devep;
+ }
- if (is_halted(s, p->devep)) {
- ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &urb->endpoint);
- if (ret < 0) {
- DPRINTF("husb: failed to clear halt. ep 0x%x errno %d\n",
- urb->endpoint, errno);
- return USB_RET_NAK;
+ if (is_halted(s, p->devep)) {
+ ret = ioctl(s->fd, USBDEVFS_CLEAR_HALT, &urb->endpoint);
+ if (ret < 0) {
+ DPRINTF("husb: failed to clear halt. ep 0x%x errno %d\n",
+ urb->endpoint, errno);
+ return USB_RET_NAK;
+ }
+ clear_halt(s, p->devep);
}
- clear_halt(s, p->devep);
- }
- urb->buffer = p->data;
- urb->buffer_length = p->len;
+ if (is_isoc(s, p->devep)) {
+ /* Setup ISOC transfer */
+ urb->type = USBDEVFS_URB_TYPE_ISO;
+ urb->flags = USBDEVFS_URB_ISO_ASAP;
+ urb->number_of_packets = 1;
+ urb->iso_frame_desc[0].length = p->len;
+ } else {
+ /* Setup bulk transfer */
+ urb->type = USBDEVFS_URB_TYPE_BULK;
+ }
- if (is_isoc(s, p->devep)) {
- /* Setup ISOC transfer */
- urb->type = USBDEVFS_URB_TYPE_ISO;
- urb->flags = USBDEVFS_URB_ISO_ASAP;
- urb->number_of_packets = 1;
- urb->iso_frame_desc[0].length = p->len;
- } else {
- /* Setup bulk transfer */
- urb->type = USBDEVFS_URB_TYPE_BULK;
- }
+ urb->usercontext = s;
- urb->usercontext = s;
+ /* USBFS limits max request size to 16k */
+ if (rem > MAX_USBFS_BUFFER_SIZE) {
+ len = MAX_USBFS_BUFFER_SIZE;
+ aurb->more = 1;
+ } else {
+ len = rem;
+ aurb->more = 0;
+ }
+ urb->buffer_length = len;
+ urb->buffer = pbuf;
- ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
+ ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
- DPRINTF("husb: data submit. ep 0x%x len %u aurb %p\n",
- urb->endpoint, p->len, aurb);
+ DPRINTF("husb: data submit. ep 0x%x len %u aurb %p\n",
+ urb->endpoint, len, aurb);
- if (ret < 0) {
- DPRINTF("husb: submit failed. errno %d\n", errno);
- async_free(aurb);
+ if (ret < 0) {
+ DPRINTF("husb: submit failed. errno %d\n", errno);
+ async_free(aurb);
- switch(errno) {
- case ETIMEDOUT:
- return USB_RET_NAK;
- case EPIPE:
- default:
- return USB_RET_STALL;
+ switch(errno) {
+ case ETIMEDOUT:
+ return USB_RET_NAK;
+ case EPIPE:
+ default:
+ return USB_RET_STALL;
+ }
}
+
+ usb_defer_packet(p, async_cancel, aurb);
+
+ pbuf += len;
+ rem -= len;
}
- usb_defer_packet(p, async_cancel, aurb);
return USB_RET_ASYNC;
}
@@ -919,7 +944,8 @@ static int usb_linux_update_endp_table(USBHostDevice *s)
}
static int usb_host_open(USBHostDevice *dev, int bus_num,
- int addr, int devpath, const char *prod_name)
+ int addr, int devpath, const char *prod_name,
+ int speed)
{
int fd = -1, ret;
struct usbdevfs_connectinfo ci;
@@ -983,17 +1009,17 @@ static int usb_host_open(USBHostDevice *dev, int bus_num,
goto fail;
}
- printf("husb: grabbed usb device %d.%d\n", bus_num, addr);
+ printf("husb: grabbed usb device %d.%d, %s speed\n",
+ bus_num, addr, speed == USB_SPEED_HIGH ? "high":"low/full");
ret = usb_linux_update_endp_table(dev);
if (ret) {
goto fail;
}
- if (ci.slow) {
- dev->dev.speed = USB_SPEED_LOW;
- } else {
- dev->dev.speed = USB_SPEED_HIGH;
+ dev->dev.speed = speed;
+ if ( (speed != USB_SPEED_HIGH) && (usb_bus_migrate_v1(&dev->dev) != 0) ) {
+ goto fail;
}
if (!prod_name || prod_name[0] == '\0') {
@@ -1088,7 +1114,8 @@ USBDevice *usb_host_device_open(const char *devname)
USBDevice *dev;
char *p;
- dev = usb_create(NULL /* FIXME */, "usb-host");
+ /* default host devices to ehci */
+ dev = usb_create_v2("usb-host");
if (strstr(devname, "auto:")) {
if (parse_filter(devname, &filter) < 0) {
@@ -1507,7 +1534,7 @@ static int usb_host_auto_scan(void *opaque, int bus_num, int addr, int devpath,
}
DPRINTF("husb: auto open: bus_num %d addr %d\n", bus_num, addr);
- usb_host_open(s, bus_num, addr, devpath, product_name);
+ usb_host_open(s, bus_num, addr, devpath, product_name, speed);
}
return 0;
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: USB Scanner
2011-01-03 22:59 ` David Ahern
@ 2011-01-03 23:35 ` Erik Rull
2011-01-03 23:50 ` David Ahern
0 siblings, 1 reply; 6+ messages in thread
From: Erik Rull @ 2011-01-03 23:35 UTC (permalink / raw)
To: David Ahern; +Cc: kvm
David Ahern wrote:
>
>
> The attached work for me with an Epson CX7800. I was able to print and
> scan with Vista; driver setup for XP was more of a pain - I only got the
> printer setup working.
>
> It's the EHCI code from
>
> git://git.kiszka.org/qemu.git ehci
>
> with http://www.mail-archive.com/qemu-devel@nongnu.org/msg38795.html and
> a couple of local additions. Applies cleanly to qemu-kvm.git as of today
> (b724fa4bb46b82c54b5f0a2cf9177194020433efO).
>
> EHCI is not ready for prime time, but works for use cases like this one.
>
> David
>
Hi David,
thanks for the patch.
Have you tested USB transfer rates using a USB Memory Stick or something
similar? If yes, which transfer rates have you reached?
Best regards,
Erik
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: USB Scanner
2011-01-03 23:35 ` Erik Rull
@ 2011-01-03 23:50 ` David Ahern
2011-01-04 0:21 ` David Ahern
0 siblings, 1 reply; 6+ messages in thread
From: David Ahern @ 2011-01-03 23:50 UTC (permalink / raw)
To: Erik Rull; +Cc: kvm
On 01/03/11 16:35, Erik Rull wrote:
> David Ahern wrote:
>>
>>
>> The attached work for me with an Epson CX7800. I was able to print and
>> scan with Vista; driver setup for XP was more of a pain - I only got the
>> printer setup working.
>>
>> It's the EHCI code from
>>
>> git://git.kiszka.org/qemu.git ehci
>>
>> with http://www.mail-archive.com/qemu-devel@nongnu.org/msg38795.html and
>> a couple of local additions. Applies cleanly to qemu-kvm.git as of today
>> (b724fa4bb46b82c54b5f0a2cf9177194020433efO).
>>
>> EHCI is not ready for prime time, but works for use cases like this one.
>>
>> David
>>
>
> Hi David,
>
> thanks for the patch.
> Have you tested USB transfer rates using a USB Memory Stick or something
> similar? If yes, which transfer rates have you reached?
USB keys (storage devices) were the primary target I used for testing.
In fact, with EHCI I can run a linux guest from a USB key just fine -
something I just verified again:
qemu-kvm -usbdevice host:${USBDEV}
where USBDEV is the vendor:product id from lsusb. Add other args to
install from a CD/DVD to the USB key. If your mileage varies first and
foremost make sure the VM is using kvm -- speed is horrible with qemu/tcg.
David
>
> Best regards,
>
> Erik
> --
> To unsubscribe from this list: send the line "unsubscribe kvm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: USB Scanner
2011-01-03 23:50 ` David Ahern
@ 2011-01-04 0:21 ` David Ahern
0 siblings, 0 replies; 6+ messages in thread
From: David Ahern @ 2011-01-04 0:21 UTC (permalink / raw)
To: David Ahern; +Cc: Erik Rull, kvm
On 01/03/11 16:50, David Ahern wrote:
>> thanks for the patch.
>> Have you tested USB transfer rates using a USB Memory Stick or something
>> similar? If yes, which transfer rates have you reached?
hmm.... more specifically, 8-11 Mbps depending on type of USB storage
device. Harddrives work better than flash.
As an example, on my Dell laptop with a T9550@2.66GHz the command:
time dd if=/dev/zero of=/tmp/junk oflag=dsync bs=1M count=256
shows the following rates:
Kingston, 8GB stick (enterprise quality versus consumer grade): 9.7 MB/sec
Seagate external drive (older, low end consumer model): 11.6 MB/sec
David
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2011-01-04 0:21 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-01-03 8:41 USB Scanner Matthias Henze (L)
2011-01-03 15:43 ` Erik Rull
2011-01-03 22:59 ` David Ahern
2011-01-03 23:35 ` Erik Rull
2011-01-03 23:50 ` David Ahern
2011-01-04 0:21 ` David Ahern
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox