Commit f371e750 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

usb serial gadget: CDC ACM fixes

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: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d75379a5
...@@ -135,7 +135,10 @@ struct gs_port { ...@@ -135,7 +135,10 @@ struct gs_port {
int port_in_use; /* open/close in progress */ int port_in_use; /* open/close in progress */
wait_queue_head_t port_write_wait;/* waiting to write */ wait_queue_head_t port_write_wait;/* waiting to write */
struct gs_buf *port_write_buf; 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 */ /* the device structure holds info for the USB device */
...@@ -199,6 +202,8 @@ static int gs_setup_standard(struct usb_gadget *gadget, ...@@ -199,6 +202,8 @@ static int gs_setup_standard(struct usb_gadget *gadget,
static int gs_setup_class(struct usb_gadget *gadget, static int gs_setup_class(struct usb_gadget *gadget,
const struct usb_ctrlrequest *ctrl); const struct usb_ctrlrequest *ctrl);
static void gs_setup_complete(struct usb_ep *ep, struct usb_request *req); 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 void gs_disconnect(struct usb_gadget *gadget);
static int gs_set_config(struct gs_dev *dev, unsigned config); static int gs_set_config(struct gs_dev *dev, unsigned config);
static void gs_reset_config(struct gs_dev *dev); static void gs_reset_config(struct gs_dev *dev);
...@@ -406,7 +411,7 @@ static struct usb_cdc_acm_descriptor gs_acm_descriptor = { ...@@ -406,7 +411,7 @@ static struct usb_cdc_acm_descriptor gs_acm_descriptor = {
.bLength = sizeof(gs_acm_descriptor), .bLength = sizeof(gs_acm_descriptor),
.bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_ACM_TYPE, .bDescriptorSubType = USB_CDC_ACM_TYPE,
.bmCapabilities = 0, .bmCapabilities = (1 << 1),
}; };
static const struct usb_cdc_union_desc gs_union_desc = { static const struct usb_cdc_union_desc gs_union_desc = {
...@@ -1502,6 +1507,8 @@ static int gs_setup(struct usb_gadget *gadget, ...@@ -1502,6 +1507,8 @@ static int gs_setup(struct usb_gadget *gadget,
u16 wValue = le16_to_cpu(ctrl->wValue); u16 wValue = le16_to_cpu(ctrl->wValue);
u16 wLength = le16_to_cpu(ctrl->wLength); u16 wLength = le16_to_cpu(ctrl->wLength);
req->complete = gs_setup_complete;
switch (ctrl->bRequestType & USB_TYPE_MASK) { switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD: case USB_TYPE_STANDARD:
ret = gs_setup_standard(gadget,ctrl); ret = gs_setup_standard(gadget,ctrl);
...@@ -1679,18 +1686,14 @@ static int gs_setup_class(struct usb_gadget *gadget, ...@@ -1679,18 +1686,14 @@ static int gs_setup_class(struct usb_gadget *gadget,
switch (ctrl->bRequest) { switch (ctrl->bRequest) {
case USB_CDC_REQ_SET_LINE_CODING: case USB_CDC_REQ_SET_LINE_CODING:
/* FIXME Submit req to read the data; have its completion if (wLength != sizeof(struct usb_cdc_line_coding))
* handler copy that data to port->port_line_coding (iff break;
* it's valid) and maybe pass it on. Until then, fail. ret = wLength;
*/ req->complete = gs_setup_complete_set_line_coding;
pr_warning("gs_setup: set_line_coding "
"unuspported\n");
break; break;
case USB_CDC_REQ_GET_LINE_CODING: case USB_CDC_REQ_GET_LINE_CODING:
port = dev->dev_port[0]; /* ACM only has one port */ ret = min_t(int, wLength, sizeof(struct usb_cdc_line_coding));
ret = min(wLength,
(u16)sizeof(struct usb_cdc_line_coding));
if (port) { if (port) {
spin_lock(&port->port_lock); spin_lock(&port->port_lock);
memcpy(req->buf, &port->port_line_coding, ret); memcpy(req->buf, &port->port_line_coding, ret);
...@@ -1699,15 +1702,27 @@ static int gs_setup_class(struct usb_gadget *gadget, ...@@ -1699,15 +1702,27 @@ static int gs_setup_class(struct usb_gadget *gadget,
break; break;
case USB_CDC_REQ_SET_CONTROL_LINE_STATE: case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
/* FIXME Submit req to read the data; have its completion if (wLength != 0)
* handler use that to set the state (iff it's valid) and break;
* maybe pass it on. Until then, fail. ret = 0;
*/ if (port) {
pr_warning("gs_setup: set_control_line_state " /* REVISIT: we currently just remember this data.
"unuspported\n"); * 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; break;
default: 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, " pr_err("gs_setup: unknown class request, "
"type=%02x, request=%02x, value=%04x, " "type=%02x, request=%02x, value=%04x, "
"index=%04x, length=%d\n", "index=%04x, length=%d\n",
...@@ -1719,6 +1734,42 @@ static int gs_setup_class(struct usb_gadget *gadget, ...@@ -1719,6 +1734,42 @@ static int gs_setup_class(struct usb_gadget *gadget,
return ret; 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 * gs_setup_complete
*/ */
...@@ -1906,6 +1957,11 @@ static int gs_set_config(struct gs_dev *dev, unsigned config) ...@@ -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", pr_info("gs_set_config: %s configured, %s speed %s config\n",
GS_LONG_NAME, GS_LONG_NAME,
gadget->speed == USB_SPEED_HIGH ? "high" : "full", gadget->speed == USB_SPEED_HIGH ? "high" : "full",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment