summaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget/u_serial.c
diff options
context:
space:
mode:
authorDavid Brownell <dbrownell@users.sourceforge.net>2008-08-07 03:49:57 +0200
committerGreg Kroah-Hartman <gregkh@suse.de>2008-08-14 02:32:57 +0200
commit1f1ba11b64947051fc32aa15fcccef6463b433f7 (patch)
tree546d99300dc56f4fe744385263eb3864e843f83e /drivers/usb/gadget/u_serial.c
parentusb gadget: remove needless struct members (diff)
downloadlinux-1f1ba11b64947051fc32aa15fcccef6463b433f7.tar.xz
linux-1f1ba11b64947051fc32aa15fcccef6463b433f7.zip
usb gadget: issue notifications from ACM function
Update the CDC-ACM gadget code to support the peripheral-to-host notifications when the tty is opened or closed, or issues a BREAK. The serial framework code calls new generic hooks; right now only CDC-ACM uses those hooks. This resolves several REVISIT comments in the code. (Based on a patch from Felipe Balbi.) Note that this doesn't expose USB_CDC_CAP_BRK to the host, since this code still rejects USB_CDC_REQ_SEND_BREAK control requests for host-to-peripheral BREAK signaling (received via /dev/ttyGS*). Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Cc: Felipe Balbi <felipe.balbi@nokia.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/gadget/u_serial.c')
-rw-r--r--drivers/usb/gadget/u_serial.c56
1 files changed, 42 insertions, 14 deletions
diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c
index 6641efa55639..53d59287f2bc 100644
--- a/drivers/usb/gadget/u_serial.c
+++ b/drivers/usb/gadget/u_serial.c
@@ -60,7 +60,8 @@
* tty_struct links to the tty/filesystem framework
*
* gserial <---> gs_port ... links will be null when the USB link is
- * inactive; managed by gserial_{connect,disconnect}().
+ * inactive; managed by gserial_{connect,disconnect}(). each gserial
+ * instance can wrap its own USB control protocol.
* gserial->ioport == usb_ep->driver_data ... gs_port
* gs_port->port_usb ... gserial
*
@@ -181,7 +182,7 @@ static void gs_buf_clear(struct gs_buf *gb)
/*
* gs_buf_data_avail
*
- * Return the number of bytes of data available in the circular
+ * Return the number of bytes of data written into the circular
* buffer.
*/
static unsigned gs_buf_data_avail(struct gs_buf *gb)
@@ -282,7 +283,7 @@ gs_buf_get(struct gs_buf *gb, char *buf, unsigned count)
* Allocate a usb_request and its buffer. Returns a pointer to the
* usb_request or NULL if there is an error.
*/
-static struct usb_request *
+struct usb_request *
gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
{
struct usb_request *req;
@@ -306,7 +307,7 @@ gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
*
* Free a usb_request and its buffer.
*/
-static void gs_free_req(struct usb_ep *ep, struct usb_request *req)
+void gs_free_req(struct usb_ep *ep, struct usb_request *req)
{
kfree(req->buf);
usb_ep_free_request(ep, req);
@@ -788,10 +789,13 @@ static int gs_open(struct tty_struct *tty, struct file *file)
/* if connected, start the I/O stream */
if (port->port_usb) {
+ struct gserial *gser = port->port_usb;
+
pr_debug("gs_open: start ttyGS%d\n", port->port_num);
gs_start_io(port);
- /* REVISIT for ACM, issue "network connected" event */
+ if (gser->connect)
+ gser->connect(gser);
}
pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file);
@@ -818,6 +822,7 @@ static int gs_writes_finished(struct gs_port *p)
static void gs_close(struct tty_struct *tty, struct file *file)
{
struct gs_port *port = tty->driver_data;
+ struct gserial *gser;
spin_lock_irq(&port->port_lock);
@@ -837,26 +842,27 @@ static void gs_close(struct tty_struct *tty, struct file *file)
port->openclose = true;
port->open_count = 0;
- if (port->port_usb)
- /* REVISIT for ACM, issue "network disconnected" event */;
+ gser = port->port_usb;
+ if (gser && gser->disconnect)
+ gser->disconnect(gser);
/* wait for circular write buffer to drain, disconnect, or at
* most GS_CLOSE_TIMEOUT seconds; then discard the rest
*/
- if (gs_buf_data_avail(&port->port_write_buf) > 0
- && port->port_usb) {
+ if (gs_buf_data_avail(&port->port_write_buf) > 0 && gser) {
spin_unlock_irq(&port->port_lock);
wait_event_interruptible_timeout(port->drain_wait,
gs_writes_finished(port),
GS_CLOSE_TIMEOUT * HZ);
spin_lock_irq(&port->port_lock);
+ gser = port->port_usb;
}
/* Iff we're disconnected, there can be no I/O in flight so it's
* ok to free the circular buffer; else just scrub it. And don't
* let the push tasklet fire again until we're re-opened.
*/
- if (port->port_usb == NULL)
+ if (gser == NULL)
gs_buf_free(&port->port_write_buf);
else
gs_buf_clear(&port->port_write_buf);
@@ -974,6 +980,24 @@ static void gs_unthrottle(struct tty_struct *tty)
spin_unlock_irqrestore(&port->port_lock, flags);
}
+static int gs_break_ctl(struct tty_struct *tty, int duration)
+{
+ struct gs_port *port = tty->driver_data;
+ int status = 0;
+ struct gserial *gser;
+
+ pr_vdebug("gs_break_ctl: ttyGS%d, send break (%d) \n",
+ port->port_num, duration);
+
+ spin_lock_irq(&port->port_lock);
+ gser = port->port_usb;
+ if (gser && gser->send_break)
+ status = gser->send_break(gser, duration);
+ spin_unlock_irq(&port->port_lock);
+
+ return status;
+}
+
static const struct tty_operations gs_tty_ops = {
.open = gs_open,
.close = gs_close,
@@ -983,6 +1007,7 @@ static const struct tty_operations gs_tty_ops = {
.write_room = gs_write_room,
.chars_in_buffer = gs_chars_in_buffer,
.unthrottle = gs_unthrottle,
+ .break_ctl = gs_break_ctl,
};
/*-------------------------------------------------------------------------*/
@@ -1230,14 +1255,17 @@ int gserial_connect(struct gserial *gser, u8 port_num)
/* REVISIT if waiting on "carrier detect", signal. */
- /* REVISIT for ACM, issue "network connection" status notification:
- * connected if open_count, else disconnected.
+ /* if it's already open, start I/O ... and notify the serial
+ * protocol about open/close status (connect/disconnect).
*/
-
- /* if it's already open, start I/O */
if (port->open_count) {
pr_debug("gserial_connect: start ttyGS%d\n", port->port_num);
gs_start_io(port);
+ if (gser->connect)
+ gser->connect(gser);
+ } else {
+ if (gser->disconnect)
+ gser->disconnect(gser);
}
spin_unlock_irqrestore(&port->port_lock, flags);