Send bulk and interrupt packets to the device and return the result to the host. Each one is a request and an answer, so they reuse the packet tracking and the NAK handling added for control transfers.
A bulk request carries a 32 bit length, so the host can ask us to allocate up to 4 GB. Refuse a request that big. Do not answer it with fewer bytes instead: the host counts the bytes it gets back, and it resets the device when some are missing. Advertise usb_redir_cap_32bits_bulk_length. We already read and write the high half of the length, but without the capability redirect.c asserts when its own guest asks for a transfer larger than 64KiB. Signed-off-by: Jamin Lin <[email protected]> --- hw/usb/redirect-server.c | 240 ++++++++++++++++++++++++++++++++++++++- hw/usb/trace-events | 6 + 2 files changed, 245 insertions(+), 1 deletion(-) diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c index 55a1f79623..9a83cc22ef 100644 --- a/hw/usb/redirect-server.c +++ b/hw/usb/redirect-server.c @@ -90,7 +90,15 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER) */ #define USBREDIR_SERVER_RETRY_MS 1000 +/* + * 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) + #define USBREDIR_SERVER_CTRL 0 +#define USBREDIR_SERVER_BULK 1 +#define USBREDIR_SERVER_INTERRUPT 2 /* Which message answers the host when a control transfer ends. */ typedef enum { @@ -113,7 +121,9 @@ typedef struct USBRedirServerPkt { USBPacket pkt; /* Kept for the usbredir response. */ + struct usb_redir_interrupt_packet_header intr_hdr; struct usb_redir_control_packet_header ctrl_hdr; + struct usb_redir_bulk_packet_header bulk_hdr; /* * The IOV points here. IN data lands in it, OUT data is copied in. @@ -549,6 +559,67 @@ static void usbredir_server_ctrl_complete(USBRedirServer *s, } } +static void usbredir_server_bulk_reply(USBRedirServer *s, uint64_t id, + const struct usb_redir_bulk_packet_header *hdr, uint8_t status, + uint8_t *data, int actual) +{ + struct usb_redir_bulk_packet_header resp = *hdr; + bool is_in = !!(hdr->endpoint & USB_DIR_IN); + int data_len = is_in ? actual : 0; + + resp.status = status; + /* Both directions report how much the device moved. */ + resp.length = actual; + resp.length_high = actual >> 16; + + usbredirparser_send_bulk_packet(s->parser, id, &resp, + data_len > 0 ? data : NULL, data_len); + usbredirparser_do_write(s->parser); +} + +static void usbredir_server_bulk_complete(USBRedirServer *s, + USBRedirServerPkt *rp) +{ + uint8_t status = usbredir_server_status(rp->pkt.status); + int actual = rp->pkt.actual_length; + + trace_usbredir_server_bulk_complete(rp->redir_id, rp->bulk_hdr.endpoint, + status, actual); + usbredir_server_bulk_reply(s, rp->redir_id, &rp->bulk_hdr, status, + rp->data, actual); +} + +static void usbredir_server_interrupt_reply(USBRedirServer *s, uint64_t id, + const struct usb_redir_interrupt_packet_header *hdr, uint8_t status, + uint8_t *data, int actual) +{ + struct usb_redir_interrupt_packet_header resp = *hdr; + bool is_in = !!(hdr->endpoint & USB_DIR_IN); + int data_len = is_in ? actual : 0; + + resp.status = status; + /* Both directions report how much the device moved. */ + resp.length = actual; + + usbredirparser_send_interrupt_packet(s->parser, id, &resp, + data_len > 0 ? data : NULL, + data_len); + usbredirparser_do_write(s->parser); +} + +static void usbredir_server_interrupt_complete(USBRedirServer *s, + USBRedirServerPkt *rp) +{ + uint8_t status = usbredir_server_status(rp->pkt.status); + int actual = rp->pkt.actual_length; + + trace_usbredir_server_interrupt_complete(rp->redir_id, + rp->intr_hdr.endpoint, + status, actual); + usbredir_server_interrupt_reply(s, rp->redir_id, &rp->intr_hdr, status, + rp->data, actual); +} + /* * USB port ops */ @@ -622,6 +693,12 @@ static void usbredir_server_packet_complete(USBPort *port, USBPacket *p) case USBREDIR_SERVER_CTRL: usbredir_server_ctrl_complete(s, rp); break; + case USBREDIR_SERVER_BULK: + usbredir_server_bulk_complete(s, rp); + break; + case USBREDIR_SERVER_INTERRUPT: + usbredir_server_interrupt_complete(s, rp); + break; default: g_assert_not_reached(); } @@ -1033,6 +1110,121 @@ static void usbredir_server_get_alt_setting(void *priv, uint64_t id, USBREDIR_SERVER_REPLY_ALT); } +static void usbredir_server_bulk_packet(void *priv, uint64_t id, + struct usb_redir_bulk_packet_header *hdr, + uint8_t *data, int data_len) +{ + USBRedirServer *s = priv; + int idx = usbredir_server_ep_index(hdr->endpoint); + USBDevice *device = usbredir_server_device(s); + bool is_in = !!(hdr->endpoint & USB_DIR_IN); + int pid = is_in ? USB_TOKEN_IN : USB_TOKEN_OUT; + int ep_nr = hdr->endpoint & 0x0f; + USBRedirServerPkt *rp; + USBEndpoint *ep; + uint8_t status; + uint32_t len; + + /* The host still waits for an answer, even to a request we refuse. */ + status = usbredir_server_ep_status(s, hdr->endpoint, + USB_ENDPOINT_XFER_BULK); + if (status != usb_redir_success) { + trace_usbredir_server_refused(id, hdr->endpoint, status, + USB_ENDPOINT_XFER_BULK, + s->ep[idx].type); + usbredir_server_bulk_reply(s, id, hdr, status, NULL, 0); + usbredirparser_free_packet_data(s->parser, data); + return; + } + + /* IN: what the host asked for. OUT: what the host sent. */ + len = is_in ? (((uint32_t)hdr->length_high << 16) | hdr->length) + : (uint32_t)data_len; + + /* + * Too big. Tell the host the transfer failed. + * Do not send back less data instead. The host would see the + * missing bytes as an error and reset the device. + */ + if (len > USBREDIR_SERVER_MAX_BULK) { + trace_usbredir_server_bulk_too_big(id, hdr->endpoint, len); + usbredir_server_bulk_reply(s, id, hdr, usb_redir_inval, NULL, 0); + usbredirparser_free_packet_data(s->parser, data); + return; + } + + ep = usb_ep_get(device, pid, ep_nr); + rp = usbredir_server_pkt_alloc(len, idx); + rp->redir_id = id; + rp->type = USBREDIR_SERVER_BULK; + rp->bulk_hdr = *hdr; + + usb_packet_setup(&rp->pkt, pid, ep, 0, s->next_id++, false, false); + + /* OUT data comes from the host. IN data is written by the device. */ + if (!is_in && len > 0) { + memcpy(rp->data, data, len); + } + usb_packet_addbuf(&rp->pkt, rp->data, len); + + trace_usbredir_server_bulk(id, hdr->endpoint, len); + usbredirparser_free_packet_data(s->parser, data); + usbredir_server_submit(s, rp); +} + +/* + * The host sends an interrupt_packet. It is a request: interrupt OUT + * data to write, or a single IN request to answer. + */ +static void usbredir_server_interrupt_packet(void *priv, uint64_t id, + struct usb_redir_interrupt_packet_header *hdr, + uint8_t *data, int data_len) +{ + USBRedirServer *s = priv; + int idx = usbredir_server_ep_index(hdr->endpoint); + USBDevice *device = usbredir_server_device(s); + bool is_in = !!(hdr->endpoint & USB_DIR_IN); + int pid = is_in ? USB_TOKEN_IN : USB_TOKEN_OUT; + int ep_nr = hdr->endpoint & 0x0f; + USBRedirServerPkt *rp; + USBEndpoint *ep; + uint8_t status; + int len; + + /* The host still waits for an answer, even to a request we refuse. */ + status = usbredir_server_ep_status(s, hdr->endpoint, + USB_ENDPOINT_XFER_INT); + if (status != usb_redir_success) { + trace_usbredir_server_refused(id, hdr->endpoint, status, + USB_ENDPOINT_XFER_INT, + s->ep[idx].type); + usbredir_server_interrupt_reply(s, id, hdr, status, NULL, 0); + usbredirparser_free_packet_data(s->parser, data); + return; + } + + /* IN: what the host asked for. OUT: what the host sent. */ + len = is_in ? hdr->length : data_len; + + ep = usb_ep_get(device, pid, ep_nr); + rp = usbredir_server_pkt_alloc(len, idx); + rp->redir_id = id; + rp->type = USBREDIR_SERVER_INTERRUPT; + rp->intr_hdr = *hdr; + + usb_packet_setup(&rp->pkt, pid, ep, 0, s->next_id++, false, false); + + /* OUT data comes from the host. IN data is written by the device. */ + if (!is_in && len > 0) { + memcpy(rp->data, data, len); + } + usb_packet_addbuf(&rp->pkt, rp->data, len); + + trace_usbredir_server_interrupt(id, hdr->endpoint, len); + usbredirparser_free_packet_data(s->parser, data); + usbredir_server_submit(s, rp); +} + static void usbredir_server_filter_reject(void *priv) { trace_usbredir_server_filter_reject(); @@ -1056,6 +1248,24 @@ static void usbredir_server_interface_info(void *priv, /* The host should not send this to a device. Nothing to do. */ } +static void usbredir_server_alloc_bulk_streams(void *priv, uint64_t id, + struct usb_redir_alloc_bulk_streams_header *hdr) +{ + /* We do not advertise bulk streams. Nothing to do. */ +} + +static void usbredir_server_start_bulk_receiving(void *priv, uint64_t id, + struct usb_redir_start_bulk_receiving_header *hdr) +{ + /* We do not advertise this. Nothing to do. */ +} + +static void usbredir_server_stop_bulk_receiving(void *priv, uint64_t id, + struct usb_redir_stop_bulk_receiving_header *hdr) +{ + /* We do not advertise this. Nothing to do. */ +} + static void usbredir_server_cancel_data_packet(void *priv, uint64_t id) { USBRedirServer *s = priv; @@ -1096,7 +1306,9 @@ static void usbredir_server_cancel_data_packet(void *priv, uint64_t id) static void usbredir_server_send_cancelled(USBRedirServer *s, USBRedirServerPkt *rp) { + struct usb_redir_interrupt_packet_header intr; struct usb_redir_control_packet_header ctrl; + struct usb_redir_bulk_packet_header bulk; switch (rp->type) { case USBREDIR_SERVER_CTRL: @@ -1106,6 +1318,21 @@ static void usbredir_server_send_cancelled(USBRedirServer *s, usbredirparser_send_control_packet(s->parser, rp->redir_id, &ctrl, NULL, 0); break; + case USBREDIR_SERVER_BULK: + bulk = rp->bulk_hdr; + bulk.status = usb_redir_cancelled; + bulk.length_high = 0; + bulk.length = 0; + usbredirparser_send_bulk_packet(s->parser, rp->redir_id, + &bulk, NULL, 0); + break; + case USBREDIR_SERVER_INTERRUPT: + intr = rp->intr_hdr; + intr.status = usb_redir_cancelled; + intr.length = 0; + usbredirparser_send_interrupt_packet(s->parser, rp->redir_id, + &intr, NULL, 0); + break; default: return; } @@ -1153,6 +1380,8 @@ static void usbredir_server_create_parser(USBRedirServer *s) s->parser->hello_func = usbredir_server_hello; s->parser->reset_func = usbredir_server_reset; s->parser->control_packet_func = usbredir_server_control_packet; + s->parser->bulk_packet_func = usbredir_server_bulk_packet; + s->parser->interrupt_packet_func = usbredir_server_interrupt_packet; s->parser->set_configuration_func = usbredir_server_set_configuration; /* The parser calls these directly, so they must not be NULL. */ @@ -1164,12 +1393,21 @@ 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->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 = + usbredir_server_start_bulk_receiving; + s->parser->stop_bulk_receiving_func = + usbredir_server_stop_bulk_receiving; - /* Capabilities: 64-bit IDs, connect_device_version, ep_info sizes */ + /* + * Capabilities: 64-bit IDs, connect_device_version, ep_info sizes + * and 32-bit bulk lengths. + */ usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); + usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); /* * In USB the host is the side that starts every transfer; a device diff --git a/hw/usb/trace-events b/hw/usb/trace-events index d63156a930..5ec5127efa 100644 --- a/hw/usb/trace-events +++ b/hw/usb/trace-events @@ -417,3 +417,9 @@ usbredir_server_nak_park(uint64_t id, int ep) "id %" PRIu64 " ep %d" usbredir_server_nak_retry(uint64_t id, int ep) "id %" PRIu64 " ep %d" usbredir_server_ctrl_complete(uint64_t id, int status, int actual) "id %" PRIu64 " status %d actual %d" usbredir_server_ctrl_refused(uint64_t id, int status) "id %" PRIu64 " status %d" +usbredir_server_bulk(uint64_t id, uint8_t ep, size_t len) "id %" PRIu64 " ep 0x%02x len %zu" +usbredir_server_bulk_complete(uint64_t id, uint8_t ep, int status, int actual) "id %" PRIu64 " ep 0x%02x status %d actual %d" +usbredir_server_bulk_too_big(uint64_t id, uint8_t ep, uint32_t len) "id %" PRIu64 " ep 0x%02x len %u" +usbredir_server_refused(uint64_t id, uint8_t ep, int status, uint8_t want, uint8_t got) "id %" PRIu64 " ep 0x%02x status %d, wanted type %u, it is %u" +usbredir_server_interrupt(uint64_t id, uint8_t ep, size_t len) "id %" PRIu64 " ep 0x%02x len %zu" +usbredir_server_interrupt_complete(uint64_t id, uint8_t ep, int status, int actual) "id %" PRIu64 " ep 0x%02x status %d actual %d" -- 2.43.0
