Interrupt IN is not a request and an answer in usbredir. The host asks
once with start_interrupt_receiving. After that it expects a packet
every time the device has data.

Park one IN packet on the device for each streaming endpoint. A device
that answers USB_RET_ASYNC holds the packet and completes it when it
has data. A device that answers USB_RET_NAK gives the packet back at
once, so park a new one when the bus reports a wakeup on that endpoint.
A slow timer parks one too, for a device that does not report a wakeup.

Signed-off-by: Jamin Lin <[email protected]>
---
 include/hw/usb/redirect-server.h |  15 ++
 hw/usb/redirect-server.c         | 248 +++++++++++++++++++++++++++++++
 hw/usb/trace-events              |   4 +
 3 files changed, 267 insertions(+)

diff --git a/include/hw/usb/redirect-server.h b/include/hw/usb/redirect-server.h
index f7b2261db4..1fb2a8a7a4 100644
--- a/include/hw/usb/redirect-server.h
+++ b/include/hw/usb/redirect-server.h
@@ -28,16 +28,23 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER)
 #define USBREDIR_SERVER_MAX_EP 32
 #define USBREDIR_SERVER_EP_IN_BASE 16
 
+/* An endpoint number is 4 bits, so 0 to 15. */
+#define USBREDIR_SERVER_MAX_EP_NR 16
+
 /*
  * The bulk length field is 32 bits, so the host can ask for up to 4 GB.
  * This is the largest transfer accepted.
  */
 #define USBREDIR_SERVER_MAX_BULK (1 * MiB)
 
+/* Buffer size for an interrupt IN endpoint before its descriptor is seen. */
+#define USBREDIR_SERVER_INTR_DEFAULT_LEN 64
+
 #define USBREDIR_SERVER_CTRL_SETUP 0
 #define USBREDIR_SERVER_CTRL_STATUS 1
 #define USBREDIR_SERVER_BULK 2
 #define USBREDIR_SERVER_INTR 3
