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 {
uint8_t type;
uint8_t halted;
uint8_t iso_started;
AsyncURB *iso_urb;
int iso_urb_idx;
int iso_buffer_used;
int max_packet_size;
};
@ -189,6 +191,21 @@ static void set_halt(USBHostDevice *s, int ep)
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)
{
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;
}
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)
{
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
printf("husb: leaking iso urbs because of discard failure\n");
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)
@ -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;
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);
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);
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);
j = aurb[i].iso_frame_idx;
if (j >= 0 && j < ISO_FRAME_DESC_PER_URB) {
/* Check urb status */
if (aurb[i].urb.status) {
len = urb_status_to_usb_ret(aurb[i].urb.status);
/* Move to the next urb */
aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1;
/* Check frame status */
} else if (aurb[i].urb.iso_frame_desc[j].status) {
len = urb_status_to_usb_ret(aurb[i].urb.iso_frame_desc[j].status);
/* Check the frame fits */
} else if (aurb[i].urb.iso_frame_desc[j].actual_length > p->len) {
printf("husb: error received isoc data is larger then packet\n");
len = USB_RET_NAK;
/* All good copy data over */
if (in) {
/* Check urb status */
if (aurb[i].urb.status) {
len = urb_status_to_usb_ret(aurb[i].urb.status);
/* Move to the next urb */
aurb[i].iso_frame_idx = ISO_FRAME_DESC_PER_URB - 1;
/* Check frame status */
} else if (aurb[i].urb.iso_frame_desc[j].status) {
len = urb_status_to_usb_ret(
aurb[i].urb.iso_frame_desc[j].status);
/* Check the frame fits */
} else if (aurb[i].urb.iso_frame_desc[j].actual_length > p->len) {
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 {
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);
len = p->len;
offset = (j == 0) ? 0 : get_iso_buffer_used(s, p->devep);
/* Check the frame fits */
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++;
if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
i = (i + 1) % ISO_URB_COUNT;
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 */
for (i = 0; i < ISO_URB_COUNT; i++) {
if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]);
if (ret < 0) {
printf("husb error submitting isoc urb %d: %d\n", i, errno);
if (len == 0) {
switch(errno) {
case ETIMEDOUT:
len = USB_RET_NAK;
case EPIPE:
default:
len = USB_RET_STALL;
if (is_iso_started(s, p->devep)) {
/* (Re)-submit all fully consumed / filled urbs */
for (i = 0; i < ISO_URB_COUNT; i++) {
if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]);
if (ret < 0) {
printf("husb error submitting iso urb %d: %d\n", i, errno);
if (!in || len == 0) {
switch(errno) {
case ETIMEDOUT:
len = USB_RET_NAK;
case EPIPE:
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);
}
if (is_isoc(s, p->devep) && p->pid == USB_TOKEN_IN)
return usb_host_handle_iso_data(s, p);
if (is_isoc(s, p->devep)) {
return usb_host_handle_iso_data(s, p, p->pid == USB_TOKEN_IN);
}
aurb = async_alloc();
aurb->hdev = s;
@ -653,19 +714,8 @@ static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
urb->endpoint = ep;
urb->buffer = p->data;
urb->buffer_length = p->len;
if (is_isoc(s, p->devep)) {
/* 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;
urb->type = USBDEVFS_URB_TYPE_BULK;
urb->usercontext = s;
ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);