All of lore.kernel.org
 help / color / mirror / Atom feed
From: Max Krasnyansky <maxk@kernel.org>
To: anthony@codemonkey.ws, aurelien@aurel32.net, qemu-devel@nongnu.org
Cc: kvm@vger.kernel.org, Max Krasnyansky <maxk@kernel.org>
Subject: [PATCH] husb: Make control transactions asynchronous
Date: Sat,  6 Sep 2008 05:40:34 +0000	[thread overview]
Message-ID: <1220679634-7976-1-git-send-email-maxk@kernel.org> (raw)

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>
---
 usb-linux.c |  461 +++++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 369 insertions(+), 92 deletions(-)

diff --git a/usb-linux.c b/usb-linux.c
index c31d56a..3ac4440 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -25,28 +25,21 @@
  * 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 <signal.h>
+
+#include <linux/usb/ch9.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 "hw/usb.h"
 
 typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
                         int vendor_id, int product_id,
@@ -54,7 +47,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 +59,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 +92,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 +184,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 +236,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 +271,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 +280,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 +301,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 +348,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 +401,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)
-{
-    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)
+static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
 {
-    USBHostDevice *s = (USBHostDevice *) dev;
-    AsyncURB *aurb;
     struct usbdevfs_urb *urb;
+    AsyncURB *aurb;
     int ret;
 
     aurb = async_alloc();
@@ -474,12 +469,292 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
     return USB_RET_ASYNC;
 }
 
+static int ctrl_error()
+{
+    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 +899,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 +925,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')
-- 
1.5.5.1


WARNING: multiple messages have this Message-ID (diff)
From: Max Krasnyansky <maxk@kernel.org>
To: anthony@codemonkey.ws, aurelien@aurel32.net, qemu-devel@nongnu.org
Cc: kvm@vger.kernel.org, Max Krasnyansky <maxk@kernel.org>
Subject: [Qemu-devel] [PATCH] husb: Make control transactions asynchronous
Date: Sat,  6 Sep 2008 05:40:34 +0000	[thread overview]
Message-ID: <1220679634-7976-1-git-send-email-maxk@kernel.org> (raw)

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>
---
 usb-linux.c |  461 +++++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 369 insertions(+), 92 deletions(-)

diff --git a/usb-linux.c b/usb-linux.c
index c31d56a..3ac4440 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -25,28 +25,21 @@
  * 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 <signal.h>
+
+#include <linux/usb/ch9.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 "hw/usb.h"
 
 typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id,
                         int vendor_id, int product_id,
@@ -54,7 +47,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 +59,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 +92,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 +184,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 +236,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 +271,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 +280,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 +301,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 +348,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 +401,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)
-{
-    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)
+static int usb_host_handle_data(USBHostDevice *s, USBPacket *p)
 {
-    USBHostDevice *s = (USBHostDevice *) dev;
-    AsyncURB *aurb;
     struct usbdevfs_urb *urb;
+    AsyncURB *aurb;
     int ret;
 
     aurb = async_alloc();
@@ -474,12 +469,292 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
     return USB_RET_ASYNC;
 }
 
+static int ctrl_error()
+{
+    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 +899,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 +925,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')
-- 
1.5.5.1

             reply	other threads:[~2008-09-06  5:41 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-09-06  5:40 Max Krasnyansky [this message]
2008-09-06  5:40 ` [Qemu-devel] [PATCH] husb: Make control transactions asynchronous Max Krasnyansky
2008-09-11 20:59 ` Anthony Liguori
2008-09-11 20:59   ` [Qemu-devel] " Anthony Liguori
2008-09-12  3:47   ` Max Krasnyansky
2008-09-12  3:47     ` [Qemu-devel] " Max Krasnyansky

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1220679634-7976-1-git-send-email-maxk@kernel.org \
    --to=maxk@kernel.org \
    --cc=anthony@codemonkey.ws \
    --cc=aurelien@aurel32.net \
    --cc=kvm@vger.kernel.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.