|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH v8 17/18] hw/usb/redirect-server: Implement isochronous OUT transfers
The protocol has three callbacks for isochronous transfers:
start_iso_stream_func, stop_iso_stream_func and iso_packet_func.
Isochronous has no capability bit, and the parser calls them without
checking, so a server cannot leave them out.
Implement the three for an OUT endpoint. Endpoint 0 is control, so
refuse it. The host sends a packet for every OUT transfer. It sends
nothing for an IN transfer, so refuse an IN endpoint with a stall.
Only a stall makes the host close the stream. Any other status leaves
it open on the host side.
An isochronous packet has no id, and the host wants no answer. Report
a failure with an iso_stream_status. A broken endpoint fails every
frame, so send at most one report a second. A stall does not wait. A
stall ends the stream, and the host has to hear it.
With no id, a cancel never matches an isochronous packet. A NAK does
not park it for a retry either.
Reported-by: Junjie Cao <junjie.cao@xxxxxxxxx>
Signed-off-by: Jamin Lin <jamin_lin@xxxxxxxxxxxxxx>
---
hw/usb/redirect-server.c | 161 ++++++++++++++++++++++++++++++++++++++-
hw/usb/trace-events | 5 ++
2 files changed, 162 insertions(+), 4 deletions(-)
diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index cfd2bcc438..95d25b5292 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -99,10 +99,14 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER)
/* Buffer size for an interrupt IN endpoint before its descriptor is seen. */
#define USBREDIR_SERVER_INTR_STREAM_DEFAULT_LEN 64
+/* The shortest time between two failure reports. */
+#define USBREDIR_SERVER_ISO_STREAM_STATUS_MS 1000
+
#define USBREDIR_SERVER_CTRL 0
#define USBREDIR_SERVER_BULK 1
#define USBREDIR_SERVER_INTERRUPT 2
#define USBREDIR_SERVER_INTR_STREAM 3
+#define USBREDIR_SERVER_ISO_STREAM 4
/* Which message answers the host when a control transfer ends. */
typedef enum {
@@ -128,6 +132,7 @@ typedef struct USBRedirServerPkt {
struct usb_redir_interrupt_packet_header intr_hdr;
struct usb_redir_control_packet_header ctrl_hdr;
struct usb_redir_bulk_packet_header bulk_hdr;
+ struct usb_redir_iso_packet_header iso_hdr;
/*
* The IOV points here. IN data lands in it, OUT data is copied in.
@@ -169,6 +174,13 @@ typedef struct USBRedirServerEp {
QEMUBH *intr_stream_bh;
bool intr_streaming;
+ /*
+ * Isochronous OUT streaming. The next failure report goes out at
+ * this time.
+ */
+ int64_t iso_status_next_ns;
+ bool iso_streaming;
+
uint16_t max_packet;
uint8_t interval;
uint8_t iface;
@@ -724,6 +736,47 @@ static void
usbredir_server_intr_stream_complete(USBRedirServer *s,
}
}
+/*
+ * Isochronous transfers have no answer and no retry, so a good packet
+ * needs nothing back.
+ *
+ * A broken endpoint fails every frame, so we do not report every
+ * failure.
+ */
+static void usbredir_server_iso_stream_complete(USBRedirServer *s,
+ USBRedirServerPkt *rp)
+{
+ struct usb_redir_iso_stream_status_header st;
+ USBRedirServerEp *ep = &s->ep[rp->ep_idx];
+ USBPacket *p = &rp->pkt;
+ int64_t now_ns;
+
+ trace_usbredir_server_iso_stream_complete(rp->iso_hdr.endpoint, p->status,
+ p->actual_length);
+
+ switch (p->status) {
+ case USB_RET_SUCCESS:
+ return;
+ case USB_RET_STALL:
+ /* The stream ends here, so the host has to hear this one. */
+ ep->iso_streaming = false;
+ break;
+ default:
+ now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ if (now_ns < ep->iso_status_next_ns) {
+ return;
+ }
+ ep->iso_status_next_ns = now_ns +
+ (int64_t)USBREDIR_SERVER_ISO_STREAM_STATUS_MS * SCALE_MS;
+ break;
+ }
+
+ st.endpoint = rp->iso_hdr.endpoint;
+ st.status = usbredir_server_status(p->status);
+ usbredirparser_send_iso_stream_status(s->parser, 0, &st);
+ usbredirparser_do_write(s->parser);
+}
+
/*
* USB port ops
*/
@@ -806,6 +859,9 @@ static void usbredir_server_packet_complete(USBPort *port,
USBPacket *p)
case USBREDIR_SERVER_INTR_STREAM:
usbredir_server_intr_stream_complete(s, rp);
break;
+ case USBREDIR_SERVER_ISO_STREAM:
+ usbredir_server_iso_stream_complete(s, rp);
+ break;
default:
g_assert_not_reached();
}
@@ -845,11 +901,12 @@ static USBRedirServerAsk
usbredir_server_ask_device(USBRedirServer *s,
/*
* Keep the request and send it again when the device wakes the
- * endpoint. A streamed request asks again by itself, with its own
- * timer.
+ * endpoint. A streamed request does not wait here. The interrupt
+ * stream has a timer of its own, and isochronous never retries.
*/
if (rp->pkt.status == USB_RET_NAK &&
- rp->type != USBREDIR_SERVER_INTR_STREAM) {
+ rp->type != USBREDIR_SERVER_INTR_STREAM &&
+ rp->type != USBREDIR_SERVER_ISO_STREAM) {
trace_usbredir_server_nak_park(rp->redir_id, rp->ep_idx);
QTAILQ_INSERT_HEAD(&ep->waiting, rp, waiting_link);
timer_mod(ep->retry, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
@@ -1513,6 +1570,93 @@ static void
usbredir_server_stop_interrupt_receiving(void *priv, uint64_t id,
usbredir_server_intr_stream_cancel(s, ep_nr);
}
+static void usbredir_server_start_iso_stream(void *priv, uint64_t id,
+ struct usb_redir_start_iso_stream_header *hdr)
+{
+ int idx = usbredir_server_ep_index(hdr->endpoint);
+ struct usb_redir_iso_stream_status_header st = {
+ .endpoint = hdr->endpoint,
+ };
+ USBRedirServer *s = priv;
+
+ st.status = usbredir_server_ep_status(s, hdr->endpoint,
+ USB_ENDPOINT_XFER_ISOC);
+
+ /*
+ * Only an OUT stream works here. Only a stall makes the host
+ * close the stream.
+ */
+ if (hdr->endpoint & USB_DIR_IN) {
+ st.status = usb_redir_stall;
+ }
+
+ if (st.status == usb_redir_success) {
+ s->ep[idx].iso_streaming = true;
+ s->ep[idx].iso_status_next_ns = 0;
+ }
+
+ trace_usbredir_server_iso_stream_start(hdr->endpoint, hdr->pkts_per_urb,
+ hdr->no_urbs, st.status);
+ usbredirparser_send_iso_stream_status(s->parser, id, &st);
+ usbredirparser_do_write(s->parser);
+}
+
+static void usbredir_server_stop_iso_stream(void *priv, uint64_t id,
+ struct usb_redir_stop_iso_stream_header *hdr)
+{
+ int idx = usbredir_server_ep_index(hdr->endpoint);
+ int ep_nr = hdr->endpoint & 0x0f;
+ USBRedirServer *s = priv;
+
+ trace_usbredir_server_iso_stream_stop(hdr->endpoint);
+
+ /* Endpoint 0 is control, and an IN endpoint never streams. */
+ if (ep_nr == 0 || (hdr->endpoint & USB_DIR_IN)) {
+ return;
+ }
+
+ s->ep[idx].iso_streaming = false;
+}
+
+/* An isochronous packet has no id, and the host wants no answer. */
+static void usbredir_server_iso_packet(void *priv, uint64_t id,
+ struct usb_redir_iso_packet_header *hdr, uint8_t *data, int data_len)
+{
+ int idx = usbredir_server_ep_index(hdr->endpoint);
+ USBDevice *device = usbredir_server_device(priv);
+ int ep_nr = hdr->endpoint & 0x0f;
+ USBRedirServer *s = priv;
+ USBRedirServerPkt *rp;
+ USBEndpoint *ep;
+ uint8_t status;
+
+ status = usbredir_server_ep_status(s, hdr->endpoint,
+ USB_ENDPOINT_XFER_ISOC);
+
+ if (status != usb_redir_success || !s->ep[idx].iso_streaming) {
+ trace_usbredir_server_iso_stream_dropped(hdr->endpoint, data_len);
+ usbredirparser_free_packet_data(s->parser, data);
+ return;
+ }
+
+ ep = usb_ep_get(device, USB_TOKEN_OUT, ep_nr);
+ rp = usbredir_server_pkt_alloc(data_len, idx);
+ rp->type = USBREDIR_SERVER_ISO_STREAM;
+ rp->iso_hdr = *hdr;
+ rp->redir_id = 0;
+
+ usb_packet_setup(&rp->pkt, USB_TOKEN_OUT, ep, 0, s->next_id++,
+ false, false);
+ if (data_len > 0) {
+ memcpy(rp->data, data, data_len);
+ }
+ usb_packet_addbuf(&rp->pkt, rp->data, data_len);
+
+ trace_usbredir_server_iso_stream_packet(hdr->endpoint, data_len);
+ usbredirparser_free_packet_data(s->parser, data);
+ usbredir_server_submit(s, rp);
+}
+
static void usbredir_server_alloc_bulk_streams(void *priv, uint64_t id,
struct usb_redir_alloc_bulk_streams_header *hdr)
{
@@ -1542,7 +1686,8 @@ static void usbredir_server_cancel_data_packet(void
*priv, uint64_t id)
* A streamed request comes from us, not from the host.
* The host has no id for it, so a cancel never matches it.
*/
- if (rp->type == USBREDIR_SERVER_INTR_STREAM) {
+ if (rp->type == USBREDIR_SERVER_INTR_STREAM ||
+ rp->type == USBREDIR_SERVER_ISO_STREAM) {
continue;
}
@@ -1627,6 +1772,11 @@ static void
usbredir_server_stop_transfers(USBRedirServer *s)
timer_del(s->ep[i].intr_stream_timer);
}
+ for (i = 1; i < USBREDIR_SERVER_EP_IN_BASE; i++) {
+ s->ep[i].iso_streaming = false;
+ s->ep[i].iso_status_next_ns = 0;
+ }
+
/*
* No "cancelled" response here. This runs on a bus reset, a detach or
* a closed chardev, and the host has dropped its own queues already.
@@ -1676,6 +1826,9 @@ static void usbredir_server_create_parser(USBRedirServer
*s)
usbredir_server_start_interrupt_receiving;
s->parser->stop_interrupt_receiving_func =
usbredir_server_stop_interrupt_receiving;
+ s->parser->start_iso_stream_func = usbredir_server_start_iso_stream;
+ s->parser->stop_iso_stream_func = usbredir_server_stop_iso_stream;
+ s->parser->iso_packet_func = usbredir_server_iso_packet;
s->parser->alloc_bulk_streams_func = usbredir_server_alloc_bulk_streams;
s->parser->cancel_data_packet_func = usbredir_server_cancel_data_packet;
s->parser->start_bulk_receiving_func =
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 6eb47085d0..642f42c864 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -427,3 +427,8 @@ usbredir_server_intr_stream_start(uint8_t ep, int status)
"start_interrupt_recei
usbredir_server_intr_stream_stop(uint8_t ep) "stop_interrupt_receiving ep
0x%02x"
usbredir_server_intr_stream_park(unsigned ep_nr, int len) "parked IN packet ep
%u len %d"
usbredir_server_intr_stream_complete(unsigned ep_nr, int status, int actual)
"ep %u status %d actual %d"
+usbredir_server_iso_stream_start(uint8_t ep, uint8_t pkts, uint8_t urbs, int
status) "start_iso_stream ep 0x%02x pkts_per_urb %u no_urbs %u -> status %d"
+usbredir_server_iso_stream_stop(uint8_t ep) "stop_iso_stream ep 0x%02x"
+usbredir_server_iso_stream_packet(uint8_t ep, int len) "iso_packet ep 0x%02x
len %d"
+usbredir_server_iso_stream_dropped(uint8_t ep, int len) "iso_packet dropped ep
0x%02x len %d"
+usbredir_server_iso_stream_complete(uint8_t ep, int status, int actual) "iso
ep 0x%02x status %d actual %d"
--
2.43.0
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |