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: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
d75379a538
commit
f371e750c9
|
@ -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,
|
||||||
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 = {
|
||||||
.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,
|
||||||
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,
|
||||||
|
|
||||||
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,
|
||||||
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,
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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",
|
||||||
|
|
Loading…
Reference in New Issue