qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Gerd Hoffmann <kraxel@redhat.com>
To: qemu-devel@nongnu.org
Cc: Gerd Hoffmann <kraxel@redhat.com>
Subject: [Qemu-devel] [PATCH 08/31] usb-ehci: multiqueue support
Date: Mon,  6 Jun 2011 14:38:59 +0200	[thread overview]
Message-ID: <1307363962-27223-9-git-send-email-kraxel@redhat.com> (raw)
In-Reply-To: <1307363962-27223-1-git-send-email-kraxel@redhat.com>

This patch adds support for keeping multiple queues going at the same
time.  One slow device will not affect other devices any more.

The patch adds code to manage EHCIQueue structs.  It also does a number
of changes to the state machine:

 * The state machine will never ever stop in EXECUTING any more.
   Instead it will continue with the next queue (aka HORIZONTALQH) when
   the usb device returns USB_RET_ASYNC.
 * The state machine will stop processing when it figures it walks in
   circles (easy to figure now that we have a EHCIQueue struct for each
   QH we've processed).  The bailout logic should not be needed any
   more.  For now it is still in, but will assert() in case it triggers.
 * The state machine will just skip queues with a async USBPacket in
   flight.
 * The state machine will resume processing as soon as the async
   USBPacket is finished.

The patch also takes care to flush the QH struct back to guest memory
when needed, so we don't get stale data when (re-)loading it from guest
memory in FETCHQH state.

It also makes the writeback code to not touch the first three dwords of
the QH struct as the EHCI must not write them.  This actually fixes a
bug where QH chaining changes (next ptr) by the linux ehci driver where
overwritten by the emulated EHCI.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 hw/usb-ehci.c |  171 ++++++++++++++++++++++++++++++++++++++++++++++-----------
 trace-events  |    5 +-
 2 files changed, 142 insertions(+), 34 deletions(-)

diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c
index 3656a35..5cbb675 100644
--- a/hw/usb-ehci.c
+++ b/hw/usb-ehci.c
@@ -345,6 +345,9 @@ enum async_state {
 
 struct EHCIQueue {
     EHCIState *ehci;
+    QTAILQ_ENTRY(EHCIQueue) next;
+    bool async_schedule;
+    uint32_t seen, ts;
 
     /* cached data from guest - needs to be flushed
      * when guest removes an entry (doorbell, handshake sequence)
@@ -400,7 +403,7 @@ struct EHCIState {
     int pstate;                        // Current state in periodic schedule
     USBPort ports[NB_PORTS];
     uint32_t usbsts_pending;
-    EHCIQueue queue;
+    QTAILQ_HEAD(, EHCIQueue) queues;
 
     uint32_t a_fetch_addr;   // which address to look at next
     uint32_t p_fetch_addr;   // which address to look at next
@@ -594,9 +597,9 @@ static int ehci_get_fetch_addr(EHCIState *s, int async)
     return async ? s->a_fetch_addr : s->p_fetch_addr;
 }
 
-static void ehci_trace_qh(EHCIState *s, target_phys_addr_t addr, EHCIqh *qh)
+static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh)
 {
-    trace_usb_ehci_qh(addr, qh->next,
+    trace_usb_ehci_qh(q, addr, qh->next,
                       qh->current_qtd, qh->next_qtd, qh->altnext_qtd,
                       get_field(qh->epchar, QH_EPCHAR_RL),
                       get_field(qh->epchar, QH_EPCHAR_MPLEN),
@@ -609,9 +612,9 @@ static void ehci_trace_qh(EHCIState *s, target_phys_addr_t addr, EHCIqh *qh)
                       (bool)(qh->epchar & QH_EPCHAR_I));
 }
 
-static void ehci_trace_qtd(EHCIState *s, target_phys_addr_t addr, EHCIqtd *qtd)
+static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd)
 {
-    trace_usb_ehci_qtd(addr, qtd->next, qtd->altnext,
+    trace_usb_ehci_qtd(q, addr, qtd->next, qtd->altnext,
                        get_field(qtd->token, QTD_TOKEN_TBYTES),
                        get_field(qtd->token, QTD_TOKEN_CPAGE),
                        get_field(qtd->token, QTD_TOKEN_CERR),
@@ -628,6 +631,69 @@ static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd)
     trace_usb_ehci_itd(addr, itd->next);
 }
 
+/* queue management */
+
+static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, int async)
+{
+    EHCIQueue *q;
+
+    q = qemu_mallocz(sizeof(*q));
+    q->ehci = ehci;
+    q->async_schedule = async;
+    QTAILQ_INSERT_HEAD(&ehci->queues, q, next);
+    trace_usb_ehci_queue_action(q, "alloc");
+    return q;
+}
+
+static void ehci_free_queue(EHCIQueue *q)
+{
+    trace_usb_ehci_queue_action(q, "free");
+    if (q->async == EHCI_ASYNC_INFLIGHT) {
+        usb_cancel_packet(&q->packet);
+    }
+    QTAILQ_REMOVE(&q->ehci->queues, q, next);
+    qemu_free(q);
+}
+
+static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr)
+{
+    EHCIQueue *q;
+
+    QTAILQ_FOREACH(q, &ehci->queues, next) {
+        if (addr == q->qhaddr) {
+            return q;
+        }
+    }
+    return NULL;
+}
+
+static void ehci_queues_rip_unused(EHCIState *ehci)
+{
+    EHCIQueue *q, *tmp;
+
+    QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) {
+        if (q->seen) {
+            q->seen = 0;
+            q->ts = ehci->last_run_usec;
+            continue;
+        }
+        if (ehci->last_run_usec < q->ts + 250000) {
+            /* allow 0.25 sec idle */
+            continue;
+        }
+        ehci_free_queue(q);
+    }
+}
+
+static void ehci_queues_rip_all(EHCIState *ehci)
+{
+    EHCIQueue *q, *tmp;
+
+    QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) {
+        ehci_free_queue(q);
+    }
+}
+
 /* Attach or detach a device on root hub */
 
 static void ehci_attach(USBPort *port)
