husb: Make control transactions asynchronous (Max Krasnyansky)

USB is 99.8% async now :). 0.2% is the three control requests that
we need to execute synchronously. We could off-load that to a thread
or something but it's not worth the pain since those requests are
performed only during device initialization (ie when device is
connected to the VM).

The change is a bit bigger than I wanted due to the fact that generic
handle_packet()/handle_control() interface was not designed for
async transactions. So I ended up adding custom handle_packet()
code to usb-linux. We can make that generic if/when some other
component needs it.

Signed-off-by: Max Krasnyansky <maxk@kernel.org>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>



git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5204 c046a42c-6fe2-441c-8c8c-71466251a162
master
aliguori 2008-09-14 01:06:09 +00:00
parent 9d0efc88e4
commit 446ab1284e
1 changed files with 371 additions and 93 deletions

View File

@ -25,28 +25,20 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu-common.h"
#include "qemu-timer.h"
#include "hw/usb.h"
#include "console.h"
#if defined(__linux__)
#include <dirent.h>
#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>
#include <linux/version.h>
#include <signal.h>
/* We redefine it to avoid version problems */
struct usb_ctrltransfer {
uint8_t bRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
uint32_t timeout;
void *data;
};
#include <linux/usb/ch9.h>
#include <linux/usbdevice_fs.h>
#include <linux/version.h>
#include "hw/usb.h"
typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
int vendor_id, int product_id,
@ -54,7 +46,6 @@ typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
static int usb_host_find_device(int *pbus_num, int *paddr,
char *product_name, int product_name_size,
const char *devname);
//#define DEBUG
#ifdef DEBUG
@ -67,14 +58,32 @@ static int usb_host_find_device(int *pbus_num, int *paddr,
#define PRODUCT_NAME_SZ 32
#define MAX_ENDPOINTS 16
struct sigaction sigact;
/* endpoint association data */
struct endp_data {
uint8_t type;
uint8_t halted;
};
enum {
CTRL_STATE_IDLE = 0,
CTRL_STATE_SETUP,
CTRL_STATE_DATA,
CTRL_STATE_ACK
};
/*
* Control transfer state.
* Note that 'buffer' _must_ follow 'req' field because
* we need contigious buffer when we submit control URB.
*/
struct ctrl_struct {
uint16_t len;
uint16_t offset;
uint8_t state;
struct usb_ctrlrequest req;
uint8_t buffer[1024];
};
typedef struct USBHostDevice {
USBDevice dev;
int fd;
@ -82,8 +91,10 @@ typedef struct USBHostDevice {
uint8_t descr[1024];
int descr_len;
int configuration;
int ninterfaces;
int closing;
struct ctrl_struct ctrl;
struct endp_data endp_table[MAX_ENDPOINTS];
/* Host side address */
@ -172,6 +183,26 @@ static void async_free(AsyncURB *aurb)
qemu_free(aurb);
}
static void async_complete_ctrl(USBHostDevice *s, USBPacket *p)
{
switch(s->ctrl.state) {
case CTRL_STATE_SETUP:
if (p->len < s->ctrl.len)
s->ctrl.len = p->len;
s->ctrl.state = CTRL_STATE_DATA;
p->len = 8;
break;
case CTRL_STATE_ACK:
s->ctrl.state = CTRL_STATE_IDLE;
p->len = 0;
break;
default:
break;
}
}
static void async_complete(void *opaque)
{
USBHostDevice *s = opaque;
@ -204,6 +235,8 @@ static void async_complete(void *opaque)
switch (aurb->urb.status) {
case 0:
p->len = aurb->urb.actual_length;
if (aurb->urb.type == USBDEVFS_URB_TYPE_CONTROL)
async_complete_ctrl(s, p);
break;
case -EPIPE:
@ -237,7 +270,7 @@ static void async_cancel(USBPacket *unused, void *opaque)
}
}
static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
static int usb_host_claim_interfaces(USBHostDevice *dev, int configuration)
{
int dev_descr_len, config_descr_len;
int interface, nb_interfaces, nb_configurations;
@ -246,6 +279,8 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
if (configuration == 0) /* address state - ignore */
return 1;
dprintf("husb: claiming interfaces. config %d\n", configuration);
i = 0;
dev_descr_len = dev->descr[0];
if (dev_descr_len > dev->descr_len)
@ -265,8 +300,10 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
printf("husb: config #%d need %d\n", dev->descr[i + 5], configuration);
if (configuration < 0 || configuration == dev->descr[i + 5])
if (configuration < 0 || configuration == dev->descr[i + 5]) {
configuration = dev->descr[i + 5];
break;
}
i += config_descr_len;
}
@ -310,17 +347,37 @@ static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
printf("husb: %d interfaces claimed for configuration %d\n",
nb_interfaces, configuration);
dev->ninterfaces = nb_interfaces;
dev->configuration = configuration;
return 1;
}
static int usb_host_release_interfaces(USBHostDevice *s)
{
int ret, i;
dprintf("husb: releasing interfaces\n");
for (i = 0; i < s->ninterfaces; i++) {
ret = ioctl(s->fd, USBDEVFS_RELEASEINTERFACE, &i);
if (ret < 0) {
perror("husb: failed to release interface");
return 0;
}
}
return 1;
}
static void usb_host_handle_reset(USBDevice *dev)
{
USBHostDevice *s = (USBHostDevice *)dev;
USBHostDevice *s = (USBHostDevice *) dev;
dprintf("husb: reset device %u.%u\n", s->bus_num, s->addr);
ioctl(s->fd, USBDEVFS_RESET);
usb_host_update_interfaces(s, s->configuration);
usb_host_claim_interfaces(s, s->configuration);
}
static void usb_host_handle_destroy(USBDevice *dev)
@ -343,73 +400,10 @@ static void usb_host_handle_destroy(USBDevice *dev)
static int usb_linux_update_endp_table(USBHostDevice *s);
static int usb_host_handle_control(USBDevice *dev,
int request,
int value,
int index,
int length,
uint8_t *data)
static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
{
USBHostDevice *s = (USBHostDevice *)dev;
struct usb_ctrltransfer ct;
struct usbdevfs_setinterface si;
int intf_update_required = 0;
int ret;
if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) {
/* specific SET_ADDRESS support */
dev->addr = value;
return 0;
} else if (request == ((USB_RECIP_INTERFACE << 8) |
USB_REQ_SET_INTERFACE)) {
/* set alternate setting for the interface */
si.interface = index;
si.altsetting = value;
ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
usb_linux_update_endp_table(s);
} else if (request == (DeviceOutRequest | USB_REQ_SET_CONFIGURATION)) {
dprintf("husb: ctrl set config %d\n", value & 0xff);
if (s->configuration != (value & 0xff)) {
s->configuration = (value & 0xff);
intf_update_required = 1;
}
goto do_request;
} else {
do_request:
ct.bRequestType = request >> 8;
ct.bRequest = request;
ct.wValue = value;
ct.wIndex = index;
ct.wLength = length;
ct.timeout = 50;
ct.data = data;
ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
dprintf("husb: ctrl req 0x%x val 0x%x index %u len %u ret %d\n",
ct.bRequest, ct.wValue, ct.wIndex, ct.wLength, ret);
}
if (ret < 0) {
switch(errno) {
case ETIMEDOUT:
return USB_RET_NAK;
default:
return USB_RET_STALL;
}
} else {
if (intf_update_required) {
dprintf("husb: updating interfaces\n");
usb_host_update_interfaces(s, value & 0xff);
}
return ret;
}
}
static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
{
USBHostDevice *s = (USBHostDevice *) dev;
AsyncURB *aurb;
struct usbdevfs_urb *urb;
AsyncURB *aurb;
int ret;
aurb = async_alloc();
@ -474,12 +468,292 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
return USB_RET_ASYNC;
}
static int ctrl_error(void)
{
if (errno == ETIMEDOUT)
return USB_RET_NAK;
else
return USB_RET_STALL;
}
static int usb_host_set_address(USBHostDevice *s, int addr)
{
dprintf("husb: ctrl set addr %u\n", addr);
s->dev.addr = addr;
return 0;
}
static int usb_host_set_config(USBHostDevice *s, int config)
{
usb_host_release_interfaces(s);
int ret = ioctl(s->fd, USBDEVFS_SETCONFIGURATION, &config);
dprintf("husb: ctrl set config %d ret %d errno %d\n", config, ret, errno);
if (ret < 0)
return ctrl_error();
usb_host_claim_interfaces(s, config);
return 0;
}
static int usb_host_set_interface(USBHostDevice *s, int iface, int alt)
{
struct usbdevfs_setinterface si;
int ret;
si.interface = iface;
si.altsetting = alt;
ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
dprintf("husb: ctrl set iface %d altset %d ret %d errno %d\n",
iface, alt, ret, errno);
if (ret < 0)
return ctrl_error();
usb_linux_update_endp_table(s);
return 0;
}
static int usb_host_handle_control(USBHostDevice *s, USBPacket *p)
{
struct usbdevfs_urb *urb;
AsyncURB *aurb;
int ret, value, index;
/*
* Process certain standard device requests.
* These are infrequent and are processed synchronously.
*/
value = le16_to_cpu(s->ctrl.req.wValue);
index = le16_to_cpu(s->ctrl.req.wIndex);
dprintf("husb: ctrl type 0x%x req 0x%x val 0x%x index %u len %u\n",
s->ctrl.req.bRequestType, s->ctrl.req.bRequest, value, index,
s->ctrl.len);
if (s->ctrl.req.bRequestType == 0) {
switch (s->ctrl.req.bRequest) {
case USB_REQ_SET_ADDRESS:
return usb_host_set_address(s, value);
case USB_REQ_SET_CONFIGURATION:
return usb_host_set_config(s, value & 0xff);
}
}
if (s->ctrl.req.bRequestType == 1 &&
s->ctrl.req.bRequest == USB_REQ_SET_INTERFACE)
return usb_host_set_interface(s, index, value);
/* The rest are asynchronous */
aurb = async_alloc();
if (!aurb) {
dprintf("husb: async malloc failed\n");
return USB_RET_NAK;
}
aurb->hdev = s;
aurb->packet = p;
/*
* Setup ctrl transfer.
*
* s->ctrl is layed out such that data buffer immediately follows
* 'req' struct which is exactly what usbdevfs expects.
*/
urb = &aurb->urb;
urb->type = USBDEVFS_URB_TYPE_CONTROL;
urb->endpoint = p->devep;
urb->buffer = &s->ctrl.req;
urb->buffer_length = 8 + s->ctrl.len;
urb->usercontext = s;
ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
dprintf("husb: submit ctrl. len %u aurb %p\n", urb->buffer_length, aurb);
if (ret < 0) {
dprintf("husb: submit failed. errno %d\n", errno);
async_free(aurb);
switch(errno) {
case ETIMEDOUT:
return USB_RET_NAK;
case EPIPE:
default:
return USB_RET_STALL;
}
}
usb_defer_packet(p, async_cancel, aurb);
return USB_RET_ASYNC;
}
static int do_token_setup(USBDevice *dev, USBPacket *p)
{
USBHostDevice *s = (USBHostDevice *) dev;
int ret = 0;
if (p->len != 8)
return USB_RET_STALL;
memcpy(&s->ctrl.req, p->data, 8);
s->ctrl.len = le16_to_cpu(s->ctrl.req.wLength);
s->ctrl.offset = 0;
s->ctrl.state = CTRL_STATE_SETUP;
if (s->ctrl.req.bRequestType & USB_DIR_IN) {
ret = usb_host_handle_control(s, p);
if (ret < 0)
return ret;
if (ret < s->ctrl.len)
s->ctrl.len = ret;
s->ctrl.state = CTRL_STATE_DATA;
} else {
if (s->ctrl.len == 0)
s->ctrl.state = CTRL_STATE_ACK;
else
s->ctrl.state = CTRL_STATE_DATA;
}
return ret;
}
static int do_token_in(USBDevice *dev, USBPacket *p)
{
USBHostDevice *s = (USBHostDevice *) dev;
int ret = 0;
if (p->devep != 0)
return usb_host_handle_data(s, p);
switch(s->ctrl.state) {
case CTRL_STATE_ACK:
if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) {
ret = usb_host_handle_control(s, p);
if (ret == USB_RET_ASYNC)
return USB_RET_ASYNC;
s->ctrl.state = CTRL_STATE_IDLE;
return ret > 0 ? 0 : ret;
}
return 0;
case CTRL_STATE_DATA:
if (s->ctrl.req.bRequestType & USB_DIR_IN) {
int len = s->ctrl.len - s->ctrl.offset;
if (len > p->len)
len = p->len;
memcpy(p->data, s->ctrl.buffer + s->ctrl.offset, len);
s->ctrl.offset += len;
if (s->ctrl.offset >= s->ctrl.len)
s->ctrl.state = CTRL_STATE_ACK;
return len;
}
s->ctrl.state = CTRL_STATE_IDLE;
return USB_RET_STALL;
default:
return USB_RET_STALL;
}
}
static int do_token_out(USBDevice *dev, USBPacket *p)
{
USBHostDevice *s = (USBHostDevice *) dev;
if (p->devep != 0)
return usb_host_handle_data(s, p);
switch(s->ctrl.state) {
case CTRL_STATE_ACK:
if (s->ctrl.req.bRequestType & USB_DIR_IN) {
s->ctrl.state = CTRL_STATE_IDLE;
/* transfer OK */
} else {
/* ignore additional output */
}
return 0;
case CTRL_STATE_DATA:
if (!(s->ctrl.req.bRequestType & USB_DIR_IN)) {
int len = s->ctrl.len - s->ctrl.offset;
if (len > p->len)
len = p->len;
memcpy(s->ctrl.buffer + s->ctrl.offset, p->data, len);
s->ctrl.offset += len;
if (s->ctrl.offset >= s->ctrl.len)
s->ctrl.state = CTRL_STATE_ACK;
return len;
}
s->ctrl.state = CTRL_STATE_IDLE;
return USB_RET_STALL;
default:
return USB_RET_STALL;
}
}
/*
* Packet handler.
* Called by the HC (host controller).
*
* Returns length of the transaction or one of the USB_RET_XXX codes.
*/
int usb_host_handle_packet(USBDevice *s, USBPacket *p)
{
switch(p->pid) {
case USB_MSG_ATTACH:
s->state = USB_STATE_ATTACHED;
return 0;
case USB_MSG_DETACH:
s->state = USB_STATE_NOTATTACHED;
return 0;
case USB_MSG_RESET:
s->remote_wakeup = 0;
s->addr = 0;
s->state = USB_STATE_DEFAULT;
s->handle_reset(s);
return 0;
}
/* Rest of the PIDs must match our address */
if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr)
return USB_RET_NODEV;
switch (p->pid) {
case USB_TOKEN_SETUP:
return do_token_setup(s, p);
case USB_TOKEN_IN:
return do_token_in(s, p);
case USB_TOKEN_OUT:
return do_token_out(s, p);
default:
return USB_RET_STALL;
}
}
/* returns 1 on problem encountered or 0 for success */
static int usb_linux_update_endp_table(USBHostDevice *s)
{
uint8_t *descriptors;
uint8_t devep, type, configuration, alt_interface;
struct usb_ctrltransfer ct;
struct usbdevfs_ctrltransfer ct;
int interface, ret, length, i;
ct.bRequestType = USB_DIR_IN;
@ -624,10 +898,14 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p
#endif
dev->fd = fd;
dev->configuration = 1;
/* XXX - do something about initial configuration */
if (!usb_host_update_interfaces(dev, -1))
/*
* Initial configuration is -1 which makes us claim first
* available config. We used to start with 1, which does not
* always work. I've seen devices where first config starts
* with 2.
*/
if (!usb_host_claim_interfaces(dev, -1))
goto fail;
ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci);
@ -646,11 +924,9 @@ static USBDevice *usb_host_device_open_addr(int bus_num, int addr, const char *p
dev->dev.speed = USB_SPEED_LOW;
else
dev->dev.speed = USB_SPEED_HIGH;
dev->dev.handle_packet = usb_generic_handle_packet;
dev->dev.handle_reset = usb_host_handle_reset;
dev->dev.handle_control = usb_host_handle_control;
dev->dev.handle_data = usb_host_handle_data;
dev->dev.handle_packet = usb_host_handle_packet;
dev->dev.handle_reset = usb_host_handle_reset;
dev->dev.handle_destroy = usb_host_handle_destroy;
if (!prod_name || prod_name[0] == '\0')
@ -1055,6 +1331,8 @@ void usb_host_info(void)
#else
#include "hw/usb.h"
void usb_host_info(void)
{
term_printf("USB host devices not supported\n");