diff options
author | David Brownell <david-b@pacbell.net> | 2008-04-18 20:37:49 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-05-02 13:25:53 -0400 |
commit | f371e750c9324f3498842ee833a0242a11b359e6 (patch) | |
tree | 9a5a0415554f323ff53c8d39c2c5217ac0fae5ec /drivers/usb | |
parent | d75379a538708c5a8e3dba673d866c3f5f856620 (diff) |
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>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/gadget/serial.c | 90 |
1 files changed, 73 insertions, 17 deletions
diff --git a/drivers/usb/gadget/serial.c b/drivers/usb/gadget/serial.c index 8d158e5640e3..54cdd6f94034 100644 --- a/drivers/usb/gadget/serial.c +++ b/drivers/usb/gadget/serial.c | |||
@@ -135,7 +135,10 @@ struct gs_port { | |||
135 | int port_in_use; /* open/close in progress */ | 135 | int port_in_use; /* open/close in progress */ |
136 | wait_queue_head_t port_write_wait;/* waiting to write */ | 136 | wait_queue_head_t port_write_wait;/* waiting to write */ |
137 | struct gs_buf *port_write_buf; | 137 | struct gs_buf *port_write_buf; |
138 | struct usb_cdc_line_coding port_line_coding; | 138 | struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ |
139 | u16 port_handshake_bits; | ||
140 | #define RS232_RTS (1 << 1) | ||
141 | #define RS232_DTE (1 << 0) | ||
139 | }; | 142 | }; |
140 | 143 | ||
141 | /* the device structure holds info for the USB device */ | 144 | /* the device structure holds info for the USB device */ |
@@ -199,6 +202,8 @@ static int gs_setup_standard(struct usb_gadget *gadget, | |||
199 | static int gs_setup_class(struct usb_gadget *gadget, | 202 | static int gs_setup_class(struct usb_gadget *gadget, |
200 | const struct usb_ctrlrequest *ctrl); | 203 | const struct usb_ctrlrequest *ctrl); |
201 | static void gs_setup_complete(struct usb_ep *ep, struct usb_request *req); | 204 | static void gs_setup_complete(struct usb_ep *ep, struct usb_request *req); |
205 | static void gs_setup_complete_set_line_coding(struct usb_ep *ep, | ||
206 | struct usb_request *req); | ||
202 | static void gs_disconnect(struct usb_gadget *gadget); | 207 | static void gs_disconnect(struct usb_gadget *gadget); |
203 | static int gs_set_config(struct gs_dev *dev, unsigned config); | 208 | static int gs_set_config(struct gs_dev *dev, unsigned config); |
204 | static void gs_reset_config(struct gs_dev *dev); | 209 | static void gs_reset_config(struct gs_dev *dev); |
@@ -406,7 +411,7 @@ static struct usb_cdc_acm_descriptor gs_acm_descriptor = { | |||
406 | .bLength = sizeof(gs_acm_descriptor), | 411 | .bLength = sizeof(gs_acm_descriptor), |
407 | .bDescriptorType = USB_DT_CS_INTERFACE, | 412 | .bDescriptorType = USB_DT_CS_INTERFACE, |
408 | .bDescriptorSubType = USB_CDC_ACM_TYPE, | 413 | .bDescriptorSubType = USB_CDC_ACM_TYPE, |
409 | .bmCapabilities = 0, | 414 | .bmCapabilities = (1 << 1), |
410 | }; | 415 | }; |
411 | 416 | ||
412 | static const struct usb_cdc_union_desc gs_union_desc = { | 417 | static const struct usb_cdc_union_desc gs_union_desc = { |
@@ -1502,6 +1507,8 @@ static int gs_setup(struct usb_gadget *gadget, | |||
1502 | u16 wValue = le16_to_cpu(ctrl->wValue); | 1507 | u16 wValue = le16_to_cpu(ctrl->wValue); |
1503 | u16 wLength = le16_to_cpu(ctrl->wLength); | 1508 | u16 wLength = le16_to_cpu(ctrl->wLength); |
1504 | 1509 | ||
1510 | req->complete = gs_setup_complete; | ||
1511 | |||
1505 | switch (ctrl->bRequestType & USB_TYPE_MASK) { | 1512 | switch (ctrl->bRequestType & USB_TYPE_MASK) { |
1506 | case USB_TYPE_STANDARD: | 1513 | case USB_TYPE_STANDARD: |
1507 | ret = gs_setup_standard(gadget,ctrl); | 1514 | ret = gs_setup_standard(gadget,ctrl); |
@@ -1679,18 +1686,14 @@ static int gs_setup_class(struct usb_gadget *gadget, | |||
1679 | 1686 | ||
1680 | switch (ctrl->bRequest) { | 1687 | switch (ctrl->bRequest) { |
1681 | case USB_CDC_REQ_SET_LINE_CODING: | 1688 | case USB_CDC_REQ_SET_LINE_CODING: |
1682 | /* FIXME Submit req to read the data; have its completion | 1689 | if (wLength != sizeof(struct usb_cdc_line_coding)) |
1683 | * handler copy that data to port->port_line_coding (iff | 1690 | break; |
1684 | * it's valid) and maybe pass it on. Until then, fail. | 1691 | ret = wLength; |
1685 | */ | 1692 | req->complete = gs_setup_complete_set_line_coding; |
1686 | pr_warning("gs_setup: set_line_coding " | ||
1687 | "unuspported\n"); | ||
1688 | break; | 1693 | break; |
1689 | 1694 | ||
1690 | case USB_CDC_REQ_GET_LINE_CODING: | 1695 | case USB_CDC_REQ_GET_LINE_CODING: |
1691 | port = dev->dev_port[0]; /* ACM only has one port */ | 1696 | ret = min_t(int, wLength, sizeof(struct usb_cdc_line_coding)); |
1692 | ret = min(wLength, | ||
1693 | (u16)sizeof(struct usb_cdc_line_coding)); | ||
1694 | if (port) { | 1697 | if (port) { |
1695 | spin_lock(&port->port_lock); | 1698 | spin_lock(&port->port_lock); |
1696 | memcpy(req->buf, &port->port_line_coding, ret); | 1699 | memcpy(req->buf, &port->port_line_coding, ret); |
@@ -1699,15 +1702,27 @@ static int gs_setup_class(struct usb_gadget *gadget, | |||
1699 | break; | 1702 | break; |
1700 | 1703 | ||
1701 | case USB_CDC_REQ_SET_CONTROL_LINE_STATE: | 1704 | case USB_CDC_REQ_SET_CONTROL_LINE_STATE: |
1702 | /* FIXME Submit req to read the data; have its completion | 1705 | if (wLength != 0) |
1703 | * handler use that to set the state (iff it's valid) and | 1706 | break; |
1704 | * maybe pass it on. Until then, fail. | 1707 | ret = 0; |
1705 | */ | 1708 | if (port) { |
1706 | pr_warning("gs_setup: set_control_line_state " | 1709 | /* REVISIT: we currently just remember this data. |
1707 | "unuspported\n"); | 1710 | * If we change that, update whatever hardware needs |
1711 | * updating. | ||
1712 | */ | ||
1713 | spin_lock(&port->port_lock); | ||
1714 | port->port_handshake_bits = wValue; | ||
1715 | spin_unlock(&port->port_lock); | ||
1716 | } | ||
1708 | break; | 1717 | break; |
1709 | 1718 | ||
1710 | default: | 1719 | default: |
1720 | /* NOTE: strictly speaking, we should accept AT-commands | ||
1721 | * using SEND_ENCPSULATED_COMMAND/GET_ENCAPSULATED_RESPONSE. | ||
1722 | * But our call management descriptor says we don't handle | ||
1723 | * call management, so we should be able to get by without | ||
1724 | * handling those "required" commands (except by stalling). | ||
1725 | */ | ||
1711 | pr_err("gs_setup: unknown class request, " | 1726 | pr_err("gs_setup: unknown class request, " |
1712 | "type=%02x, request=%02x, value=%04x, " | 1727 | "type=%02x, request=%02x, value=%04x, " |
1713 | "index=%04x, length=%d\n", | 1728 | "index=%04x, length=%d\n", |
@@ -1719,6 +1734,42 @@ static int gs_setup_class(struct usb_gadget *gadget, | |||
1719 | return ret; | 1734 | return ret; |
1720 | } | 1735 | } |
1721 | 1736 | ||
1737 | static void gs_setup_complete_set_line_coding(struct usb_ep *ep, | ||
1738 | struct usb_request *req) | ||
1739 | { | ||
1740 | struct gs_dev *dev = ep->driver_data; | ||
1741 | struct gs_port *port = dev->dev_port[0]; /* ACM only has one port */ | ||
1742 | |||
1743 | switch (req->status) { | ||
1744 | case 0: | ||
1745 | /* normal completion */ | ||
1746 | if (req->actual != sizeof(port->port_line_coding)) | ||
1747 | usb_ep_set_halt(ep); | ||
1748 | else if (port) { | ||
1749 | struct usb_cdc_line_coding *value = req->buf; | ||
1750 | |||
1751 | /* REVISIT: we currently just remember this data. | ||
1752 | * If we change that, (a) validate it first, then | ||
1753 | * (b) update whatever hardware needs updating. | ||
1754 | */ | ||
1755 | spin_lock(&port->port_lock); | ||
1756 | port->port_line_coding = *value; | ||
1757 | spin_unlock(&port->port_lock); | ||
1758 | } | ||
1759 | break; | ||
1760 | |||
1761 | case -ESHUTDOWN: | ||
1762 | /* disconnect */ | ||
1763 | gs_free_req(ep, req); | ||
1764 | break; | ||
1765 | |||
1766 | default: | ||
1767 | /* unexpected */ | ||
1768 | break; | ||
1769 | } | ||
1770 | return; | ||
1771 | } | ||
1772 | |||
1722 | /* | 1773 | /* |
1723 | * gs_setup_complete | 1774 | * gs_setup_complete |
1724 | */ | 1775 | */ |
@@ -1906,6 +1957,11 @@ static int gs_set_config(struct gs_dev *dev, unsigned config) | |||
1906 | } | 1957 | } |
1907 | } | 1958 | } |
1908 | 1959 | ||
1960 | /* REVISIT the ACM mode should be able to actually *issue* some | ||
1961 | * notifications, for at least serial state change events if | ||
1962 | * not also for network connection; say so in bmCapabilities. | ||
1963 | */ | ||
1964 | |||
1909 | pr_info("gs_set_config: %s configured, %s speed %s config\n", | 1965 | pr_info("gs_set_config: %s configured, %s speed %s config\n", |
1910 | GS_LONG_NAME, | 1966 | GS_LONG_NAME, |
1911 | gadget->speed == USB_SPEED_HIGH ? "high" : "full", | 1967 | gadget->speed == USB_SPEED_HIGH ? "high" : "full", |