@@ -697,6 +763,7 @@ static void ehci_reset(void *opaque)
             usb_attach(&s->ports[i], s->ports[i].dev);
         }
     }
+    ehci_queues_rip_all(s);
 }
 
 static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr)
@@ -1022,8 +1089,8 @@ static void ehci_async_complete_packet(USBDevice *dev, USBPacket *packet)
 {
     EHCIQueue *q = container_of(packet, EHCIQueue, packet);
 
-    DPRINTF("Async packet complete\n");
-    assert(q->async == EHIC_ASYNC_INFLIGHT);
+    trace_usb_ehci_queue_action(q, "wakeup");
+    assert(q->async == EHCI_ASYNC_INFLIGHT);
     q->async = EHCI_ASYNC_FINISHED;
     q->usb_status = packet->len;
 }
@@ -1032,10 +1099,7 @@ static void ehci_execute_complete(EHCIQueue *q)
 {
     int c_err, reload;
 
-    if (q->async == EHCI_ASYNC_INFLIGHT) {
-        DPRINTF("not done yet\n");
-        return;
-    }
+    assert(q->async != EHCI_ASYNC_INFLIGHT);
     q->async = EHCI_ASYNC_NONE;
 
     DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n",
@@ -1185,10 +1249,6 @@ static int ehci_execute(EHCIQueue *q)
         return USB_RET_PROCERR;
     }
 
-    if (ret == USB_RET_ASYNC) {
-        q->async = EHCI_ASYNC_INFLIGHT;
-    }
-
     return ret;
 }
 
@@ -1328,10 +1388,12 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
         ehci_set_usbsts(ehci, USBSTS_REC);
     }
 
+    ehci_queues_rip_unused(ehci);
+
     /*  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);
-        ehci_trace_qh(ehci, NLPTR_GET(entry), &qh);
+        ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
 
         if (qh.epchar & QH_EPCHAR_H) {
             if (async) {
@@ -1419,11 +1481,34 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
     int reload;
 
     entry = ehci_get_fetch_addr(ehci, async);
-    q = &ehci->queue; /* temporary */
+    q = ehci_find_queue_by_qh(ehci, entry);
+    if (NULL == q) {
+        q = ehci_alloc_queue(ehci, async);
+    }
     q->qhaddr = entry;
