usb-linux: Add support for buffering iso out usb packets

Extend the iso buffering code to also buffer iso out packets, this
fixes for example using usb speakers with usb redirection.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
This commit is contained in:
Hans de Goede 2010-11-26 19:11:03 +01:00 committed by Gerd Hoffmann
parent 3a4854b372
commit bb6d5498c6

View File

@ -101,8 +101,10 @@ typedef struct AsyncURB AsyncURB;
struct endp_data { struct endp_data {
uint8_t type; uint8_t type;
uint8_t halted; uint8_t halted;
uint8_t iso_started;
AsyncURB *iso_urb; AsyncURB *iso_urb;
int iso_urb_idx; int iso_urb_idx;
int iso_buffer_used;
int max_packet_size; int max_packet_size;
}; };
@ -189,6 +191,21 @@ static void set_halt(USBHostDevice *s, int ep)
s->endp_table[ep - 1].halted = 1; s->endp_table[ep - 1].halted = 1;
} }
static int is_iso_started(USBHostDevice *s, int ep)
{
return s->endp_table[ep - 1].iso_started;
}
static void clear_iso_started(USBHostDevice *s, int ep)
{
s->endp_table[ep - 1].iso_started = 0;
}
static void set_iso_started(USBHostDevice *s, int ep)
{
s->endp_table[ep - 1].iso_started = 1;
}
static void set_iso_urb(USBHostDevice *s, int ep, AsyncURB *iso_urb) static void set_iso_urb(USBHostDevice *s, int ep, AsyncURB *iso_urb)
{ {
s->endp_table[ep - 1].iso_urb = iso_urb; s->endp_table[ep - 1].iso_urb = iso_urb;
@ -209,6 +226,16 @@ static int get_iso_urb_idx(USBHostDevice *s, int ep)
return s->endp_table[ep - 1].iso_urb_idx; return s->endp_table[ep - 1].iso_urb_idx;
} }
static void set_iso_buffer_used(USBHostDevice *s, int ep, int i)
{
s->endp_table[ep - 1].iso_buffer_used = i;
}
static int get_iso_buffer_used(USBHostDevice *s, int ep)
{
return s->endp_table[ep - 1].iso_buffer_used;
}
static int get_max_packet_size(USBHostDevice *s, int ep) static int get_max_packet_size(USBHostDevice *s, int ep)
{ {
return s->endp_table[ep - 1].max_packet_size; return s->endp_table[ep - 1].max_packet_size;
@ -534,6 +561,8 @@ static void usb_host_stop_n_free_iso(USBHostDevice *s, uint8_t ep)
else else
printf("husb: leaking iso urbs because of discard failure\n"); printf("husb: leaking iso urbs because of discard failure\n");
set_iso_urb(s, ep, NULL); set_iso_urb(s, ep, NULL);
set_iso_urb_idx(s, ep, 0);
clear_iso_started(s, ep);
} }
static int urb_status_to_usb_ret(int status) static int urb_status_to_usb_ret(int status)
@ -546,10 +575,10 @@ static int urb_status_to_usb_ret(int status)
} }
} }
static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p) static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in)
{ {
AsyncURB *aurb; AsyncURB *aurb;
int i, j, ret, max_packet_size, len = 0; int i, j, ret, max_packet_size, offset, len = 0;
max_packet_size = get_max_packet_size(s, p->devep); max_packet_size = get_max_packet_size(s, p->devep);
if (max_packet_size == 0) if (max_packet_size == 0)
@ -557,57 +586,88 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p)
aurb = get_iso_urb(s, p->devep); aurb = get_iso_urb(s, p->devep);
if (!aurb) { if (!aurb) {
aurb = usb_host_alloc_iso(s, p->devep, 1); aurb = usb_host_alloc_iso(s, p->devep, in);
} }
i = get_iso_urb_idx(s, p->devep); i = get_iso_urb_idx(s, p->devep);
j = aurb[i].iso_frame_idx; j = aurb[i].iso_frame_idx;
if (j >= 0 && j < ISO_FRAME_DESC_PER_URB) { if (j >= 0 && j < ISO_FRAME_DESC_PER_URB) {
/* Check urb status */ if (in) {
if (aurb[i].urb.status) { /* Check urb status */
len = urb_status_to_usb_ret(aurb[i].urb.status); if (aurb[i].urb.status) {
/* Move to the next urb */ len = urb_status_to_usb_ret(aurb[i].urb.status);
aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1; /* Move to the next urb */
/* Check frame status */ aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1;
} else if (aurb[i].urb.iso_frame_desc[j].status) { /* Check frame status */
len = urb_status_to_usb_ret(aurb[i].urb.iso_frame_desc[j].status); } else if (aurb[i].urb.iso_frame_desc[j].status) {
/* Check the frame fits */ len = urb_status_to_usb_ret(
} else if (aurb[i].urb.iso_frame_desc[j].actual_length > p->len) { aurb[i].urb.iso_frame_desc[j].status);
printf("husb: error received isoc data is larger then packet\n"); /* Check the frame fits */
len = USB_RET_NAK; } else if (aurb[i].urb.iso_frame_desc[j].actual_length > p->len) {
/* All good copy data over */ printf("husb: received iso data is larger then packet\n");
len = USB_RET_NAK;
/* All good copy data over */
} else {
len = aurb[i].urb.iso_frame_desc[j].actual_length;
memcpy(p->data,
aurb[i].urb.buffer +
j * aurb[i].urb.iso_frame_desc[0].length,
len);
}
} else { } else {
len = aurb[i].urb.iso_frame_desc[j].actual_length; len = p->len;
memcpy(p->data, offset = (j == 0) ? 0 : get_iso_buffer_used(s, p->devep);
aurb[i].urb.buffer +
j * aurb[i].urb.iso_frame_desc[0].length, /* Check the frame fits */
len); if (len > max_packet_size) {
printf("husb: send iso data is larger then max packet size\n");
return USB_RET_NAK;
}
/* All good copy data over */
memcpy(aurb[i].urb.buffer + offset, p->data, len);
aurb[i].urb.iso_frame_desc[j].length = len;
offset += len;
set_iso_buffer_used(s, p->devep, offset);
/* Start the stream once we have buffered enough data */
if (!is_iso_started(s, p->devep) && i == 1 && j == 8) {
set_iso_started(s, p->devep);
}
} }
aurb[i].iso_frame_idx++; aurb[i].iso_frame_idx++;
if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) { if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
i = (i + 1) % ISO_URB_COUNT; i = (i + 1) % ISO_URB_COUNT;
set_iso_urb_idx(s, p->devep, i); set_iso_urb_idx(s, p->devep, i);
} }
} else {
if (in) {
set_iso_started(s, p->devep);
} else {
DPRINTF("hubs: iso out error no free buffer, dropping packet\n");
}
} }
/* (Re)-submit all fully consumed urbs */ if (is_iso_started(s, p->devep)) {
for (i = 0; i < ISO_URB_COUNT; i++) { /* (Re)-submit all fully consumed / filled urbs */
if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) { for (i = 0; i < ISO_URB_COUNT; i++) {
ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]); if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
if (ret < 0) { ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]);
printf("husb error submitting isoc urb %d: %d\n", i, errno); if (ret < 0) {
if (len == 0) { printf("husb error submitting iso urb %d: %d\n", i, errno);
switch(errno) { if (!in || len == 0) {
case ETIMEDOUT: switch(errno) {
len = USB_RET_NAK; case ETIMEDOUT:
case EPIPE: len = USB_RET_NAK;
default: case EPIPE:
len = USB_RET_STALL; default:
len = USB_RET_STALL;
}
} }
break;
} }
break; aurb[i].iso_frame_idx = -1;
} }
aurb[i].iso_frame_idx = -1;
} }
} }
@ -641,8 +701,9 @@ static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
clear_halt(s, p->devep); clear_halt(s, p->devep);
} }
if (is_isoc(s, p->devep) && p->pid == USB_TOKEN_IN) if (is_isoc(s, p->devep)) {
return usb_host_handle_iso_data(s, p); return usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN);
}
aurb = async_alloc(); aurb = async_alloc();
aurb->hdev = s; aurb->hdev = s;
@ -653,19 +714,8 @@ static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
urb->endpoint = ep; urb->endpoint = ep;
urb->buffer = p->data; urb->buffer = p->data;
urb->buffer_length = p->len; urb->buffer_length = p->len;
urb->type = USBDEVFS_URB_TYPE_BULK;
if (is_isoc(s, p->devep)) { urb->usercontext = s;
/* Setup ISOC transfer */
urb->type = USBDEVFS_URB_TYPE_ISO;
urb->flags = USBDEVFS_URB_ISO_ASAP;
urb->number_of_packets = 1;
urb->iso_frame_desc[0].length = p->len;
} else {
/* Setup bulk transfer */
urb->type = USBDEVFS_URB_TYPE_BULK;
}
urb->usercontext = s;
ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb); ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);