summary refs log tree commit diff stats
path: root/hw/usb
diff options
context:
space:
mode:
Diffstat (limited to 'hw/usb')
-rw-r--r--hw/usb/bus.c13
-rw-r--r--hw/usb/hcd-ehci.c69
-rw-r--r--hw/usb/hcd-uhci.c4
-rw-r--r--hw/usb/host-linux.c79
4 files changed, 139 insertions, 26 deletions
diff --git a/hw/usb/bus.c b/hw/usb/bus.c
index f87cc5f443..b649360dd3 100644
--- a/hw/usb/bus.c
+++ b/hw/usb/bus.c
@@ -37,10 +37,23 @@ static const TypeInfo usb_bus_info = {
 static int next_usb_bus = 0;
 static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses);
 
+static int usb_device_post_load(void *opaque, int version_id)
+{
+    USBDevice *dev = opaque;
+
+    if (dev->state == USB_STATE_NOTATTACHED) {
+        dev->attached = 0;
+    } else {
+        dev->attached = 1;
+    }
+    return 0;
+}
+
 const VMStateDescription vmstate_usb_device = {
     .name = "USBDevice",
     .version_id = 1,
     .minimum_version_id = 1,
+    .post_load = usb_device_post_load,
     .fields = (VMStateField []) {
         VMSTATE_UINT8(addr, USBDevice),
         VMSTATE_INT32(state, USBDevice),
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 5298204d9d..6d2d54940d 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -414,16 +414,17 @@ struct EHCIState {
      */
     QEMUTimer *frame_timer;
     QEMUBH *async_bh;
-    int astate;                        // Current state in asynchronous schedule
-    int pstate;                        // Current state in periodic schedule
+    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;
     EHCIQueueHead aqueues;
     EHCIQueueHead pqueues;
 
-    uint32_t a_fetch_addr;   // which address to look at next
-    uint32_t p_fetch_addr;   // which address to look at next
+    /* which address to look at next */
+    uint32_t a_fetch_addr;
+    uint32_t p_fetch_addr;
 
     USBPacket ipacket;
     QEMUSGList isgl;
@@ -568,6 +569,7 @@ static inline void ehci_set_interrupt(EHCIState *s, int intr)
         level = 1;
     }
 
+    trace_usb_ehci_interrupt(level, s->usbsts, s->usbintr);
     qemu_set_irq(s->irq, level);
 }
 
@@ -821,8 +823,9 @@ static void ehci_attach(USBPort *port)
 {
     EHCIState *s = port->opaque;
     uint32_t *portsc = &s->portsc[port->index];
+    const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
 
-    trace_usb_ehci_port_attach(port->index, port->dev->product_desc);
+    trace_usb_ehci_port_attach(port->index, owner, port->dev->product_desc);
 
     if (*portsc & PORTSC_POWNER) {
         USBPort *companion = s->companion_ports[port->index];
@@ -841,8 +844,9 @@ static void ehci_detach(USBPort *port)
 {
     EHCIState *s = port->opaque;
     uint32_t *portsc = &s->portsc[port->index];
+    const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
 
-    trace_usb_ehci_port_detach(port->index);
+    trace_usb_ehci_port_detach(port->index, owner);
 
     if (*portsc & PORTSC_POWNER) {
         USBPort *companion = s->companion_ports[port->index];
@@ -2390,9 +2394,58 @@ static USBBusOps ehci_bus_ops = {
     .register_companion = ehci_register_companion,
 };
 
+static int usb_ehci_post_load(void *opaque, int version_id)
+{
+    EHCIState *s = opaque;
+    int i;
+
+    for (i = 0; i < NB_PORTS; i++) {
+        USBPort *companion = s->companion_ports[i];
+        if (companion == NULL) {
+            continue;
+        }
+        if (s->portsc[i] & PORTSC_POWNER) {
+            companion->dev = s->ports[i].dev;
+        } else {
+            companion->dev = NULL;
+        }
+    }
+
+    return 0;
+}
+
 static const VMStateDescription vmstate_ehci = {
-    .name = "ehci",
-    .unmigratable = 1,
+    .name        = "ehci",
+    .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),
+        VMSTATE_UINT32(usbintr, EHCIState),
+        VMSTATE_UINT32(frindex, EHCIState),
+        VMSTATE_UINT32(ctrldssegment, EHCIState),
+        VMSTATE_UINT32(periodiclistbase, EHCIState),
+        VMSTATE_UINT32(asynclistaddr, EHCIState),
+        VMSTATE_UINT32(configflag, EHCIState),
+        VMSTATE_UINT32(portsc[0], EHCIState),
+        VMSTATE_UINT32(portsc[1], EHCIState),
+        VMSTATE_UINT32(portsc[2], EHCIState),
+        VMSTATE_UINT32(portsc[3], EHCIState),
+        VMSTATE_UINT32(portsc[4], EHCIState),
+        VMSTATE_UINT32(portsc[5], EHCIState),
+        /* frame timer */
+        VMSTATE_TIMER(frame_timer, EHCIState),
+        VMSTATE_UINT64(last_run_ns, EHCIState),
+        VMSTATE_UINT32(async_stepdown, EHCIState),
+        /* schedule state */
+        VMSTATE_UINT32(astate, EHCIState),
+        VMSTATE_UINT32(pstate, EHCIState),
+        VMSTATE_UINT32(a_fetch_addr, EHCIState),
+        VMSTATE_UINT32(p_fetch_addr, EHCIState),
+        VMSTATE_END_OF_LIST()
+    }
 };
 
 static Property ehci_properties[] = {
diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c
index 9871e24f50..2ebce04b24 100644
--- a/hw/usb/hcd-uhci.c
+++ b/hw/usb/hcd-uhci.c
@@ -292,10 +292,10 @@ static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev)
 
 static void uhci_async_cancel_all(UHCIState *s)
 {
-    UHCIQueue *queue;
+    UHCIQueue *queue, *nq;
     UHCIAsync *curr, *n;
 
-    QTAILQ_FOREACH(queue, &s->queues, next) {
+    QTAILQ_FOREACH_SAFE(queue, &s->queues, next, nq) {
         QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) {
             uhci_async_unlink(curr);
             uhci_async_cancel(curr);
diff --git a/hw/usb/host-linux.c b/hw/usb/host-linux.c
index a95b0eda55..5479fb5987 100644
--- a/hw/usb/host-linux.c
+++ b/hw/usb/host-linux.c
@@ -111,6 +111,7 @@ typedef struct USBHostDevice {
     uint32_t  iso_urb_count;
     uint32_t  options;
     Notifier  exit;
+    QEMUBH    *bh;
 
     struct endp_data ep_in[USB_MAX_ENDPOINTS];
     struct endp_data ep_out[USB_MAX_ENDPOINTS];
@@ -1421,6 +1422,43 @@ static void usb_host_exit_notifier(struct Notifier *n, void *data)
     }
 }
 
+/*
+ * This is *NOT* about restoring state.  We have absolutely no idea
+ * what state the host device is in at the moment and whenever it is
+ * still present in the first place.  Attemping to contine where we
+ * left off is impossible.
+ *
+ * What we are going to to to here is emulate a surprise removal of
+ * the usb device passed through, then kick host scan so the device
+ * will get re-attached (and re-initialized by the guest) in case it
+ * is still present.
+ *
+ * As the device removal will change the state of other devices (usb
+ * host controller, most likely interrupt controller too) we have to
+ * wait with it until *all* vmstate is loaded.  Thus post_load just
+ * kicks a bottom half which then does the actual work.
+ */
+static void usb_host_post_load_bh(void *opaque)
+{
+    USBHostDevice *dev = opaque;
+
+    if (dev->fd != -1) {
+        usb_host_close(dev);
+    }
+    if (dev->dev.attached) {
+        usb_device_detach(&dev->dev);
+    }
+    usb_host_auto_check(NULL);
+}
+
+static int usb_host_post_load(void *opaque, int version_id)
+{
+    USBHostDevice *dev = opaque;
+
+    qemu_bh_schedule(dev->bh);
+    return 0;
+}
+
 static int usb_host_initfn(USBDevice *dev)
 {
     USBHostDevice *s = DO_UPCAST(USBHostDevice, dev, dev);
@@ -1432,6 +1470,7 @@ static int usb_host_initfn(USBDevice *dev)
     QTAILQ_INSERT_TAIL(&hostdevs, s, next);
     s->exit.notify = usb_host_exit_notifier;
     qemu_add_exit_notifier(&s->exit);
+    s->bh = qemu_bh_new(usb_host_post_load_bh, s);
     usb_host_auto_check(NULL);
 
     if (s->match.bus_num != 0 && s->match.port != NULL) {
@@ -1443,7 +1482,13 @@ static int usb_host_initfn(USBDevice *dev)
 
 static const VMStateDescription vmstate_usb_host = {
     .name = "usb-host",
-    .unmigratable = 1,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = usb_host_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_USB_DEVICE(dev, USBHostDevice),
+        VMSTATE_END_OF_LIST()
+    }
 };
 
 static Property usb_host_dev_properties[] = {
@@ -1737,25 +1782,27 @@ static void usb_host_auto_check(void *unused)
     struct USBHostDevice *s;
     int unconnected = 0;
 
-    usb_host_scan(NULL, usb_host_auto_scan);
+    if (runstate_is_running()) {
+        usb_host_scan(NULL, usb_host_auto_scan);
 
-    QTAILQ_FOREACH(s, &hostdevs, next) {
-        if (s->fd == -1) {
-            unconnected++;
-        }
-        if (s->seen == 0) {
-            s->errcount = 0;
+        QTAILQ_FOREACH(s, &hostdevs, next) {
+            if (s->fd == -1) {
+                unconnected++;
+            }
+            if (s->seen == 0) {
+                s->errcount = 0;
+            }
+            s->seen = 0;
         }
-        s->seen = 0;
-    }
 
-    if (unconnected == 0) {
-        /* nothing to watch */
-        if (usb_auto_timer) {
-            qemu_del_timer(usb_auto_timer);
-            trace_usb_host_auto_scan_disabled();
+        if (unconnected == 0) {
+            /* nothing to watch */
+            if (usb_auto_timer) {
+                qemu_del_timer(usb_auto_timer);
+                trace_usb_host_auto_scan_disabled();
+            }
+            return;
         }
-        return;
     }
 
     if (!usb_auto_timer) {