+    q->seen++;
+
+    if (q->seen > 1) {
+        /* we are going in circles -- stop processing */
+        ehci_set_state(ehci, async, EST_ACTIVE);
+        q = NULL;
+        goto out;
+    }
 
     get_dwords(NLPTR_GET(q->qhaddr), (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2);
-    ehci_trace_qh(ehci, NLPTR_GET(q->qhaddr), &q->qh);
+    ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh);
+
+    if (q->async == EHCI_ASYNC_INFLIGHT) {
+        /* I/O still in progress -- skip queue */
+        ehci_set_state(ehci, async, EST_HORIZONTALQH);
+        goto out;
+    }
+    if (q->async == EHCI_ASYNC_FINISHED) {
+        /* I/O finished -- continue processing queue */
+        trace_usb_ehci_queue_action(q, "resume");
+        ehci_set_state(ehci, async, EST_EXECUTING);
+        goto out;
+    }
 
     if (async && (q->qh.epchar & QH_EPCHAR_H)) {
 
@@ -1542,7 +1627,7 @@ static int ehci_state_fetchqtd(EHCIQueue *q, int async)
     int again = 0;
 
     get_dwords(NLPTR_GET(q->qtdaddr),(uint32_t *) &q->qtd, sizeof(EHCIqtd) >> 2);
-    ehci_trace_qtd(q->ehci, NLPTR_GET(q->qtdaddr), &q->qtd);
+    ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &q->qtd);
 
     if (q->qtd.token & QTD_TOKEN_ACTIVE) {
         ehci_set_state(q->ehci, async, EST_EXECUTE);
@@ -1570,6 +1655,23 @@ static int ehci_state_horizqh(EHCIQueue *q, int async)
     return again;
 }
 
+/*
+ *  Write the qh back to guest physical memory.  This step isn't
+ *  in the EHCI spec but we need to do it since we don't share
+ *  physical memory with our guest VM.
+ *
+ *  The first three bytes are read-only for the EHCI, so skip them
+ *  when writing back the qh.
+ */
+static void ehci_flush_qh(EHCIQueue *q)
+{
+    uint32_t *qh = (uint32_t *) &q->qh;
+    uint32_t dwords = sizeof(EHCIqh) >> 2;
+    uint32_t addr = NLPTR_GET(q->qhaddr);
+
+    put_dwords(addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
+}
+
 static int ehci_state_execute(EHCIQueue *q, int async)
 {
     int again = 0;
@@ -1614,12 +1716,18 @@ static int ehci_state_execute(EHCIQueue *q, int async)
         again = -1;
         goto out;
     }
-    ehci_set_state(q->ehci, async, EST_EXECUTING);
-
-    if (q->usb_status != USB_RET_ASYNC) {
+    if (q->usb_status == USB_RET_ASYNC) {
+        ehci_flush_qh(q);
+        trace_usb_ehci_queue_action(q, "suspend");
+        q->async = EHCI_ASYNC_INFLIGHT;
+        ehci_set_state(q->ehci, async, EST_HORIZONTALQH);
         again = 1;
+        goto out;
     }
 
+    ehci_set_state(q->ehci, async, EST_EXECUTING);
+    again = 1;
+
 out:
     return again;
 }
@@ -1660,13 +1768,6 @@ static int ehci_state_executing(EHCIQueue *q, int async)
         set_field(&q->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.
-     */
-    put_dwords(NLPTR_GET(q->qhaddr), (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2);
-
     /* 4.10.5 */
     if ((q->usb_status == USB_RET_NAK) || (q->qh.token & QTD_TOKEN_ACTIVE)) {
         ehci_set_state(q->ehci, async, EST_HORIZONTALQH);
@@ -1677,6 +1778,7 @@ static int ehci_state_executing(EHCIQueue *q, int async)
     again = 1;
 
 out:
+    ehci_flush_qh(q);
     return again;
 }
 
@@ -1686,7 +1788,7 @@ static int ehci_state_writeback(EHCIQueue *q, int async)
     int again = 0;
 
     /*  Write back the QTD from the QH area */
-    ehci_trace_qtd(q->ehci, NLPTR_GET(q->qtdaddr), (EHCIqtd*) &q->qh.next_qtd);
+    ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), (EHCIqtd*) &q->qh.next_qtd);
     put_dwords(NLPTR_GET(q->qtdaddr),(uint32_t *) &q->qh.next_qtd,
                 sizeof(EHCIqtd) >> 2);
 
@@ -1720,11 +1822,14 @@ static void ehci_advance_state(EHCIState *ehci,
              * something is wrong with the linked list. TO-DO: why is
              * this hack needed?
              */
+            assert(iter < MAX_ITERATIONS);
+#if 0
             if (iter > MAX_ITERATIONS) {
                 DPRINTF("\n*** advance_state: bailing on MAX ITERATIONS***\n");
                 ehci_set_state(ehci, async, EST_ACTIVE);
                 break;
             }
+#endif
         }
         switch(ehci_get_state(ehci, async)) {
         case EST_WAITLISTHEAD:
@@ -1762,7 +1867,7 @@ static void ehci_advance_state(EHCIState *ehci,
             break;
 
         case EST_EXECUTING:
-            q = &ehci->queue; /* temporary */
+            assert(q != NULL);
             again = ehci_state_executing(q, async);
             break;
 
@@ -1773,6 +1878,7 @@ static void ehci_advance_state(EHCIState *ehci,
         default:
             fprintf(stderr, "Bad state!\n");
             again = -1;
+            assert(0);
             break;
         }
 
@@ -1780,6 +1886,7 @@ static void ehci_advance_state(EHCIState *ehci,
             fprintf(stderr, "processing error - resetting ehci HC\n");
             ehci_reset(ehci);
             again = 0;
+            assert(0);
         }
     }
     while (again);
@@ -2070,7 +2177,7 @@ static int usb_ehci_initfn(PCIDevice *dev)
     }
 
     s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s);
