usb-redir: merge interrupt packets

Interrupt packets (limited by wMaxPacketSize) should be buffered and merged
by algorithm described in USB spec.
(see usb_20.pdf/5.7.3 Interrupt Transfer Packet Size Constraints).

Signed-off-by: Martin Cerveny <M.Cerveny@computer.org>
Message-id: 20190724125859.14624-2-M.Cerveny@computer.org
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Martin Cerveny 2019-07-24 14:58:59 +02:00 committed by Gerd Hoffmann
parent 7b84b90966
commit baeed70508
1 changed files with 48 additions and 21 deletions

View File

@ -819,8 +819,8 @@ static void usbredir_handle_interrupt_in_data(USBRedirDevice *dev,
USBPacket *p, uint8_t ep) USBPacket *p, uint8_t ep)
{ {
/* Input interrupt endpoint, buffered packet input */ /* Input interrupt endpoint, buffered packet input */
struct buf_packet *intp; struct buf_packet *intp, *intp_to_free;
int status, len; int status, len, sum;
if (!dev->endpoint[EP2I(ep)].interrupt_started && if (!dev->endpoint[EP2I(ep)].interrupt_started &&
!dev->endpoint[EP2I(ep)].interrupt_error) { !dev->endpoint[EP2I(ep)].interrupt_error) {
@ -839,9 +839,17 @@ static void usbredir_handle_interrupt_in_data(USBRedirDevice *dev,
dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0;
} }
intp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq); /* check for completed interrupt message (with all fragments) */
sum = 0;
QTAILQ_FOREACH(intp, &dev->endpoint[EP2I(ep)].bufpq, next) {
sum += intp->len;
if (intp->len < dev->endpoint[EP2I(ep)].max_packet_size ||
sum >= p->iov.size)
break;
}
if (intp == NULL) { if (intp == NULL) {
DPRINTF2("interrupt-token-in ep %02X, no intp\n", ep); DPRINTF2("interrupt-token-in ep %02X, no intp, buffered %d\n", ep, sum);
/* Check interrupt_error for stream errors */ /* Check interrupt_error for stream errors */
status = dev->endpoint[EP2I(ep)].interrupt_error; status = dev->endpoint[EP2I(ep)].interrupt_error;
dev->endpoint[EP2I(ep)].interrupt_error = 0; dev->endpoint[EP2I(ep)].interrupt_error = 0;
@ -852,18 +860,42 @@ static void usbredir_handle_interrupt_in_data(USBRedirDevice *dev,
} }
return; return;
} }
DPRINTF("interrupt-token-in ep %02X status %d len %d\n", ep,
intp->status, intp->len);
status = intp->status; /* copy of completed interrupt message */
len = intp->len; sum = 0;
if (len > p->iov.size) { status = usb_redir_success;
ERROR("received int data is larger then packet ep %02X\n", ep); intp_to_free = NULL;
len = p->iov.size; QTAILQ_FOREACH(intp, &dev->endpoint[EP2I(ep)].bufpq, next) {
status = usb_redir_babble; if (intp_to_free) {
bufp_free(dev, intp_to_free, ep);
}
DPRINTF("interrupt-token-in ep %02X fragment status %d len %d\n", ep,
intp->status, intp->len);
sum += intp->len;
len = intp->len;
if (status == usb_redir_success) {
status = intp->status;
}
if (sum > p->iov.size) {
ERROR("received int data is larger then packet ep %02X\n", ep);
len -= (sum - p->iov.size);
sum = p->iov.size;
status = usb_redir_babble;
}
usb_packet_copy(p, intp->data, len);
intp_to_free = intp;
if (intp->len < dev->endpoint[EP2I(ep)].max_packet_size ||
sum >= p->iov.size)
break;
} }
usb_packet_copy(p, intp->data, len); if (intp_to_free) {
bufp_free(dev, intp, ep); bufp_free(dev, intp_to_free, ep);
}
DPRINTF("interrupt-token-in ep %02X summary status %d len %d\n", ep,
status, sum);
usbredir_handle_status(dev, p, status); usbredir_handle_status(dev, p, status);
} }
@ -2041,22 +2073,17 @@ static void usbredir_interrupt_packet(void *priv, uint64_t id,
} }
if (ep & USB_DIR_IN) { if (ep & USB_DIR_IN) {
bool q_was_empty;
if (dev->endpoint[EP2I(ep)].interrupt_started == 0) { if (dev->endpoint[EP2I(ep)].interrupt_started == 0) {
DPRINTF("received int packet while not started ep %02X\n", ep); DPRINTF("received int packet while not started ep %02X\n", ep);
free(data); free(data);
return; return;
} }
q_was_empty = QTAILQ_EMPTY(&dev->endpoint[EP2I(ep)].bufpq);
/* bufp_alloc also adds the packet to the ep queue */ /* bufp_alloc also adds the packet to the ep queue */
bufp_alloc(dev, data, data_len, interrupt_packet->status, ep, data); bufp_alloc(dev, data, data_len, interrupt_packet->status, ep, data);
if (q_was_empty) { /* insufficient data solved with USB_RET_NAK */
usb_wakeup(usb_ep_get(&dev->dev, USB_TOKEN_IN, ep & 0x0f), 0); usb_wakeup(usb_ep_get(&dev->dev, USB_TOKEN_IN, ep & 0x0f), 0);
}
} else { } else {
/* /*
* We report output interrupt packets as completed directly upon * We report output interrupt packets as completed directly upon