+#define USBREDIR_SERVER_INTR_STREAM 4
 
 /* Which message answers the host when a control transfer ends. */
 typedef enum {
@@ -87,6 +94,14 @@ struct USBRedirServer {
     bool host_connected;
     bool device_announced;
 
+    /*
+     * Interrupt IN streaming, indexed by endpoint number. intr_bh asks the
+     * device again; intr_retry does the same after a delay on NAK.
+     */
+    bool intr_in_started[USBREDIR_SERVER_MAX_EP_NR];
+    QEMUBH *intr_bh;
+    QEMUTimer *intr_retry;
+
     /* In-flight packet tracking */
     QTAILQ_HEAD(, USBRedirServerPkt) inflight;
     uint64_t next_id;
diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index a733a007e3..f84ece02e4 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -69,6 +69,13 @@
 /* Wait this long after attach before we announce the device. */
 #define USBREDIR_SERVER_ANNOUNCE_DEBOUNCE_MS 10
 
+/*
+ * Ask again this often after an endpoint answered NAK. Most devices wake
+ * the bus when data arrives, and then the next ask happens at once. This
+ * timer is for the devices that do not wake the bus.
+ */
+#define USBREDIR_SERVER_INTR_RETRY_MS 1000
+
 static void usbredir_server_pkt_free(USBRedirServerPkt *rp);
 static void usbredir_server_stop_transfers(USBRedirServer *s);
 static void usbredir_server_send_cancelled(USBRedirServer *s,
@@ -402,6 +409,63 @@ static void usbredir_server_intr_complete(USBRedirServer 
*s,
     usbredirparser_do_write(s->parser);
 }
 
+/*
+ * The device answered a parked request. Send the answer to the host, then
+ * ask again, because the host still wants more. Do not ask from here: the
+ * device may answer at once, and this function would call itself over and
+ * over. Let the BH ask, or the retry timer after a NAK.
+ */
+static void usbredir_server_intr_stream_complete(USBRedirServer *s,
+                                                 USBRedirServerPkt *rp)
+{
+    struct usb_redir_interrupt_packet_header resp = rp->intr_hdr;
+    struct usb_redir_interrupt_receiving_status_header st;
+    int ep_nr = resp.endpoint & 0x0f;
+    USBPacket *p = &rp->pkt;
+    int actual = p->actual_length;
+    bool retry = false;
+
+    trace_usbredir_server_intr_stream_complete(ep_nr, p->status, actual);
+
+    switch (p->status) {
+    case USB_RET_SUCCESS:
+        resp.status = usb_redir_success;
+        resp.length = actual;
+        usbredirparser_send_interrupt_packet(s->parser, 0, &resp,
+                                             actual ? rp->data : NULL,
+                                             actual);
+        usbredirparser_do_write(s->parser);
+        break;
+    case USB_RET_NAK:
+        /* nothing to report yet; ask again shortly */
+        retry = true;
+        break;
+    default:
+        /*
+         * The endpoint stalled or failed. There is no data to send, so
+         * send a status message. The host keeps that status and gives it
+         * to its guest. On a stall the host also ends the stream, so end
+         * it here too. A later request from the guest starts a new one.
+         */
+        st.endpoint = resp.endpoint;
+        st.status = usbredir_server_status(p->status);
+        usbredirparser_send_interrupt_receiving_status(s->parser, 0, &st);
+        usbredirparser_do_write(s->parser);
+        s->intr_in_started[ep_nr] = false;
+        break;
+    }
+
+    if (s->intr_in_started[ep_nr]) {
+        if (retry) {
+            timer_mod(s->intr_retry,
+                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+                      (int64_t)USBREDIR_SERVER_INTR_RETRY_MS * SCALE_MS);
+        } else {
+            qemu_bh_schedule(s->intr_bh);
+        }
+    }
+}
+
 /*
  * USB port ops
  */
@@ -504,6 +568,9 @@ static void usbredir_server_packet_complete(USBPort *port, 
USBPacket *p)
     case USBREDIR_SERVER_INTR:
         usbredir_server_intr_complete(s, rp);
         break;
+    case USBREDIR_SERVER_INTR_STREAM:
+        usbredir_server_intr_stream_complete(s, rp);
+        break;
     }
 
     usbredir_server_pkt_free(rp);
@@ -521,7 +588,25 @@ static USBPortOps usbredir_server_port_ops = {
  * USB bus ops
  */
 
+static void usbredir_server_wakeup_ep(USBBus *bus, USBEndpoint *ep,
+                                      unsigned int stream)
+{
+    USBRedirServer *s = container_of(bus, USBRedirServer, bus);
+    int ep_nr = ep->nr;
+
+    /*
+     * The device has data. A streaming interrupt IN endpoint may have
+     * answered NAK. Ask now instead of waiting for the retry timer.
+     * Every other transfer already has its request on the device.
+     */
+    if (ep->pid == USB_TOKEN_IN && ep_nr < USBREDIR_SERVER_MAX_EP_NR &&
+        s->intr_in_started[ep_nr]) {
+        qemu_bh_schedule(s->intr_bh);
+    }
+}
+
 static USBBusOps usbredir_server_bus_ops = {
+    .wakeup_endpoint = usbredir_server_wakeup_ep,
 };
 
 /*
@@ -572,6 +657,91 @@ static void usbredir_server_drop_pkt(USBRedirServer *s,
     usbredir_server_pkt_free(rp);
 }
 
+/*
+ * Interrupt IN streaming
+ */
+
+static bool usbredir_server_intr_inflight(USBRedirServer *s, int ep_nr)
+{
+    USBRedirServerPkt *rp;
+
+    QTAILQ_FOREACH(rp, &s->inflight, next) {
+        if (rp->type == USBREDIR_SERVER_INTR_STREAM &&
+            (rp->intr_hdr.endpoint & 0x0f) == ep_nr) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * Ask the device for data on @ep_nr and leave the request waiting. A device
+ * sends data only when asked. Do nothing if a request on @ep_nr is still
+ * unanswered.
+ */
+static void usbredir_server_intr_park(USBRedirServer *s, int ep_nr)
+{
+    USBDevice *device = usbredir_server_device(s);
+    USBRedirServerPkt *rp;
+    USBEndpoint *ep;
+    int len;
+
+    /* No device to ask. */
+    if (!device || !device->attached) {
+        return;
+    }
+
+    /* The host did not ask for this endpoint, or a request is unanswered. */
+    if (!s->intr_in_started[ep_nr] ||
+        usbredir_server_intr_inflight(s, ep_nr)) {
+        return;
+    }
+
+    len = s->ep_max_packet[ep_nr + USBREDIR_SERVER_EP_IN_BASE];
+    if (len == 0) {
+        len = USBREDIR_SERVER_INTR_DEFAULT_LEN;
+    }
+    ep = usb_ep_get(device, USB_TOKEN_IN, ep_nr);
+    rp = usbredir_server_pkt_alloc(len);
+    rp->type = USBREDIR_SERVER_INTR_STREAM;
+    /* streamed data carries no host id */
+    rp->redir_id = 0;
+    rp->intr_hdr.endpoint = ep_nr | USB_DIR_IN;
+
+    usb_packet_setup(&rp->pkt, USB_TOKEN_IN, ep, 0, s->next_id++,
+                     false, false);
+    usb_packet_addbuf(&rp->pkt, rp->data, len);
+
+    trace_usbredir_server_intr_park(ep_nr, len);
+    usbredir_server_submit_to_device(s, rp);
+}
+
+/* BH and timer callback: ask again on every streaming endpoint. */
+static void usbredir_server_intr_kick(void *opaque)
+{
+    USBRedirServer *s = opaque;
+    int i;
+
+    /* Endpoint 0 is the control endpoint. It never streams. */
+    for (i = 1; i < USBREDIR_SERVER_MAX_EP_NR; i++) {
+        usbredir_server_intr_park(s, i);
+    }
+}
+
+static void usbredir_server_intr_cancel(USBRedirServer *s, int ep_nr)
+{
+    USBRedirServerPkt *tmp;
+    USBRedirServerPkt *rp;
+
+    QTAILQ_FOREACH_SAFE(rp, &s->inflight, next, tmp) {
+        if (rp->type != USBREDIR_SERVER_INTR_STREAM ||
+            (rp->intr_hdr.endpoint & 0x0f) != ep_nr) {
+            continue;
+        }
+        usbredir_server_drop_pkt(s, rp);
+    }
+}
+
 /*
  * usbredirparser I/O and logging callbacks
  */
@@ -1002,6 +1172,58 @@ static void usbredir_server_interface_info(void *priv,
     /* The host should not send this to a device. Nothing to do. */
 }
 
+/*
+ * The host asks to stream an interrupt IN endpoint. Send the receiving
+ * status first. Without that status the host throws the interrupt
+ * packet away. Then ask the device once.
+ */
+static void usbredir_server_start_interrupt_receiving(void *priv,
+    uint64_t id, struct usb_redir_start_interrupt_receiving_header *hdr)
+{
+    struct usb_redir_interrupt_receiving_status_header st = {
+        .endpoint = hdr->endpoint,
+        .status = usb_redir_success,
+    };
+    USBRedirServer *s = priv;
+    USBDevice *device = usbredir_server_device(s);
+    int ep_nr = hdr->endpoint & 0x0f;
+
+    /*
+     * Endpoint 0 is control and an OUT endpoint never streams. There also
+     * has to be a host to send to and a device to ask.
+     */
+    if (ep_nr == 0 || !(hdr->endpoint & USB_DIR_IN) ||
+        !s->host_connected || !device || !device->attached) {
+        st.status = usb_redir_ioerror;
+    } else {
+        s->intr_in_started[ep_nr] = true;
+    }
+
+    trace_usbredir_server_intr_start(hdr->endpoint, st.status);
+    usbredirparser_send_interrupt_receiving_status(s->parser, id, &st);
+    usbredirparser_do_write(s->parser);
+
+    if (st.status == usb_redir_success) {
+        usbredir_server_intr_park(s, ep_nr);
+    }
+}
+
+static void usbredir_server_stop_interrupt_receiving(void *priv, uint64_t id,
+    struct usb_redir_stop_interrupt_receiving_header *hdr)
+{
+    int ep_nr = hdr->endpoint & 0x0f;
+    USBRedirServer *s = priv;
+
+    /* Endpoint 0 is control, and an OUT endpoint never streams. */
+    if (ep_nr == 0 || !(hdr->endpoint & USB_DIR_IN)) {
+        return;
+    }
+
+    trace_usbredir_server_intr_stop(hdr->endpoint);
+    s->intr_in_started[ep_nr] = false;
+    usbredir_server_intr_cancel(s, ep_nr);
+}
+
 static void usbredir_server_alloc_bulk_streams(void *priv, uint64_t id,
     struct usb_redir_alloc_bulk_streams_header *hdr)
 {
@@ -1106,6 +1328,14 @@ static void 
usbredir_server_stop_transfers(USBRedirServer *s)
 {
     USBRedirServerPkt *rp;
 
+    memset(s->intr_in_started, 0, sizeof(s->intr_in_started));
+    if (s->intr_bh) {
+        qemu_bh_cancel(s->intr_bh);
+    }
+    if (s->intr_retry) {
+        timer_del(s->intr_retry);
+    }
+
     /*
      * 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.
@@ -1151,6 +1381,10 @@ static void usbredir_server_create_parser(USBRedirServer 
*s)
     s->parser->device_disconnect_ack_func =
         usbredir_server_device_disconnect_ack;
     s->parser->interface_info_func = usbredir_server_interface_info;
+    s->parser->start_interrupt_receiving_func =
+        usbredir_server_start_interrupt_receiving;
+    s->parser->stop_interrupt_receiving_func =
+        usbredir_server_stop_interrupt_receiving;
     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 =
@@ -1292,6 +1526,10 @@ static void usbredir_server_realize(DeviceState *dev, 
Error **errp)
 
     s->announce_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
                                      usbredir_server_do_announce, s);
+    s->intr_retry = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                                 usbredir_server_intr_kick, s);
+    s->intr_bh = qemu_bh_new_guarded(usbredir_server_intr_kick, s,
+                                     &dev->mem_reentrancy_guard);
     s->chardev_close_bh = qemu_bh_new_guarded(usbredir_server_chardev_close_bh,
                                               s, &dev->mem_reentrancy_guard);
 
@@ -1311,6 +1549,16 @@ static void usbredir_server_unrealize(DeviceState *dev)
 
     timer_free(s->announce_timer);
 
+    if (s->intr_retry) {
+        timer_free(s->intr_retry);
+        s->intr_retry = NULL;
+    }
+
+    if (s->intr_bh) {
+        qemu_bh_delete(s->intr_bh);
+        s->intr_bh = NULL;
+    }
+
     if (s->chardev_close_bh) {
         qemu_bh_delete(s->chardev_close_bh);
         s->chardev_close_bh = NULL;
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index c9ab80c6ba..931996e8cd 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -416,3 +416,7 @@ usbredir_server_bulk_complete(uint64_t id, uint8_t ep, int 
status, int actual) "
 usbredir_server_bulk_too_big(uint64_t id, uint8_t ep, uint32_t len) "id %" 
PRIu64 " ep 0x%02x len %u"
 usbredir_server_interrupt(uint64_t id, uint8_t ep, size_t len) "id %" PRIu64 " 
ep 0x%02x len %zu"
 usbredir_server_intr_complete(uint64_t id, uint8_t ep, int status, int actual) 
"id %" PRIu64 " ep 0x%02x status %d actual %d"
+usbredir_server_intr_start(uint8_t ep, int status) "start_interrupt_receiving 
ep 0x%02x -> status %d"
+usbredir_server_intr_stop(uint8_t ep) "stop_interrupt_receiving ep 0x%02x"
+usbredir_server_intr_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"
-- 
2.53.0

Reply via email to