-    s->queue.ehci = s;
+    QTAILQ_INIT(&s->queues);
 
     qemu_register_reset(ehci_reset, s);
 
diff --git a/trace-events b/trace-events
index 3426e14..51e2e7c 100644
--- a/trace-events
+++ b/trace-events
@@ -201,13 +201,14 @@ disable usb_ehci_mmio_writel(uint32_t addr, const char *str, uint32_t val) "wr m
 disable usb_ehci_mmio_change(uint32_t addr, const char *str, uint32_t new, uint32_t old) "ch mmio %04x [%s] = %x (old: %x)"
 disable usb_ehci_usbsts(const char *sts, int state) "usbsts %s %d"
 disable usb_ehci_state(const char *schedule, const char *state) "%s schedule %s"
-disable usb_ehci_qh(uint32_t addr, uint32_t next, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd, int rl, int mplen, int eps, int ep, int devaddr, int c, int h, int dtc, int i) "QH @ %08x: next %08x qtds %08x,%08x,%08x - rl %d, mplen %d, eps %d, ep %d, dev %d, c %d, h %d, dtc %d, i %d"
-disable usb_ehci_qtd(uint32_t addr, uint32_t next, uint32_t altnext, int tbytes, int cpage, int cerr, int pid, int ioc, int active, int halt, int babble, int xacterr) "QH @ %08x: next %08x altnext %08x - tbytes %d, cpage %d, cerr %d, pid %d, ioc %d, active %d, halt %d, babble %d, xacterr %d"
+disable usb_ehci_qh(void *q, uint32_t addr, uint32_t next, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd, int rl, int mplen, int eps, int ep, int devaddr, int c, int h, int dtc, int i) "q %p - QH @ %08x: next %08x qtds %08x,%08x,%08x - rl %d, mplen %d, eps %d, ep %d, dev %d, c %d, h %d, dtc %d, i %d"
+disable usb_ehci_qtd(void *q, uint32_t addr, uint32_t next, uint32_t altnext, int tbytes, int cpage, int cerr, int pid, int ioc, int active, int halt, int babble, int xacterr) "q %p - QTD @ %08x: next %08x altnext %08x - tbytes %d, cpage %d, cerr %d, pid %d, ioc %d, active %d, halt %d, babble %d, xacterr %d"
 disable usb_ehci_itd(uint32_t addr, uint32_t next) "ITD @ %08x: next %08x"
 disable usb_ehci_port_attach(uint32_t port, const char *device) "attach port #%d - %s"
 disable usb_ehci_port_detach(uint32_t port) "detach port #%d"
 disable usb_ehci_port_reset(uint32_t port, int enable) "reset port #%d - %d"
 disable usb_ehci_data(int rw, uint32_t cpage, uint32_t offset, uint32_t addr, uint32_t len, uint32_t bufpos) "write %d, cpage %d, offset 0x%03x, addr 0x%08x, len %d, bufpos %d"
