usb serial gadget: CDC ACM fixes
authorDavid Brownell <david-b@pacbell.net>
Sat, 19 Apr 2008 00:37:49 +0000 (17:37 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 2 May 2008 17:25:53 +0000 (10:25 -0700)
Based on a patch from <Aurel.Thomi@ruag.com>, this makes the
CDC-ACM support in the serial gadget handle the SET_LINE_CODING
and SET_CONTROL_LINE_STATE requests ... which should improve
interop with at least MS-Windows "usbser.sys" if not some other
ACM host drivers.

It also adds a few REVISIT comments where this code plays a bit
loose with the CDC ACM spec.  If this were used to hook up to a
real RS232 or modem link, those places would need a bit of work.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/gadget/serial.c

index 8d158e5..54cdd6f 100644 (file)
@@ -135,7 +135,10 @@ struct gs_port {
        int                     port_in_use;    /* open/close in progress */
        wait_queue_head_t       port_write_wait;/* waiting to write */
        struct gs_buf           *port_write_buf;
-       struct usb_cdc_line_coding      port_line_coding;
+       struct usb_cdc_line_coding port_line_coding;    /* 8-N-1 etc */
+       u16                     port_handshake_bits;
+#define RS232_RTS      (1 << 1)
+#define RS232_DTE      (1 << 0)
 };
 
 /* the device structure holds info for the USB device */
@@ -199,6 +202,8 @@ static int gs_setup_standard(struct usb_gadget *gadget,
 static int gs_setup_class(struct usb_gadget *gadget,
        const struct usb_ctrlrequest *ctrl);
 static void gs_setup_complete(struct usb_ep *ep, struct usb_request *req);
+static void gs_setup_complete_set_line_coding(struct usb_ep *ep,
+       struct usb_request *req);
 static void gs_disconnect(struct usb_gadget *gadget);
 static int gs_set_config(struct gs_dev *dev, unsigned config);
 static void gs_reset_config(struct gs_dev *dev);
@@ -406,7 +411,7 @@ static struct usb_cdc_acm_descriptor gs_acm_descriptor = {
        .bLength =              sizeof(gs_acm_descriptor),
        .bDescriptorType =      USB_DT_CS_INTERFACE,
        .bDescriptorSubType =   USB_CDC_ACM_TYPE,
-       .bmCapabilities =       0,
+       .bmCapabilities =       (1 << 1),
 };
 
 static const struct usb_cdc_union_desc gs_union_desc = {
@@ -1502,6 +1507,8 @@ static int gs_setup(struct usb_gadget *gadget,
        u16 wValue = le16_to_cpu(ctrl->wValue);
        u16 wLength = le16_to_cpu(ctrl->wLength);
 
+       req->complete = gs_setup_complete;
+
        switch (ctrl->bRequestType & USB_TYPE_MASK) {
        case USB_TYPE_STANDARD:
                ret = gs_setup_standard(gadget,ctrl);
@@ -1679,18 +1686,14 @@ static int gs_setup_class(struct usb_gadget *gadget,
 
        switch (ctrl->bRequest) {
        case USB_CDC_REQ_SET_LINE_CODING:
-               /* FIXME Submit req to read the data; have its completion
-                * handler copy that data to port->port_line_coding (iff
-                * it's valid) and maybe pass it on.  Until then, fail.
-                */
-               pr_warning("gs_setup: set_line_coding "
-                               "unuspported\n");
+               if (wLength != sizeof(struct usb_cdc_line_coding))
+                       break;
+               ret = wLength;
+               req->complete = gs_setup_complete_set_line_coding;
                break;
 
        case USB_CDC_REQ_GET_LINE_CODING:
-               port = dev->dev_port[0];        /* ACM only has one port */
-               ret = min(wLength,
-                       (u16)sizeof(struct usb_cdc_line_coding));
+               ret = min_t(int, wLength, sizeof(struct usb_cdc_line_coding));
                if (port) {
                        spin_lock(&port->port_lock);
                        memcpy(req->buf, &port->port_line_coding, ret);
@@ -1699,15 +1702,27 @@ static int gs_setup_class(struct usb_gadget *gadget,
                break;
 
        case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
-               /* FIXME Submit req to read the data; have its completion
-                * handler use that to set the state (iff it's valid) and
-                * maybe pass it on.  Until then, fail.
-                */
-               pr_warning("gs_setup: set_control_line_state "
-                               "unuspported\n");
+               if (wLength != 0)
+                       break;
+               ret = 0;
+               if (port) {
+                       /* REVISIT:  we currently just remember this data.
+                        * If we change that, update whatever hardware needs
+                        * updating.
+                        */
+                       spin_lock(&port->port_lock);
+                       port->port_handshake_bits = wValue;
+                       spin_unlock(&port->port_lock);
+               }
                break;
 
        default:
+               /* NOTE:  strictly speaking, we should accept AT-commands
+                * using SEND_ENCPSULATED_COMMAND/GET_ENCAPSULATED_RESPONSE.
+                * But our call management descriptor says we don't handle
+                * call management, so we should be able to get by without
+                * handling those "required" commands (except by stalling).
+                */
                pr_err("gs_setup: unknown class request, "
                                "type=%02x, request=%02x, value=%04x, "
                                "index=%04x, length=%d\n",
@@ -1719,6 +1734,42 @@ static int gs_setup_class(struct usb_gadget *gadget,
        return ret;
 }
 
+static void gs_setup_complete_set_line_coding(struct usb_ep *ep,
+               struct usb_request *req)
+{
+       struct gs_dev *dev = ep->driver_data;
+       struct gs_port *port = dev->dev_port[0]; /* ACM only has one port */
+
+       switch (req->status) {
+       case 0:
+               /* normal completion */
+               if (req->actual != sizeof(port->port_line_coding))
+                       usb_ep_set_halt(ep);
+               else if (port) {
+                       struct usb_cdc_line_coding      *value = req->buf;
+
+                       /* REVISIT:  we currently just remember this data.
+                        * If we change that, (a) validate it first, then
+                        * (b) update whatever hardware needs updating.
+                        */
+                       spin_lock(&port->port_lock);
+                       port->port_line_coding = *value;
+                       spin_unlock(&port->port_lock);
+               }
+               break;
+
+       case -ESHUTDOWN:
+               /* disconnect */
+               gs_free_req(ep, req);
+               break;
+
+       default:
+               /* unexpected */
+               break;
+       }
+       return;
+}
+
 /*
  * gs_setup_complete
  */
@@ -1906,6 +1957,11 @@ static int gs_set_config(struct gs_dev *dev, unsigned config)
                }
        }
 
+       /* REVISIT the ACM mode should be able to actually *issue* some
+        * notifications, for at least serial state change events if
+        * not also for network connection; say so in bmCapabilities.
+        */
+
        pr_info("gs_set_config: %s configured, %s speed %s config\n",
                GS_LONG_NAME,
                gadget->speed == USB_SPEED_HIGH ? "high" : "full",