From: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
To: qemu-devel@nongnu.org
Cc: peter.maydell@linaro.org, peter.crosthwaite@xilinx.com,
mark.burton@greensocs.com, real@ispras.ru, batuzovk@ispras.ru,
pavel.dovgaluk@ispras.ru, pbonzini@redhat.com,
fred.konrad@greensocs.com
Subject: [Qemu-devel] [RFC PATCH v2 45/49] replay: USB passthrough
Date: Thu, 17 Jul 2014 15:06:11 +0400 [thread overview]
Message-ID: <20140717110611.8352.90855.stgit@PASHA-ISP> (raw)
In-Reply-To: <20140717110153.8352.80175.stgit@PASHA-ISP>
It writes all external data, returned by libusb,
to the log. This data is read in replay mode instead of calling
libusb functions.
Command line option for connecting USB device to simulator should be
specified in both record and replay modes. In replay mode only log file
is read and USB device could be not connected to the host machine.
Signed-off-by: Pavel Dovgalyuk <pavel.dovgaluk@ispras.ru>
---
hw/usb/host-libusb.c | 525 ++++++++++++++++++++++++++++++----------------
include/hw/host-libusb.h | 105 +++++++++
replay/Makefile.objs | 1
replay/replay-events.c | 49 ++++
replay/replay-internal.h | 21 ++
replay/replay-usb.c | 188 ++++++++++++++++
replay/replay.c | 29 +++
replay/replay.h | 30 +++
8 files changed, 765 insertions(+), 183 deletions(-)
create mode 100755 include/hw/host-libusb.h
create mode 100755 replay/replay-usb.c
diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index c189147..5283c64 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -40,98 +40,15 @@
#include "monitor/monitor.h"
#include "sysemu/sysemu.h"
#include "trace.h"
+#include "qemu/log.h"
+#include "replay/replay.h"
#include "hw/usb.h"
+#include "hw/host-libusb.h"
/* ------------------------------------------------------------------------ */
-#define TYPE_USB_HOST_DEVICE "usb-host"
-#define USB_HOST_DEVICE(obj) \
- OBJECT_CHECK(USBHostDevice, (obj), TYPE_USB_HOST_DEVICE)
-
-typedef struct USBHostDevice USBHostDevice;
-typedef struct USBHostRequest USBHostRequest;
-typedef struct USBHostIsoXfer USBHostIsoXfer;
-typedef struct USBHostIsoRing USBHostIsoRing;
-
-struct USBAutoFilter {
- uint32_t bus_num;
- uint32_t addr;
- char *port;
- uint32_t vendor_id;
- uint32_t product_id;
-};
-
-enum USBHostDeviceOptions {
- USB_HOST_OPT_PIPELINE,
-};
-
-struct USBHostDevice {
- USBDevice parent_obj;
-
- /* properties */
- struct USBAutoFilter match;
- int32_t bootindex;
- uint32_t iso_urb_count;
- uint32_t iso_urb_frames;
- uint32_t options;
- uint32_t loglevel;
-
- /* state */
- QTAILQ_ENTRY(USBHostDevice) next;
- int seen, errcount;
- int bus_num;
- int addr;
- char port[16];
-
- libusb_device *dev;
- libusb_device_handle *dh;
- struct libusb_device_descriptor ddesc;
-
- struct {
- bool detached;
- bool claimed;
- } ifs[USB_MAX_INTERFACES];
-
- /* callbacks & friends */
- QEMUBH *bh_nodev;
- QEMUBH *bh_postld;
- Notifier exit;
-
- /* request queues */
- QTAILQ_HEAD(, USBHostRequest) requests;
- QTAILQ_HEAD(, USBHostIsoRing) isorings;
-};
-
-struct USBHostRequest {
- USBHostDevice *host;
- USBPacket *p;
- bool in;
- struct libusb_transfer *xfer;
- unsigned char *buffer;
- unsigned char *cbuf;
- unsigned int clen;
bool usb3ep0quirk;
- QTAILQ_ENTRY(USBHostRequest) next;
-};
-
-struct USBHostIsoXfer {
- USBHostIsoRing *ring;
- struct libusb_transfer *xfer;
- bool copy_complete;
- unsigned int packet;
- QTAILQ_ENTRY(USBHostIsoXfer) next;
-};
-
-struct USBHostIsoRing {
- USBHostDevice *host;
- USBEndpoint *ep;
- QTAILQ_HEAD(, USBHostIsoXfer) unused;
- QTAILQ_HEAD(, USBHostIsoXfer) inflight;
- QTAILQ_HEAD(, USBHostIsoXfer) copy;
- QTAILQ_ENTRY(USBHostIsoRing) next;
-};
-
static QTAILQ_HEAD(, USBHostDevice) hostdevs =
QTAILQ_HEAD_INITIALIZER(hostdevs);
@@ -143,6 +60,24 @@ static void usb_host_attach_kernel(USBHostDevice *s);
/* ------------------------------------------------------------------------ */
+typedef struct USBHostTimer {
+ QEMUTimer *timer;
+} USBHostTimer;
+
+USBHostTimer usb_auto_timer;
+static int submitted_xfers = 0;
+
+static const VMStateDescription vmstate_usb_host_timer = {
+ .name = "usb-host-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER(timer, USBHostTimer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
#define CONTROL_TIMEOUT 10000 /* 10 sec */
#define BULK_TIMEOUT 0 /* unlimited */
#define INTR_TIMEOUT 0 /* unlimited */
@@ -214,6 +149,11 @@ static void usb_host_del_fd(int fd, void *user_data)
qemu_set_fd_handler(fd, NULL, NULL, NULL);
}
+bool usb_host_has_xfers(void)
+{
+ return submitted_xfers != 0;
+}
+
static int usb_host_init(void)
{
const struct libusb_pollfd **poll;
@@ -226,18 +166,34 @@ static int usb_host_init(void)
if (rc != 0) {
return -1;
}
- libusb_set_debug(ctx, loglevel);
-
- libusb_set_pollfd_notifiers(ctx, usb_host_add_fd,
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_set_debug(ctx, loglevel);
+ libusb_set_pollfd_notifiers(ctx, usb_host_add_fd,
usb_host_del_fd,
ctx);
- poll = libusb_get_pollfds(ctx);
- if (poll) {
- for (i = 0; poll[i] != NULL; i++) {
- usb_host_add_fd(poll[i]->fd, poll[i]->events, ctx);
+
+ poll = libusb_get_pollfds(ctx);
+ if (poll) {
+ for (i = 0; poll[i] != NULL; i++) {
+ usb_host_add_fd(poll[i]->fd, poll[i]->events, ctx);
+ }
+ }
+ free(poll);
+ }
+
+ /* replay: changed timer for working with VM clock instead of RT */
+ if (!usb_auto_timer.timer) {
+ usb_auto_timer.timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, usb_host_auto_check, NULL);
+ if (!usb_auto_timer.timer) {
+ return -1;
+ }
+ trace_usb_host_auto_scan_enabled();
+
+ if (replay_mode != REPLAY_NONE) {
+ vmstate_register(NULL, 0, &vmstate_usb_host_timer, &usb_auto_timer);
}
}
- free(poll);
+
return 0;
}
@@ -252,6 +208,7 @@ static int usb_host_get_port(libusb_device *dev, char *port, size_t len)
#else
rc = libusb_get_port_path(ctx, dev, path, 7);
#endif
+
if (rc < 0) {
return 0;
}
@@ -307,6 +264,7 @@ static USBHostRequest *usb_host_req_alloc(USBHostDevice *s, USBPacket *p,
r->host = s;
r->p = p;
r->in = in;
+ // allocate xfer's in REPLAY_PLAY too
r->xfer = libusb_alloc_transfer(0);
if (bufsize) {
r->buffer = g_malloc(bufsize);
@@ -320,6 +278,7 @@ static void usb_host_req_free(USBHostRequest *r)
if (r->host) {
QTAILQ_REMOVE(&r->host->requests, r, next);
}
+ // free xfer's in REPLAY_PLAY too
libusb_free_transfer(r->xfer);
g_free(r->buffer);
g_free(r);
@@ -337,12 +296,13 @@ static USBHostRequest *usb_host_req_find(USBHostDevice *s, USBPacket *p)
return NULL;
}
-static void usb_host_req_complete_ctrl(struct libusb_transfer *xfer)
+void usb_host_req_complete_ctrl(struct libusb_transfer *xfer)
{
USBHostRequest *r = xfer->user_data;
USBHostDevice *s = r->host;
bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE);
+ --submitted_xfers;
if (r->p == NULL) {
goto out; /* request was canceled */
}
@@ -354,7 +314,7 @@ static void usb_host_req_complete_ctrl(struct libusb_transfer *xfer)
/* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices
* to work redirected to a not superspeed capable hcd */
- if (r->usb3ep0quirk && xfer->actual_length >= 18 &&
+ if (usb3ep0quirk && xfer->actual_length >= 18 &&
r->cbuf[7] == 9) {
r->cbuf[7] = 64;
}
@@ -370,12 +330,13 @@ out:
}
}
-static void usb_host_req_complete_data(struct libusb_transfer *xfer)
+void usb_host_req_complete_data(struct libusb_transfer *xfer)
{
USBHostRequest *r = xfer->user_data;
USBHostDevice *s = r->host;
bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE);
+ --submitted_xfers;
if (r->p == NULL) {
goto out; /* request was canceled */
}
@@ -419,17 +380,17 @@ static void usb_host_req_abort(USBHostRequest *r)
QTAILQ_REMOVE(&r->host->requests, r, next);
r->host = NULL;
- if (inflight) {
+ if (inflight && replay_mode != REPLAY_PLAY) {
libusb_cancel_transfer(r->xfer);
}
}
/* ------------------------------------------------------------------------ */
-static void usb_host_req_complete_iso(struct libusb_transfer *transfer)
+void usb_host_req_complete_iso(struct libusb_transfer *transfer)
{
USBHostIsoXfer *xfer = transfer->user_data;
-
+ --submitted_xfers;
if (!xfer) {
/* USBHostIsoXfer released while inflight */
g_free(transfer->buffer);
@@ -467,6 +428,7 @@ static USBHostIsoRing *usb_host_iso_alloc(USBHostDevice *s, USBEndpoint *ep)
for (i = 0; i < s->iso_urb_count; i++) {
xfer = g_new0(USBHostIsoXfer, 1);
xfer->ring = ring;
+ // OK for replay too
xfer->xfer = libusb_alloc_transfer(packets);
xfer->xfer->dev_handle = s->dh;
xfer->xfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS;
@@ -475,7 +437,7 @@ static USBHostIsoRing *usb_host_iso_alloc(USBHostDevice *s, USBEndpoint *ep)
if (ring->ep->pid == USB_TOKEN_IN) {
xfer->xfer->endpoint |= USB_DIR_IN;
}
- xfer->xfer->callback = usb_host_req_complete_iso;
+ xfer->xfer->callback = replay_mode == REPLAY_NONE ? usb_host_req_complete_iso : replay_req_complete_iso;
xfer->xfer->user_data = xfer;
xfer->xfer->num_iso_packets = packets;
@@ -514,6 +476,7 @@ static void usb_host_iso_free_xfer(USBHostIsoXfer *xfer, bool inflight)
xfer->xfer->user_data = NULL;
} else {
g_free(xfer->xfer->buffer);
+ // REPLAY_PLAY also allocates xfer
libusb_free_transfer(xfer->xfer);
}
g_free(xfer);
@@ -549,12 +512,19 @@ static void usb_host_iso_free_all(USBHostDevice *s)
}
}
+
+unsigned char *usb_host_get_iso_packet_buffer(USBHostIsoXfer *xfer, int packet)
+{
+ // replay OK
+ return libusb_get_iso_packet_buffer_simple(xfer->xfer, packet);
+}
+
static bool usb_host_iso_data_copy(USBHostIsoXfer *xfer, USBPacket *p)
{
unsigned int psize;
unsigned char *buf;
- buf = libusb_get_iso_packet_buffer_simple(xfer->xfer, xfer->packet);
+ buf = usb_host_get_iso_packet_buffer(xfer, xfer->packet);
if (p->pid == USB_TOKEN_OUT) {
psize = p->iov.size;
if (psize > xfer->ring->ep->max_packet_size) {
@@ -600,7 +570,7 @@ static void usb_host_iso_data_in(USBHostDevice *s, USBPacket *p)
while ((xfer = QTAILQ_FIRST(&ring->unused)) != NULL) {
QTAILQ_REMOVE(&ring->unused, xfer, next);
usb_host_iso_reset_xfer(xfer);
- rc = libusb_submit_transfer(xfer->xfer);
+ REPLAY_DATA_INT(rc, libusb_submit_transfer(xfer->xfer));
if (rc != 0) {
usb_host_libusb_error("libusb_submit_transfer [iso]", rc);
QTAILQ_INSERT_TAIL(&ring->unused, xfer, next);
@@ -608,6 +578,9 @@ static void usb_host_iso_data_in(USBHostDevice *s, USBPacket *p)
disconnect = true;
}
break;
+ } else {
+ replay_req_register_iso(xfer->xfer);
+ ++submitted_xfers;
}
if (QTAILQ_EMPTY(&ring->inflight)) {
trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr);
@@ -662,7 +635,7 @@ static void usb_host_iso_data_out(USBHostDevice *s, USBPacket *p)
while ((xfer = QTAILQ_FIRST(&ring->copy)) != NULL &&
xfer->copy_complete) {
QTAILQ_REMOVE(&ring->copy, xfer, next);
- rc = libusb_submit_transfer(xfer->xfer);
+ REPLAY_DATA_INT(rc, libusb_submit_transfer(xfer->xfer));
if (rc != 0) {
usb_host_libusb_error("libusb_submit_transfer [iso]", rc);
QTAILQ_INSERT_TAIL(&ring->unused, xfer, next);
@@ -670,6 +643,9 @@ static void usb_host_iso_data_out(USBHostDevice *s, USBPacket *p)
disconnect = true;
}
break;
+ } else {
+ replay_req_register_iso(xfer->xfer);
+ ++submitted_xfers;
}
if (QTAILQ_EMPTY(&ring->inflight)) {
trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr);
@@ -697,6 +673,11 @@ static void usb_host_speed_compat(USBHostDevice *s)
bool compat_full = true;
uint8_t type;
int rc, c, i, a, e;
+ bool ok = false;
+ if (replay_mode == REPLAY_PLAY) {
+ REPLAY_DATA_VAR(ok);
+ return;
+ }
for (c = 0;; c++) {
rc = libusb_get_config_descriptor(s->dev, c, &conf);
@@ -762,9 +743,12 @@ static void usb_host_ep_update(USBHostDevice *s)
[USB_ENDPOINT_XFER_INT] = "int",
};
USBDevice *udev = USB_DEVICE(s);
- struct libusb_config_descriptor *conf;
- const struct libusb_interface_descriptor *intf;
- const struct libusb_endpoint_descriptor *endp;
+ struct libusb_config_descriptor dummy_conf;
+ struct libusb_interface_descriptor dummy_intf;
+ struct libusb_endpoint_descriptor dummy_endp;
+ struct libusb_config_descriptor *conf = &dummy_conf;
+ struct libusb_interface_descriptor *intf;
+ struct libusb_endpoint_descriptor *endp;
#ifdef HAVE_STREAMS
struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp;
#endif
@@ -773,21 +757,36 @@ static void usb_host_ep_update(USBHostDevice *s)
int rc, i, e;
usb_ep_reset(udev);
- rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ REPLAY_DATA_INT(rc, libusb_get_active_config_descriptor(s->dev, &conf));
if (rc != 0) {
return;
}
trace_usb_host_parse_config(s->bus_num, s->addr,
conf->bConfigurationValue, true);
+ REPLAY_DATA_VAR(conf->bNumInterfaces);
for (i = 0; i < conf->bNumInterfaces; i++) {
- assert(udev->altsetting[i] < conf->interface[i].num_altsetting);
- intf = &conf->interface[i].altsetting[udev->altsetting[i]];
+ if (replay_mode != REPLAY_PLAY) {
+ assert(udev->altsetting[i] < conf->interface[i].num_altsetting);
+ intf = (struct libusb_interface_descriptor *)&conf->interface[i].altsetting[udev->altsetting[i]];
+ } else {
+ intf = &dummy_intf;
+ }
+ REPLAY_DATA_VAR(intf->bInterfaceNumber);
+ REPLAY_DATA_VAR(intf->bAlternateSetting);
+ REPLAY_DATA_VAR(intf->bNumEndpoints);
trace_usb_host_parse_interface(s->bus_num, s->addr,
intf->bInterfaceNumber,
intf->bAlternateSetting, true);
for (e = 0; e < intf->bNumEndpoints; e++) {
- endp = &intf->endpoint[e];
+ if (replay_mode != REPLAY_PLAY) {
+ endp = (struct libusb_endpoint_descriptor *)&intf->endpoint[e];
+ } else {
+ endp = &dummy_endp;
+ }
+ REPLAY_DATA_VAR(endp->bEndpointAddress);
+ REPLAY_DATA_VAR(endp->bmAttributes);
+ REPLAY_DATA_VAR(endp->wMaxPacketSize);
devep = endp->bEndpointAddress;
pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
@@ -825,45 +824,76 @@ static void usb_host_ep_update(USBHostDevice *s)
}
}
- libusb_free_config_descriptor(conf);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_free_config_descriptor(conf);
+ }
}
static int usb_host_open(USBHostDevice *s, libusb_device *dev)
{
USBDevice *udev = USB_DEVICE(s);
- int bus_num = libusb_get_bus_number(dev);
- int addr = libusb_get_device_address(dev);
+ int bus_num;
+ REPLAY_DATA_INT(bus_num, libusb_get_bus_number(dev));
+ int addr;
+ REPLAY_DATA_INT(addr, libusb_get_device_address(dev));
int rc;
+ int speed;
trace_usb_host_open_started(bus_num, addr);
- if (s->dh != NULL) {
+ if (s->is_open) {
goto fail;
}
- rc = libusb_open(dev, &s->dh);
+ REPLAY_DATA_INT(rc, libusb_open(dev, &s->dh));
if (rc != 0) {
goto fail;
}
+ if (replay_mode == REPLAY_PLAY) {
+ // invalid non-NULL pointer for checking conditions
+ s->dh = (void*)0xbad;
+ }
+ s->is_open = true;
+ usb_host_detach_kernel(s);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_get_device_descriptor(dev, &s->ddesc);
+ }
+ // TODO: all fields are read and written as int
+ REPLAY_DATA_VAR(s->ddesc.bLength);
+ REPLAY_DATA_VAR(s->ddesc.bDescriptorType);
+ REPLAY_DATA_VAR(s->ddesc.bcdUSB);
+ REPLAY_DATA_VAR(s->ddesc.bDeviceClass);
+ REPLAY_DATA_VAR(s->ddesc.bDeviceSubClass);
+ REPLAY_DATA_VAR(s->ddesc.bDeviceProtocol);
+ REPLAY_DATA_VAR(s->ddesc.bMaxPacketSize0);
+ REPLAY_DATA_VAR(s->ddesc.idVendor);
+ REPLAY_DATA_VAR(s->ddesc.idProduct);
+ REPLAY_DATA_VAR(s->ddesc.bcdDevice);
+ REPLAY_DATA_VAR(s->ddesc.iManufacturer);
+ REPLAY_DATA_VAR(s->ddesc.iProduct);
+ REPLAY_DATA_VAR(s->ddesc.iSerialNumber);
+ REPLAY_DATA_VAR(s->ddesc.bNumConfigurations);
s->dev = dev;
s->bus_num = bus_num;
s->addr = addr;
-
- usb_host_detach_kernel(s);
-
- libusb_get_device_descriptor(dev, &s->ddesc);
- usb_host_get_port(s->dev, s->port, sizeof(s->port));
+ if (replay_mode != REPLAY_PLAY) {
+ usb_host_get_port(s->dev, s->port, sizeof(s->port));
+ }
+ replay_data_buffer((unsigned char *)s->port, sizeof(s->port));
usb_ep_init(udev);
usb_host_ep_update(s);
- udev->speed = speed_map[libusb_get_device_speed(dev)];
+ udev->speed = speed_map[REPLAY_DATA_INT(speed, libusb_get_device_speed(dev))];
usb_host_speed_compat(s);
if (s->ddesc.iProduct) {
- libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct,
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct,
(unsigned char *)udev->product_desc,
sizeof(udev->product_desc));
+ }
+ replay_data_buffer((unsigned char*)udev->product_desc, sizeof(udev->product_desc));
} else {
snprintf(udev->product_desc, sizeof(udev->product_desc),
"host:%d.%d", bus_num, addr);
@@ -879,10 +909,13 @@ static int usb_host_open(USBHostDevice *s, libusb_device *dev)
fail:
trace_usb_host_open_failure(bus_num, addr);
- if (s->dh != NULL) {
- libusb_close(s->dh);
+ if (s->is_open) {
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_close(s->dh);
+ }
s->dh = NULL;
s->dev = NULL;
+ s->is_open = false;
}
return -1;
}
@@ -896,11 +929,20 @@ static void usb_host_abort_xfers(USBHostDevice *s)
}
}
+static void usb_host_free_xfers(USBHostDevice *s)
+{
+ USBHostRequest *r;
+
+ while ((r = QTAILQ_FIRST(&s->requests)) != NULL) {
+ usb_host_req_free(r);
+ }
+}
+
static int usb_host_close(USBHostDevice *s)
{
USBDevice *udev = USB_DEVICE(s);
- if (s->dh == NULL) {
+ if (!s->is_open) {
return -1;
}
@@ -914,11 +956,20 @@ static int usb_host_close(USBHostDevice *s)
}
usb_host_release_interfaces(s);
- libusb_reset_device(s->dh);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_reset_device(s->dh);
+ }
+
usb_host_attach_kernel(s);
- libusb_close(s->dh);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_close(s->dh);
+ }
+
+ //replay_unregister_usb_device(udev);
+
s->dh = NULL;
s->dev = NULL;
+ s->is_open = false;
usb_host_auto_check(NULL);
return 0;
@@ -933,7 +984,7 @@ static void usb_host_nodev_bh(void *opaque)
static void usb_host_nodev(USBHostDevice *s)
{
if (!s->bh_nodev) {
- s->bh_nodev = qemu_bh_new(usb_host_nodev_bh, s);
+ s->bh_nodev = qemu_bh_new_replay(usb_host_nodev_bh, s, s->bus_num);
}
qemu_bh_schedule(s->bh_nodev);
}
@@ -942,7 +993,7 @@ static void usb_host_exit_notifier(struct Notifier *n, void *data)
{
USBHostDevice *s = container_of(n, USBHostDevice, exit);
- if (s->dh) {
+ if (s->is_open) {
usb_host_release_interfaces(s);
usb_host_attach_kernel(s);
}
@@ -1004,57 +1055,70 @@ static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p)
r = usb_host_req_find(s, p);
if (r && r->p) {
r->p = NULL; /* mark as dead */
- libusb_cancel_transfer(r->xfer);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_cancel_transfer(r->xfer);
+ }
}
}
static void usb_host_detach_kernel(USBHostDevice *s)
{
- struct libusb_config_descriptor *conf;
+ struct libusb_config_descriptor dummy_conf;
+ struct libusb_config_descriptor *conf = &dummy_conf;
int rc, i;
- rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ REPLAY_DATA_INT(rc, libusb_get_active_config_descriptor(s->dev, &conf));
if (rc != 0) {
return;
}
+ REPLAY_DATA_VAR(conf->bNumInterfaces);
for (i = 0; i < conf->bNumInterfaces; i++) {
- rc = libusb_kernel_driver_active(s->dh, i);
+ REPLAY_DATA_INT(rc, libusb_kernel_driver_active(s->dh, i));
usb_host_libusb_error("libusb_kernel_driver_active", rc);
if (rc != 1) {
continue;
}
trace_usb_host_detach_kernel(s->bus_num, s->addr, i);
- rc = libusb_detach_kernel_driver(s->dh, i);
+ REPLAY_DATA_INT(rc, libusb_detach_kernel_driver(s->dh, i));
usb_host_libusb_error("libusb_detach_kernel_driver", rc);
s->ifs[i].detached = true;
}
- libusb_free_config_descriptor(conf);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_free_config_descriptor(conf);
+ }
}
static void usb_host_attach_kernel(USBHostDevice *s)
{
- struct libusb_config_descriptor *conf;
+ struct libusb_config_descriptor dummy_conf;
+ struct libusb_config_descriptor *conf = &dummy_conf;
int rc, i;
- rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ REPLAY_DATA_INT(rc, libusb_get_active_config_descriptor(s->dev, &conf));
if (rc != 0) {
return;
}
- for (i = 0; i < conf->bNumInterfaces; i++) {
+ REPLAY_DATA_VAR(conf->bNumInterfaces);
+ for (i = 0; i < conf->bNumInterfaces ; i++) {
if (!s->ifs[i].detached) {
continue;
}
trace_usb_host_attach_kernel(s->bus_num, s->addr, i);
- libusb_attach_kernel_driver(s->dh, i);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_attach_kernel_driver(s->dh, i);
+ }
s->ifs[i].detached = false;
}
- libusb_free_config_descriptor(conf);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_free_config_descriptor(conf);
+ }
}
static int usb_host_claim_interfaces(USBHostDevice *s, int configuration)
{
USBDevice *udev = USB_DEVICE(s);
- struct libusb_config_descriptor *conf;
+ struct libusb_config_descriptor dummy_conf;
+ struct libusb_config_descriptor *conf = &dummy_conf;
int rc, i;
for (i = 0; i < USB_MAX_INTERFACES; i++) {
@@ -1065,7 +1129,7 @@ static int usb_host_claim_interfaces(USBHostDevice *s, int configuration)
usb_host_detach_kernel(s);
- rc = libusb_get_active_config_descriptor(s->dev, &conf);
+ REPLAY_DATA_INT(rc, libusb_get_active_config_descriptor(s->dev, &conf));
if (rc != 0) {
if (rc == LIBUSB_ERROR_NOT_FOUND) {
/* address state - ignore */
@@ -1073,10 +1137,11 @@ static int usb_host_claim_interfaces(USBHostDevice *s, int configuration)
}
return USB_RET_STALL;
}
+ REPLAY_DATA_VAR(conf->bNumInterfaces);
for (i = 0; i < conf->bNumInterfaces; i++) {
trace_usb_host_claim_interface(s->bus_num, s->addr, configuration, i);
- rc = libusb_claim_interface(s->dh, i);
+ REPLAY_DATA_INT(rc, libusb_claim_interface(s->dh, i));
usb_host_libusb_error("libusb_claim_interface", rc);
if (rc != 0) {
return USB_RET_STALL;
@@ -1087,7 +1152,9 @@ static int usb_host_claim_interfaces(USBHostDevice *s, int configuration)
udev->ninterfaces = conf->bNumInterfaces;
udev->configuration = configuration;
- libusb_free_config_descriptor(conf);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_free_config_descriptor(conf);
+ }
return USB_RET_SUCCESS;
}
@@ -1101,7 +1168,7 @@ static void usb_host_release_interfaces(USBHostDevice *s)
continue;
}
trace_usb_host_release_interface(s->bus_num, s->addr, i);
- rc = libusb_release_interface(s->dh, i);
+ REPLAY_DATA_INT(rc, libusb_release_interface(s->dh, i));
usb_host_libusb_error("libusb_release_interface", rc);
s->ifs[i].claimed = false;
}
@@ -1122,7 +1189,7 @@ static void usb_host_set_config(USBHostDevice *s, int config, USBPacket *p)
trace_usb_host_set_config(s->bus_num, s->addr, config);
usb_host_release_interfaces(s);
- rc = libusb_set_configuration(s->dh, config);
+ REPLAY_DATA_INT(rc, libusb_set_configuration(s->dh, config));
if (rc != 0) {
usb_host_libusb_error("libusb_set_configuration", rc);
p->status = USB_RET_STALL;
@@ -1153,7 +1220,7 @@ static void usb_host_set_interface(USBHostDevice *s, int iface, int alt,
return;
}
- rc = libusb_set_interface_alt_setting(s->dh, iface, alt);
+ REPLAY_DATA_INT(rc, libusb_set_interface_alt_setting(s->dh, iface, alt));
if (rc != 0) {
usb_host_libusb_error("libusb_set_interface_alt_setting", rc);
p->status = USB_RET_STALL;
@@ -1177,7 +1244,7 @@ static void usb_host_handle_control(USBDevice *udev, USBPacket *p,
trace_usb_host_req_control(s->bus_num, s->addr, p, request, value, index);
- if (s->dh == NULL) {
+ if (!s->is_open) {
p->status = USB_RET_NODEV;
trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
return;
@@ -1202,7 +1269,9 @@ static void usb_host_handle_control(USBDevice *udev, USBPacket *p,
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
if (value == 0) { /* clear halt */
int pid = (index & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
- libusb_clear_halt(s->dh, index);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_clear_halt(s->dh, index);
+ }
usb_ep_set_halted(udev, pid, index & 0x0f, 0);
trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
return;
@@ -1222,13 +1291,15 @@ static void usb_host_handle_control(USBDevice *udev, USBPacket *p,
if (udev->speed == USB_SPEED_SUPER &&
!((udev->port->speedmask & USB_SPEED_MASK_SUPER)) &&
request == 0x8006 && value == 0x100 && index == 0) {
- r->usb3ep0quirk = true;
+ usb3ep0quirk = true;
}
+ // call in REPLAY_PLAY too - this function just copies data
libusb_fill_control_transfer(r->xfer, s->dh, r->buffer,
- usb_host_req_complete_ctrl, r,
- CONTROL_TIMEOUT);
- rc = libusb_submit_transfer(r->xfer);
+ replay_mode == REPLAY_NONE ? usb_host_req_complete_ctrl : replay_req_complete_ctrl,
+ r, CONTROL_TIMEOUT);
+
+ REPLAY_DATA_INT(rc, libusb_submit_transfer(r->xfer));
if (rc != 0) {
p->status = USB_RET_NODEV;
trace_usb_host_req_complete(s->bus_num, s->addr, p,
@@ -1237,6 +1308,9 @@ static void usb_host_handle_control(USBDevice *udev, USBPacket *p,
usb_host_nodev(s);
}
return;
+ } else {
+ replay_req_register_ctrl(r->xfer);
+ ++submitted_xfers;
}
p->status = USB_RET_ASYNC;
@@ -1258,7 +1332,7 @@ static void usb_host_handle_data(USBDevice *udev, USBPacket *p)
p->pid == USB_TOKEN_IN,
p->ep->nr, p->iov.size);
- if (s->dh == NULL) {
+ if (!s->is_open) {
p->status = USB_RET_NODEV;
trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status);
return;
@@ -1291,7 +1365,7 @@ static void usb_host_handle_data(USBDevice *udev, USBPacket *p)
} else {
libusb_fill_bulk_transfer(r->xfer, s->dh, ep,
r->buffer, size,
- usb_host_req_complete_data, r,
+ replay_mode == REPLAY_NONE ? usb_host_req_complete_data : replay_req_complete_data, r,
BULK_TIMEOUT);
}
break;
@@ -1303,7 +1377,7 @@ static void usb_host_handle_data(USBDevice *udev, USBPacket *p)
ep = p->ep->nr | (r->in ? USB_DIR_IN : 0);
libusb_fill_interrupt_transfer(r->xfer, s->dh, ep,
r->buffer, p->iov.size,
- usb_host_req_complete_data, r,
+ replay_mode == REPLAY_NONE ? usb_host_req_complete_data : replay_req_complete_data, r,
INTR_TIMEOUT);
break;
case USB_ENDPOINT_XFER_ISOC:
@@ -1322,7 +1396,7 @@ static void usb_host_handle_data(USBDevice *udev, USBPacket *p)
return;
}
- rc = libusb_submit_transfer(r->xfer);
+ REPLAY_DATA_INT(rc, libusb_submit_transfer(r->xfer));
if (rc != 0) {
p->status = USB_RET_NODEV;
trace_usb_host_req_complete(s->bus_num, s->addr, p,
@@ -1331,6 +1405,9 @@ static void usb_host_handle_data(USBDevice *udev, USBPacket *p)
usb_host_nodev(s);
}
return;
+ } else {
+ replay_req_register_data(r->xfer);
+ ++submitted_xfers;
}
p->status = USB_RET_ASYNC;
@@ -1350,7 +1427,7 @@ static void usb_host_handle_reset(USBDevice *udev)
trace_usb_host_reset(s->bus_num, s->addr);
- rc = libusb_reset_device(s->dh);
+ REPLAY_DATA_INT(rc, libusb_reset_device(s->dh));
if (rc != 0) {
usb_host_nodev(s);
}
@@ -1425,7 +1502,7 @@ static void usb_host_post_load_bh(void *opaque)
USBHostDevice *dev = opaque;
USBDevice *udev = USB_DEVICE(dev);
- if (dev->dh != NULL) {
+ if (dev->is_open) {
usb_host_close(dev);
}
if (udev->attached) {
@@ -1445,6 +1522,37 @@ static int usb_host_post_load(void *opaque, int version_id)
return 0;
}
+static int usb_host_post_load_replay(void *opaque, int version_id)
+{
+ USBHostDevice *dev = opaque;
+
+ usb_host_free_xfers(dev);
+ usb_host_iso_free_all(dev);
+ submitted_xfers = 0;
+ return 0;
+}
+
+static void usb_host_pre_save_replay(void *opaque)
+{
+ USBHostDevice *dev = opaque;
+
+ if (!QTAILQ_EMPTY(&dev->requests)) {
+ fprintf(stderr, "Replay: Saving VM state with non-empty USB requests queue\n");
+ exit(1);
+ }
+
+ if (!QTAILQ_EMPTY(&dev->isorings)) {
+ fprintf(stderr, "Replay: Saving VM state with non-empty USB iso rings queue\n");
+ exit(1);
+ }
+
+ // paranoidal checking
+ if (submitted_xfers != 0) {
+ fprintf(stderr, "Replay: Saving VM state with non-empty USB iso rings queue\n");
+ exit(1);
+ }
+}
+
static const VMStateDescription vmstate_usb_host = {
.name = "usb-host",
.version_id = 1,
@@ -1456,6 +1564,51 @@ static const VMStateDescription vmstate_usb_host = {
}
};
+static const VMStateDescription vmstate_usb_host_interface = {
+ .name = "usb-host-interface",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(detached, USBHostInterface),
+ VMSTATE_BOOL(claimed, USBHostInterface),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_usb_host_replay = {
+ .name = "usb-host",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = usb_host_post_load_replay,
+ .pre_save = usb_host_pre_save_replay,
+ .fields = (VMStateField[]) {
+ VMSTATE_USB_DEVICE(parent_obj, USBHostDevice),
+ VMSTATE_INT32(seen, USBHostDevice),
+ VMSTATE_INT32(errcount, USBHostDevice),
+ VMSTATE_INT32(bus_num, USBHostDevice),
+ VMSTATE_INT32(addr, USBHostDevice),
+ VMSTATE_INT32(bus_num, USBHostDevice),
+ VMSTATE_BOOL(is_open, USBHostDevice),
+ VMSTATE_CHAR_ARRAY(port, USBHostDevice, 16),
+ VMSTATE_UINT8(ddesc.bLength, USBHostDevice),
+ VMSTATE_UINT8(ddesc.bDescriptorType, USBHostDevice),
+ VMSTATE_UINT16(ddesc.bcdUSB, USBHostDevice),
+ VMSTATE_UINT8(ddesc.bDeviceClass, USBHostDevice),
+ VMSTATE_UINT8(ddesc.bDeviceSubClass, USBHostDevice),
+ VMSTATE_UINT8(ddesc.bDeviceProtocol, USBHostDevice),
+ VMSTATE_UINT8(ddesc.bMaxPacketSize0, USBHostDevice),
+ VMSTATE_UINT16(ddesc.idVendor, USBHostDevice),
+ VMSTATE_UINT16(ddesc.idProduct, USBHostDevice),
+ VMSTATE_UINT16(ddesc.bcdDevice, USBHostDevice),
+ VMSTATE_UINT8(ddesc.iManufacturer, USBHostDevice),
+ VMSTATE_UINT8(ddesc.iProduct, USBHostDevice),
+ VMSTATE_UINT8(ddesc.iSerialNumber, USBHostDevice),
+ VMSTATE_UINT8(ddesc.bNumConfigurations, USBHostDevice),
+ VMSTATE_STRUCT_ARRAY(ifs, USBHostDevice, USB_MAX_INTERFACES, 1, vmstate_usb_host_interface, USBHostInterface),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static Property usb_host_dev_properties[] = {
DEFINE_PROP_UINT32("hostbus", USBHostDevice, match.bus_num, 0),
DEFINE_PROP_UINT32("hostaddr", USBHostDevice, match.addr, 0),
@@ -1487,7 +1640,10 @@ static void usb_host_class_initfn(ObjectClass *klass, void *data)
uc->flush_ep_queue = usb_host_flush_ep_queue;
uc->alloc_streams = usb_host_alloc_streams;
uc->free_streams = usb_host_free_streams;
- dc->vmsd = &vmstate_usb_host;
+ if (replay_mode == REPLAY_NONE)
+ dc->vmsd = &vmstate_usb_host;
+ else
+ dc->vmsd = &vmstate_usb_host_replay;
dc->props = usb_host_dev_properties;
set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
}
@@ -1508,7 +1664,6 @@ type_init(usb_host_register_types)
/* ------------------------------------------------------------------------ */
-static QEMUTimer *usb_auto_timer;
static VMChangeStateEntry *usb_vmstate;
static void usb_host_vm_state(void *unused, int running, RunState state)
@@ -1525,70 +1680,74 @@ static void usb_host_auto_check(void *unused)
libusb_device **devs = NULL;
struct libusb_device_descriptor ddesc;
int unconnected = 0;
- int i, n;
+ int i, n, tmp;
if (usb_host_init() != 0) {
return;
}
if (runstate_is_running()) {
- n = libusb_get_device_list(ctx, &devs);
+ REPLAY_DATA_INT(n, libusb_get_device_list(ctx, &devs));
for (i = 0; i < n; i++) {
- if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) {
+ if (REPLAY_DATA_INT(tmp, libusb_get_device_descriptor(devs[i], &ddesc)) != 0) {
continue;
}
- if (ddesc.bDeviceClass == LIBUSB_CLASS_HUB) {
+ if (REPLAY_DATA_INT(tmp, ddesc.bDeviceClass) == LIBUSB_CLASS_HUB) {
continue;
}
QTAILQ_FOREACH(s, &hostdevs, next) {
f = &s->match;
if (f->bus_num > 0 &&
- f->bus_num != libusb_get_bus_number(devs[i])) {
+ f->bus_num != REPLAY_DATA_INT(tmp, libusb_get_bus_number(devs[i]))) {
continue;
}
if (f->addr > 0 &&
- f->addr != libusb_get_device_address(devs[i])) {
+ f->addr != REPLAY_DATA_INT(tmp, libusb_get_device_address(devs[i]))) {
continue;
}
if (f->port != NULL) {
char port[16] = "-";
- usb_host_get_port(devs[i], port, sizeof(port));
+ if (replay_mode != REPLAY_PLAY) {
+ usb_host_get_port(devs[i], port, sizeof(port));
+ }
+ replay_data_buffer((unsigned char *)port, sizeof(port));
if (strcmp(f->port, port) != 0) {
continue;
}
}
if (f->vendor_id > 0 &&
- f->vendor_id != ddesc.idVendor) {
+ f->vendor_id != REPLAY_DATA_INT(tmp, ddesc.idVendor)) {
continue;
}
if (f->product_id > 0 &&
- f->product_id != ddesc.idProduct) {
+ f->product_id != REPLAY_DATA_INT(tmp, ddesc.idProduct)) {
continue;
}
-
/* We got a match */
s->seen++;
if (s->errcount >= 3) {
continue;
}
- if (s->dh != NULL) {
+ if (s->is_open) {
continue;
}
- if (usb_host_open(s, devs[i]) < 0) {
+ if (usb_host_open(s, replay_mode == REPLAY_PLAY ? (libusb_device*)0xbad : devs[i]) < 0) {
s->errcount++;
continue;
}
break;
}
}
- libusb_free_device_list(devs, 1);
+ if (replay_mode != REPLAY_PLAY) {
+ libusb_free_device_list(devs, 1);
+ }
QTAILQ_FOREACH(s, &hostdevs, next) {
- if (s->dh == NULL) {
+ if (!s->is_open) {
unconnected++;
}
if (s->seen == 0) {
- if (s->dh) {
+ if (s->is_open) {
usb_host_close(s);
}
s->errcount = 0;
@@ -1608,17 +1767,19 @@ static void usb_host_auto_check(void *unused)
#endif
}
- if (!usb_vmstate) {
+ if (!usb_vmstate && replay_mode == REPLAY_NONE) {
usb_vmstate = qemu_add_vm_change_state_handler(usb_host_vm_state, NULL);
}
- if (!usb_auto_timer) {
- usb_auto_timer = timer_new_ms(QEMU_CLOCK_REALTIME, usb_host_auto_check, NULL);
+
+
+ /*if (!usb_auto_timer) {
+ usb_auto_timer.timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, usb_host_auto_check, NULL);
if (!usb_auto_timer) {
return;
}
trace_usb_host_auto_scan_enabled();
- }
- timer_mod(usb_auto_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000);
+ }*/
+ timer_mod(usb_auto_timer.timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 2000);
}
void usb_host_info(Monitor *mon, const QDict *qdict)
diff --git a/include/hw/host-libusb.h b/include/hw/host-libusb.h
new file mode 100755
index 0000000..2f5a9d7
--- /dev/null
+++ b/include/hw/host-libusb.h
@@ -0,0 +1,105 @@
+#ifndef HOST_LIBUSB_H
+#define HOST_LIBUSB_H
+
+#include <libusb.h>
+
+#include "qemu/notify.h"
+
+#define TYPE_USB_HOST_DEVICE "usb-host"
+#define USB_HOST_DEVICE(obj) \
+ OBJECT_CHECK(USBHostDevice, (obj), TYPE_USB_HOST_DEVICE)
+
+struct USBAutoFilter {
+ uint32_t bus_num;
+ uint32_t addr;
+ char *port;
+ uint32_t vendor_id;
+ uint32_t product_id;
+};
+
+typedef struct USBHostDevice USBHostDevice;
+typedef struct USBHostRequest USBHostRequest;
+typedef struct USBHostIsoXfer USBHostIsoXfer;
+typedef struct USBHostIsoRing USBHostIsoRing;
+typedef struct USBHostInterface USBHostInterface;
+
+enum USBHostDeviceOptions {
+ USB_HOST_OPT_PIPELINE,
+};
+
+struct USBHostInterface {
+ bool detached;
+ bool claimed;
+};
+
+struct USBHostDevice {
+ USBDevice parent_obj;
+
+ /* properties */
+ struct USBAutoFilter match;
+ int32_t bootindex;
+ uint32_t iso_urb_count;
+ uint32_t iso_urb_frames;
+ uint32_t options;
+ uint32_t loglevel;
+
+ /* state */
+ QTAILQ_ENTRY(USBHostDevice) next;
+ int seen, errcount;
+ int bus_num;
+ int addr;
+ char port[16];
+ bool is_open;
+
+ libusb_device *dev;
+ libusb_device_handle *dh;
+ struct libusb_device_descriptor ddesc;
+
+ USBHostInterface ifs[USB_MAX_INTERFACES];
+
+ /* callbacks & friends */
+ QEMUBH *bh_nodev;
+ QEMUBH *bh_postld;
+ Notifier exit;
+
+ /* request queues */
+ QTAILQ_HEAD(, USBHostRequest) requests;
+ QTAILQ_HEAD(, USBHostIsoRing) isorings;
+};
+
+struct USBHostRequest {
+ USBHostDevice *host;
+ USBPacket *p;
+ bool in;
+ struct libusb_transfer *xfer;
+ unsigned char *buffer;
+ unsigned char *cbuf;
+ unsigned int clen;
+ QTAILQ_ENTRY(USBHostRequest) next;
+};
+
+struct USBHostIsoXfer {
+ USBHostIsoRing *ring;
+ struct libusb_transfer *xfer;
+ bool copy_complete;
+ unsigned int packet;
+ QTAILQ_ENTRY(USBHostIsoXfer) next;
+};
+
+struct USBHostIsoRing {
+ USBHostDevice *host;
+ USBEndpoint *ep;
+ QTAILQ_HEAD(, USBHostIsoXfer) unused;
+ QTAILQ_HEAD(, USBHostIsoXfer) inflight;
+ QTAILQ_HEAD(, USBHostIsoXfer) copy;
+ QTAILQ_ENTRY(USBHostIsoRing) next;
+};
+
+void usb_host_req_complete_ctrl(struct libusb_transfer *xfer);
+void usb_host_req_complete_data(struct libusb_transfer *xfer);
+void usb_host_req_complete_iso(struct libusb_transfer *xfer);
+unsigned char *usb_host_get_iso_packet_buffer(USBHostIsoXfer *xfer, int packet);
+
+bool usb_host_has_xfers(void);
+
+#endif
diff --git a/replay/Makefile.objs b/replay/Makefile.objs
index c54e550..f2e3bdc 100755
--- a/replay/Makefile.objs
+++ b/replay/Makefile.objs
@@ -6,3 +6,4 @@ obj-y += replay-input.o
obj-y += replay-net.o
obj-y += replay-audio.o
obj-y += replay-char.o
+obj-y += replay-usb.o
diff --git a/replay/replay-events.c b/replay/replay-events.c
index 6e0120e..c7b1215 100755
--- a/replay/replay-events.c
+++ b/replay/replay-events.c
@@ -59,6 +59,17 @@ static void replay_run_event(Event *event)
case REPLAY_ASYNC_EVENT_CHAR:
replay_event_char_run(event->opaque);
break;
+#ifdef CONFIG_USB_LIBUSB
+ case REPLAY_ASYNC_EVENT_USB_CTRL:
+ replay_event_usb_ctrl(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_USB_DATA:
+ replay_event_usb_data(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_USB_ISO:
+ replay_event_usb_iso(event->opaque);
+ break;
+#endif
default:
fprintf(stderr, "Replay: invalid async event ID (%d) in the queue\n",
event->event_kind);
@@ -161,6 +172,11 @@ void replay_add_input_sync_event(void)
replay_add_event_internal(REPLAY_ASYNC_EVENT_INPUT_SYNC, NULL, NULL, 0);
}
+void replay_add_usb_event(unsigned int event_kind, uint64_t id, void *opaque)
+{
+ replay_add_event_internal(event_kind, opaque, NULL, id);
+}
+
void replay_save_events(int opt)
{
qemu_mutex_lock(&lock);
@@ -191,6 +207,17 @@ void replay_save_events(int opt)
case REPLAY_ASYNC_EVENT_CHAR:
replay_event_char_save(event->opaque);
break;
+#ifdef CONFIG_USB_LIBUSB
+ case REPLAY_ASYNC_EVENT_USB_CTRL:
+ case REPLAY_ASYNC_EVENT_USB_DATA:
+ replay_put_qword(event->id);
+ replay_event_save_usb_xfer(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_USB_ISO:
+ replay_put_qword(event->id);
+ replay_event_save_usb_iso_xfer(event->opaque);
+ break;
+#endif
}
}
@@ -273,6 +300,16 @@ void replay_read_events(int opt)
replay_run_event(&e);
/* continue with the next event */
continue;
+#ifdef CONFIG_USB_LIBUSB
+ case REPLAY_ASYNC_EVENT_USB_CTRL:
+ case REPLAY_ASYNC_EVENT_USB_DATA:
+ case REPLAY_ASYNC_EVENT_USB_ISO:
+ if (read_id == -1) {
+ read_id = replay_get_qword();
+ }
+ /* read after finding event in the list */
+ break;
+#endif
default:
fprintf(stderr, "Unknown ID %d of replay event\n", read_event_kind);
exit(1);
@@ -293,6 +330,18 @@ void replay_read_events(int opt)
if (event) {
/* read event-specific reading data */
+#ifdef CONFIG_USB_LIBUSB
+ switch (read_event_kind)
+ {
+ case REPLAY_ASYNC_EVENT_USB_CTRL:
+ case REPLAY_ASYNC_EVENT_USB_DATA:
+ replay_event_read_usb_xfer(event->opaque);
+ break;
+ case REPLAY_ASYNC_EVENT_USB_ISO:
+ replay_event_read_usb_iso_xfer(event->opaque);
+ break;
+ }
+#endif
QTAILQ_REMOVE(&events_list, event, events);
diff --git a/replay/replay-internal.h b/replay/replay-internal.h
index dfcd5fd..56b6ad9 100755
--- a/replay/replay-internal.h
+++ b/replay/replay-internal.h
@@ -40,6 +40,8 @@
#define EVENT_ASYNC_OPT 25
/* for int data */
#define EVENT_DATA_INT 26
+/* for data buffer */
+#define EVENT_DATA_BUFFER 27
/* for instruction event */
#define EVENT_INSTRUCTION 32
/* for clock read/writes */
@@ -59,7 +61,10 @@
#define REPLAY_ASYNC_EVENT_INPUT_SYNC 3
#define REPLAY_ASYNC_EVENT_NETWORK 4
#define REPLAY_ASYNC_EVENT_CHAR 5
-#define REPLAY_ASYNC_COUNT 6
+#define REPLAY_ASYNC_EVENT_USB_CTRL 6
+#define REPLAY_ASYNC_EVENT_USB_DATA 7
+#define REPLAY_ASYNC_EVENT_USB_ISO 8
+#define REPLAY_ASYNC_COUNT 9
typedef struct ReplayState {
/*! Cached clock values. */
@@ -195,4 +200,18 @@ void replay_event_char_save(void *opaque);
/*! Reads char event from the file. */
void *replay_event_char_read(void);
+/* USB events */
+
+void replay_event_usb_ctrl(void *opaque);
+void replay_event_usb_data(void *opaque);
+void replay_event_usb_iso(void *opaque);
+void replay_event_save_usb_xfer(void *opaque);
+void replay_event_save_usb_iso_xfer(void *opaque);
+void replay_event_read_usb_xfer(void *opaque);
+void replay_event_read_usb_iso_xfer(void *opaque);
+void replay_event_skip_usb_xfer(void);
+void replay_event_skip_usb_iso_xfer(void);
+bool replay_usb_has_xfers(void);
+void replay_add_usb_event(unsigned int event_id, uint64_t id, void *opaque);
+
#endif
diff --git a/replay/replay-usb.c b/replay/replay-usb.c
new file mode 100755
index 0000000..dae7f3d
--- /dev/null
+++ b/replay/replay-usb.c
@@ -0,0 +1,188 @@
+/*
+ * replay-usb.c
+ *
+ * Copyright (c) 2010-2014 Institute for System Programming
+ * of the Russian Academy of Sciences.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu-common.h"
+#include "replay.h"
+#include "replay-internal.h"
+#include "hw/usb.h"
+
+#ifdef CONFIG_USB_LIBUSB
+#include "hw/host-libusb.h"
+
+static uint64_t replay_get_xfer_id(struct libusb_transfer *xfer)
+{
+ USBHostRequest *r = xfer->user_data;
+ USBHostDevice *host = r->host;
+
+ return ((uint64_t)host->match.vendor_id << 32)
+ | host->match.product_id;
+}
+
+static uint64_t replay_get_iso_xfer_id(struct libusb_transfer *xfer)
+{
+ USBHostIsoXfer *r = xfer->user_data;
+ USBHostDevice *host = r->ring->host;
+
+ return ((uint64_t)host->match.vendor_id << 32)
+ | host->match.product_id;
+}
+
+void replay_req_complete_ctrl(struct libusb_transfer *xfer)
+{
+ if (replay_mode == REPLAY_SAVE) {
+ replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_CTRL,
+ replay_get_xfer_id(xfer), xfer);
+ }
+}
+
+void replay_req_register_ctrl(struct libusb_transfer *xfer)
+{
+ if (replay_mode == REPLAY_PLAY) {
+ replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_CTRL,
+ replay_get_xfer_id(xfer), xfer);
+ }
+}
+
+void replay_event_usb_ctrl(void *opaque)
+{
+ struct libusb_transfer *xfer = opaque;
+
+ usb_host_req_complete_ctrl(xfer);
+}
+
+void replay_event_save_usb_xfer(void *opaque)
+{
+ struct libusb_transfer *xfer = opaque;
+ USBHostRequest *r = xfer->user_data;
+ if (replay_mode == REPLAY_SAVE) {
+ replay_put_dword(xfer->status);
+ replay_put_dword(xfer->actual_length);
+ replay_put_array(xfer->buffer, r->in ? xfer->length : 0);
+ }
+}
+
+void replay_event_save_usb_iso_xfer(void *opaque)
+{
+ struct libusb_transfer *xfer = opaque;
+ USBHostIsoXfer *iso = xfer->user_data;
+ int i;
+ if (replay_mode == REPLAY_SAVE) {
+ bool in = iso->ring->ep->pid == USB_TOKEN_IN;
+ replay_put_dword(xfer->status);
+ replay_put_dword(xfer->num_iso_packets);
+ for (i = 0 ; i < xfer->num_iso_packets ; ++i) {
+ /* all other fields of the packet are not used */
+ unsigned int len = xfer->iso_packet_desc[i].actual_length;
+ if (in) {
+ replay_put_array(usb_host_get_iso_packet_buffer(iso, i), len);
+ }
+ }
+ }
+}
+
+void replay_event_read_usb_xfer(void *opaque)
+{
+ struct libusb_transfer *xfer = opaque;
+ USBHostRequest *r = xfer->user_data;
+
+ if (replay_mode == REPLAY_PLAY) {
+ xfer->status = replay_get_dword();
+ xfer->actual_length = replay_get_dword();
+ size_t sz;
+ replay_get_array(xfer->buffer, &sz);
+ if (r->in && xfer->length != (int)sz) {
+ fprintf(stderr, "Replay: trying to read USB control/data buffer with unexpected size\n");
+ exit(1);
+ }
+ }
+}
+
+void replay_event_read_usb_iso_xfer(void *opaque)
+{
+ struct libusb_transfer *xfer = opaque;
+ USBHostIsoXfer *iso = xfer->user_data;
+ int i;
+
+ if (replay_mode == REPLAY_PLAY) {
+ bool in = iso->ring->ep->pid == USB_TOKEN_IN;
+ xfer->status = replay_get_dword();
+ xfer->num_iso_packets = replay_get_dword();
+ for (i = 0 ; i < xfer->num_iso_packets ; ++i) {
+ /* all other fields of the packet are not used */
+ if (in) {
+ size_t sz;
+ replay_get_array(usb_host_get_iso_packet_buffer(iso, i), &sz);
+ xfer->iso_packet_desc[i].actual_length = (unsigned int)sz;
+ }
+ }
+ }
+}
+
+void replay_req_complete_data(struct libusb_transfer *xfer)
+{
+ if (replay_mode == REPLAY_SAVE) {
+ replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_DATA,
+ replay_get_xfer_id(xfer), xfer);
+ }
+}
+
+void replay_req_register_data(struct libusb_transfer *xfer)
+{
+ if (replay_mode == REPLAY_PLAY) {
+ replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_DATA,
+ replay_get_xfer_id(xfer), xfer);
+ }
+}
+
+
+void replay_event_usb_data(void *opaque)
+{
+ struct libusb_transfer *xfer = opaque;
+
+ usb_host_req_complete_data(xfer);
+}
+
+void replay_req_complete_iso(struct libusb_transfer *xfer)
+{
+ if (replay_mode == REPLAY_SAVE) {
+ replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_ISO,
+ replay_get_iso_xfer_id(xfer), xfer);
+ }
+}
+
+void replay_req_register_iso(struct libusb_transfer *xfer)
+{
+ if (replay_mode == REPLAY_PLAY) {
+ USBHostIsoXfer *r = xfer->user_data;
+ USBHostDevice *s = r->ring->host;
+
+ replay_add_usb_event(REPLAY_ASYNC_EVENT_USB_ISO,
+ replay_get_iso_xfer_id(xfer), xfer);
+ }
+}
+
+void replay_event_usb_iso(void *opaque)
+{
+ struct libusb_transfer *xfer = opaque;
+
+ usb_host_req_complete_iso(xfer);
+}
+
+#endif
+
+bool replay_usb_has_xfers(void)
+{
+#ifdef CONFIG_USB_LIBUSB
+ return usb_host_has_xfers();
+#else
+ return false;
+#endif
+}
diff --git a/replay/replay.c b/replay/replay.c
index 8e15b04..a89ba91 100755
--- a/replay/replay.c
+++ b/replay/replay.c
@@ -101,6 +101,16 @@ static void replay_savevm(void *opaque)
char name[128];
uint64_t offset;
+#ifdef CONFIG_USB_LIBUSB
+ if (replay_usb_has_xfers()) {
+ /* Retry of saving VM state, when USB host controller is not ready.
+ We cannot save or interrupt non-finished transfers, so
+ just wait for finishing them later. */
+ timer_mod(save_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1);
+ return;
+ }
+#endif
+
offset = ftello64(replay_file);
replay_save_instructions();
@@ -609,3 +619,22 @@ void replay_data_int(int *data)
replay_put_dword(*data);
}
}
+
+void replay_data_buffer(unsigned char *data, size_t size)
+{
+ if (replay_file && replay_mode == REPLAY_PLAY) {
+ size_t read_size = 0;
+ skip_async_events_until(EVENT_DATA_BUFFER);
+ replay_get_array(data, &read_size);
+ replay_check_error();
+ if (read_size != size) {
+ fprintf(stderr, "Replay: read non-matching size of the buffer\n");
+ exit(1);
+ }
+ replay_has_unread_data = 0;
+ } else if (replay_file && replay_mode == REPLAY_SAVE) {
+ replay_save_instructions();
+ replay_put_event(EVENT_DATA_BUFFER);
+ replay_put_array(data, size);
+ }
+}
diff --git a/replay/replay.h b/replay/replay.h
index 533fa0a..aa15803 100755
--- a/replay/replay.h
+++ b/replay/replay.h
@@ -24,6 +24,7 @@ struct QemuOpts;
struct InputEvent;
struct NetClientState;
struct CharDriverState;
+struct libusb_transfer;
/* replay modes */
#define REPLAY_NONE 0
@@ -157,9 +158,38 @@ void replay_register_char_driver(struct CharDriverState *chr);
/*! Saves write to char device event to the log */
void replay_chr_be_write(struct CharDriverState *s, uint8_t *buf, int len);
+/* USB events */
+
+/*! Called in save mode when xfer for ctrl usb data is completed */
+void replay_req_complete_ctrl(struct libusb_transfer *xfer);
+/*! Called in play mode to register xfer
+ to be completed by reading it from the log */
+void replay_req_register_ctrl(struct libusb_transfer *xfer);
+/*! Called in save mode when xfer for usb data request is completed */
+void replay_req_complete_data(struct libusb_transfer *xfer);
+/*! Called in play mode to register xfer
+ to be completed by reading it from the log */
+void replay_req_register_data(struct libusb_transfer *xfer);
+/*! Called in save mode when iso xfer for usb data request is completed */
+void replay_req_complete_iso(struct libusb_transfer *xfer);
+/*! Called in play mode to register iso xfer
+ to be completed by reading it from the log */
+void replay_req_register_iso(struct libusb_transfer *xfer);
+
/* Other data */
/*! Writes or reads integer value to/from replay log. */
void replay_data_int(int *data);
+/*! Macro for using replay_data_int function */
+#define REPLAY_DATA_INT(var, value) \
+ ((replay_mode == REPLAY_NONE ? var = (value) \
+ : replay_mode == REPLAY_SAVE \
+ ? (var = (value), replay_data_int(&var), var) \
+ : /* PLAY */ (replay_data_int(&var), var)))
+/*! Writes or reads buffer to/from replay log. */
+void replay_data_buffer(unsigned char *data, size_t size);
+/*! Macro for using replay_data_buffer function */
+#define REPLAY_DATA_VAR(var) \
+ replay_data_buffer((unsigned char*)&(var), sizeof(var))
#endif
next prev parent reply other threads:[~2014-07-17 11:06 UTC|newest]
Thread overview: 83+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-07-17 11:01 [Qemu-devel] [RFC PATCH v2 00/49] Series short description Pavel Dovgalyuk
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 01/49] acpi: accurate overflow check Pavel Dovgalyuk
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 02/49] integratorcp: adding vmstate for save/restore Pavel Dovgalyuk
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 03/49] pcspk: " Pavel Dovgalyuk
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 04/49] fdc: " Pavel Dovgalyuk
2014-07-28 9:47 ` Paolo Bonzini
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 05/49] parallel: " Pavel Dovgalyuk
2014-07-28 10:02 ` Paolo Bonzini
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 06/49] serial: fixing " Pavel Dovgalyuk
2014-07-28 9:58 ` Paolo Bonzini
2014-07-30 7:01 ` Pavel Dovgaluk
[not found] ` <19697.8771281012$1406703748@news.gmane.org>
2014-07-30 9:19 ` Paolo Bonzini
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 07/49] kvmapic: fixing loading vmstate Pavel Dovgalyuk
2014-07-28 8:49 ` Paolo Bonzini
2014-07-29 12:03 ` Pavel Dovgaluk
2014-07-29 12:16 ` Paolo Bonzini
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 08/49] hpet: fixing saving and loading process Pavel Dovgalyuk
2014-07-28 8:33 ` Paolo Bonzini
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 09/49] pckbd: adding new fields to vmstate Pavel Dovgalyuk
2014-07-28 9:36 ` Paolo Bonzini
2014-07-17 11:02 ` [Qemu-devel] [RFC PATCH v2 10/49] rtl8139: " Pavel Dovgalyuk
2014-07-28 9:41 ` Paolo Bonzini
2014-07-28 9:54 ` Pavel Dovgaluk
[not found] ` <37740.9009532586$1406541296@news.gmane.org>
2014-07-28 10:12 ` Paolo Bonzini
2014-07-30 8:24 ` Pavel Dovgaluk
2014-07-30 9:26 ` Paolo Bonzini
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 11/49] piix: do not raise irq while loading vmstate Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 12/49] mc146818rtc: add missed field to vmstate Pavel Dovgalyuk
2014-07-28 9:42 ` Paolo Bonzini
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 13/49] pl031: " Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 14/49] ide pci: reset status field before loading the vmstate Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 15/49] softmmu: fixing usage of cpu_st/ld* from helpers Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 16/49] target: save cpu state fields Pavel Dovgalyuk
2014-07-31 6:48 ` Andreas Färber
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 17/49] target-i386: update fp status fix Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 18/49] migration: add vmstate for int8 and char arrays Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 19/49] replay: global variables and function stubs Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 20/49] block: add suffix parameter to bdrv_open functions Pavel Dovgalyuk
2014-07-17 11:03 ` [Qemu-devel] [RFC PATCH v2 21/49] sysemu: system functions for replay Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 22/49] replay: internal functions for replay log Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 23/49] cpu: invent instruction count for accurate replay Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 24/49] target-arm: instructions counting code for replay Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 25/49] target-i386: " Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 26/49] replay: interrupts and exceptions Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 27/49] vga: do not use virtual clock for blinking cursor Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 28/49] replay: asynchronous events infrastructure Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 29/49] replay: recording and replaying clock ticks Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 30/49] replay: recording and replaying different timers Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 31/49] replay: shutdown event Pavel Dovgalyuk
2014-07-17 11:04 ` [Qemu-devel] [RFC PATCH v2 32/49] replay: checkpoints Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 33/49] replay: bottom halves Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 34/49] replay: replay aio requests Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 35/49] replay: thread pool Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 36/49] pl031: vmstate in replay mode Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 37/49] replay: initialization and deinitialization Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 38/49] replay: command line options Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 39/49] replay: snapshotting the virtual machine Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 40/49] replay: recording of the user input Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 41/49] tap-win32: destroy the thread at exit Pavel Dovgalyuk
2014-07-17 11:05 ` [Qemu-devel] [RFC PATCH v2 42/49] replay: network packets record/replay Pavel Dovgalyuk
2014-07-17 11:06 ` [Qemu-devel] [RFC PATCH v2 43/49] replay: audio data record/replay Pavel Dovgalyuk
2014-07-17 11:06 ` [Qemu-devel] [RFC PATCH v2 44/49] replay: serial port Pavel Dovgalyuk
2014-07-17 11:06 ` Pavel Dovgalyuk [this message]
2014-07-17 11:06 ` [Qemu-devel] [RFC PATCH v2 46/49] replay: replay_info command Pavel Dovgalyuk
2014-07-18 15:55 ` Eric Blake
2014-07-18 15:56 ` Eric Blake
2014-07-17 11:06 ` [Qemu-devel] [RFC PATCH v2 47/49] replay: replay_break command Pavel Dovgalyuk
2014-07-18 15:58 ` Eric Blake
2014-07-17 11:06 ` [Qemu-devel] [RFC PATCH v2 48/49] replay: replay_seek_step command Pavel Dovgalyuk
2014-07-18 15:59 ` Eric Blake
2014-07-17 11:06 ` [Qemu-devel] [RFC PATCH v2 49/49] gdbstub: reverse debugging Pavel Dovgalyuk
2014-07-18 8:10 ` [Qemu-devel] [RFC PATCH v2 00/49] Series short description Frederic Konrad
2014-07-24 17:48 ` Paolo Bonzini
2014-07-28 7:50 ` Pavel Dovgaluk
[not found] ` <2596.37912172384$1406533875@news.gmane.org>
2014-07-28 10:12 ` Paolo Bonzini
2014-07-30 7:44 ` Pavel Dovgaluk
2014-07-30 9:25 ` Paolo Bonzini
2014-07-30 13:19 ` Frederic Konrad
2014-07-30 13:35 ` Paolo Bonzini
2014-07-30 14:51 ` Frederic Konrad
2014-07-31 13:05 ` Frederic Konrad
2014-07-31 14:18 ` Paolo Bonzini
2014-07-31 5:44 ` Pavel Dovgaluk
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=20140717110611.8352.90855.stgit@PASHA-ISP \
--to=pavel.dovgaluk@ispras.ru \
--cc=batuzovk@ispras.ru \
--cc=fred.konrad@greensocs.com \
--cc=mark.burton@greensocs.com \
--cc=pbonzini@redhat.com \
--cc=peter.crosthwaite@xilinx.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-devel@nongnu.org \
--cc=real@ispras.ru \
/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).