+disable usb_ehci_queue_action(void *q, const char *action) "q %p: %s"
 
 # hw/usb-desc.c
 disable usb_desc_device(int addr, int len, int ret) "dev %d query device, len %d, ret %d"
-- 
1.7.1

  parent reply	other threads:[~2011-06-06 12:40 UTC|newest]

Thread overview: 38+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-06-06 12:38 [Qemu-devel] [PATCH 00/31] usb patch queue Gerd Hoffmann
2011-06-06 12:38 ` [Qemu-devel] [PATCH 01/31] usb-linux: catch ENODEV in more places Gerd Hoffmann
2011-06-06 12:38 ` [Qemu-devel] [PATCH 02/31] usb-ehci: trace mmio and usbsts Gerd Hoffmann
2011-06-06 12:38 ` [Qemu-devel] [PATCH 03/31] usb-ehci: trace state machine changes Gerd Hoffmann
2011-06-06 12:38 ` [Qemu-devel] [PATCH 04/31] usb-ehci: trace port state Gerd Hoffmann
2011-06-06 12:38 ` [Qemu-devel] [PATCH 05/31] usb-ehci: improve mmio tracing Gerd Hoffmann
2011-06-06 12:38 ` [Qemu-devel] [PATCH 06/31] usb-ehci: trace buffer copy Gerd Hoffmann
2011-06-06 12:38 ` [Qemu-devel] [PATCH 07/31] usb-ehci: add queue data struct Gerd Hoffmann
2011-06-06 12:38 ` Gerd Hoffmann [this message]
2011-06-06 14:50   ` [Qemu-devel] [PATCH 08/31] usb-ehci: multiqueue support David Ahern
2011-06-07  7:30     ` Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 09/31] usb-ehci: fix offset writeback in ehci_buffer_rw Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 10/31] usb-ehci: fix error handling Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 11/31] ehci: fix a number of unused-but-set-variable warnings (new with gcc-4.6) Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 12/31] usb: cancel async packets on unplug Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 13/31] usb-ehci: drop EXECUTING checks Gerd Hoffmann
2011-06-06 13:34   ` David Ahern
2011-06-06 13:57     ` David Ahern
2011-06-06 14:25       ` Gerd Hoffmann
2011-06-06 14:51         ` David Ahern
2011-06-06 12:39 ` [Qemu-devel] [PATCH 14/31] Fix USB mouse Set_Protocol behavior Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 15/31] The USB tablet should not claim boot protocol support Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 16/31] usb-ehci: itd handling fixes Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 17/31] usb-ehci: split trace calls to handle arg count limits Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 18/31] usb: documentation update Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 19/31] usb-linux: Get speed from sysfs rather then from the connectinfo ioctl Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 20/31] usb-linux: Teach about super speed Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 21/31] usb-linux: Don't do perror when errno is not set Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 22/31] usb-linux: Ensure devep != 0 Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 23/31] usb-linux: Don't try to open the same device twice Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 24/31] usb-linux: only cleanup in host_close when host_open was successful Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 25/31] usb: don't call usb_host_device_open from vl.c Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 26/31] usb-linux: Enlarge buffer for descriptors to 8192 bytes Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 27/31] usb-bus: Add knowledge of USB_SPEED_SUPER to usb_speed helper Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 28/31] usb-bus: Don't detach non attached devices on device exit Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 29/31] usb: Add defines for USB Serial Bus Release Number register Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 30/31] usb: Use defines for serial bus release number register for UHCI Gerd Hoffmann
2011-06-06 12:39 ` [Qemu-devel] [PATCH 31/31] usb: Use defines for serial bus release number register for EHCI Gerd Hoffmann

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1307363962-27223-9-git-send-email-kraxel@redhat.com \
    --to=kraxel@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).