diff options
Diffstat (limited to 'drivers/usb/gadget/function/u_serial.c')
| -rw-r--r-- | drivers/usb/gadget/function/u_serial.c | 981 |
1 files changed, 491 insertions, 490 deletions
diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 9b0805f55ad7..1cce5317181a 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * u_serial.c - utilities for USB gadget "serial port"/TTY support * @@ -9,26 +10,26 @@ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) * Copyright (C) 2000 Peter Berger (pberger@brimson.com) * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com) - * - * This software is distributed under the terms of the GNU General - * Public License ("GPL") as published by the Free Software Foundation, - * either version 2 of that License or (at your option) any later version. */ /* #define VERBOSE_DEBUG */ #include <linux/kernel.h> #include <linux/sched.h> -#include <linux/interrupt.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/export.h> #include <linux/module.h> #include <linux/console.h> +#include <linux/kstrtox.h> #include <linux/kthread.h> +#include <linux/workqueue.h> +#include <linux/kfifo.h> +#include <linux/serial.h> #include "u_serial.h" @@ -83,23 +84,17 @@ #define WRITE_BUF_SIZE 8192 /* TX only */ #define GS_CONSOLE_BUF_SIZE 8192 -/* circular buffer */ -struct gs_buf { - unsigned buf_size; - char *buf_buf; - char *buf_get; - char *buf_put; -}; +/* Prevents race conditions while accessing gser->ioport */ +static DEFINE_SPINLOCK(serial_port_lock); /* console info */ -struct gscons_info { - struct gs_port *port; - struct task_struct *console_thread; - struct gs_buf con_buf; - /* protect the buf and busy flag */ - spinlock_t con_lock; - int req_busy; - struct usb_request *console_req; +struct gs_console { + struct console console; + struct work_struct work; + spinlock_t lock; + struct usb_request *req; + struct kfifo buf; + size_t missed; }; /* @@ -111,8 +106,10 @@ struct gs_port { spinlock_t port_lock; /* guard port_* access */ struct gserial *port_usb; +#ifdef CONFIG_U_SERIAL_CONSOLE + struct gs_console *console; +#endif - bool openclose; /* open/close in progress */ u8 port_num; struct list_head read_pool; @@ -120,15 +117,18 @@ struct gs_port { int read_allocated; struct list_head read_queue; unsigned n_read; - struct tasklet_struct push; + struct delayed_work push; struct list_head write_pool; int write_started; int write_allocated; - struct gs_buf port_write_buf; + struct kfifo port_write_buf; wait_queue_head_t drain_wait; /* wait while writes drain */ bool write_busy; wait_queue_head_t close_wait; + bool suspended; /* port suspended */ + bool start_delayed; /* delay start when suspended */ + struct async_icount icount; /* REVISIT this state ... */ struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ @@ -157,144 +157,6 @@ static struct portmaster { /*-------------------------------------------------------------------------*/ -/* Circular Buffer */ - -/* - * gs_buf_alloc - * - * Allocate a circular buffer and all associated memory. - */ -static int gs_buf_alloc(struct gs_buf *gb, unsigned size) -{ - gb->buf_buf = kmalloc(size, GFP_KERNEL); - if (gb->buf_buf == NULL) - return -ENOMEM; - - gb->buf_size = size; - gb->buf_put = gb->buf_buf; - gb->buf_get = gb->buf_buf; - - return 0; -} - -/* - * gs_buf_free - * - * Free the buffer and all associated memory. - */ -static void gs_buf_free(struct gs_buf *gb) -{ - kfree(gb->buf_buf); - gb->buf_buf = NULL; -} - -/* - * gs_buf_clear - * - * Clear out all data in the circular buffer. - */ -static void gs_buf_clear(struct gs_buf *gb) -{ - gb->buf_get = gb->buf_put; - /* equivalent to a get of all data available */ -} - -/* - * gs_buf_data_avail - * - * Return the number of bytes of data written into the circular - * buffer. - */ -static unsigned gs_buf_data_avail(struct gs_buf *gb) -{ - return (gb->buf_size + gb->buf_put - gb->buf_get) % gb->buf_size; -} - -/* - * gs_buf_space_avail - * - * Return the number of bytes of space available in the circular - * buffer. - */ -static unsigned gs_buf_space_avail(struct gs_buf *gb) -{ - return (gb->buf_size + gb->buf_get - gb->buf_put - 1) % gb->buf_size; -} - -/* - * gs_buf_put - * - * Copy data data from a user buffer and put it into the circular buffer. - * Restrict to the amount of space available. - * - * Return the number of bytes copied. - */ -static unsigned -gs_buf_put(struct gs_buf *gb, const char *buf, unsigned count) -{ - unsigned len; - - len = gs_buf_space_avail(gb); - if (count > len) - count = len; - - if (count == 0) - return 0; - - len = gb->buf_buf + gb->buf_size - gb->buf_put; - if (count > len) { - memcpy(gb->buf_put, buf, len); - memcpy(gb->buf_buf, buf+len, count - len); - gb->buf_put = gb->buf_buf + count - len; - } else { - memcpy(gb->buf_put, buf, count); - if (count < len) - gb->buf_put += count; - else /* count == len */ - gb->buf_put = gb->buf_buf; - } - - return count; -} - -/* - * gs_buf_get - * - * Get data from the circular buffer and copy to the given buffer. - * Restrict to the amount of data available. - * - * Return the number of bytes copied. - */ -static unsigned -gs_buf_get(struct gs_buf *gb, char *buf, unsigned count) -{ - unsigned len; - - len = gs_buf_data_avail(gb); - if (count > len) - count = len; - - if (count == 0) - return 0; - - len = gb->buf_buf + gb->buf_size - gb->buf_get; - if (count > len) { - memcpy(buf, gb->buf_get, len); - memcpy(buf+len, gb->buf_buf, count - len); - gb->buf_get = gb->buf_buf + count - len; - } else { - memcpy(buf, gb->buf_get, count); - if (count < len) - gb->buf_get += count; - else /* count == len */ - gb->buf_get = gb->buf_buf; - } - - return count; -} - -/*-------------------------------------------------------------------------*/ - /* I/O glue between TTY (upper) and USB function (lower) driver layers */ /* @@ -349,11 +211,11 @@ gs_send_packet(struct gs_port *port, char *packet, unsigned size) { unsigned len; - len = gs_buf_data_avail(&port->port_write_buf); + len = kfifo_len(&port->port_write_buf); if (len < size) size = len; if (size != 0) - size = gs_buf_get(&port->port_write_buf, packet, size); + size = kfifo_out(&port->port_write_buf, packet, size); return size; } @@ -398,14 +260,13 @@ __acquires(&port->port_lock) break; } do_tty_wake = true; + port->icount.tx += len; req->length = len; list_del(&req->list); - req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0); + req->zero = kfifo_is_empty(&port->port_write_buf); - pr_vdebug("ttyGS%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", - port->port_num, len, *((u8 *)req->buf), - *((u8 *)req->buf+1), *((u8 *)req->buf+2)); + pr_vdebug("ttyGS%d: tx len=%d, %3ph ...\n", port->port_num, len, req->buf); /* Drop lock while we call out of driver; completions * could be issued while we do so. Disconnection may @@ -434,8 +295,8 @@ __acquires(&port->port_lock) break; } - if (do_tty_wake && port->port.tty) - tty_wakeup(port->port.tty); + if (do_tty_wake) + tty_port_tty_wakeup(&port->port); return status; } @@ -491,7 +352,7 @@ __acquires(&port->port_lock) } /* - * RX tasklet takes data out of the RX queue and hands it up to the TTY + * RX work takes data out of the RX queue and hands it up to the TTY * layer until it refuses to take any more data (or is throttled back). * Then it issues reads for any further data. * @@ -500,9 +361,10 @@ __acquires(&port->port_lock) * So QUEUE_SIZE packets plus however many the FIFO holds (usually two) * can be buffered before the TTY layer's buffers (currently 64 KB). */ -static void gs_rx_push(unsigned long _port) +static void gs_rx_push(struct work_struct *work) { - struct gs_port *port = (void *)_port; + struct delayed_work *w = to_delayed_work(work); + struct gs_port *port = container_of(w, struct gs_port, push); struct tty_struct *tty; struct list_head *queue = &port->read_queue; bool disconnect = false; @@ -530,14 +392,14 @@ static void gs_rx_push(unsigned long _port) /* presumably a transient fault */ pr_warn("ttyGS%d: unexpected RX status %d\n", port->port_num, req->status); - /* FALLTHROUGH */ + fallthrough; case 0: /* normal completion */ break; } /* push data to (open) tty */ - if (req->actual) { + if (req->actual && tty) { char *packet = req->buf; unsigned size = req->actual; unsigned n; @@ -550,6 +412,7 @@ static void gs_rx_push(unsigned long _port) size -= n; } + port->icount.rx += size; count = tty_insert_flip_string(&port->port, packet, size); if (count) @@ -577,21 +440,13 @@ static void gs_rx_push(unsigned long _port) /* We want our data queue to become empty ASAP, keeping data * in the tty and ldisc (not here). If we couldn't push any - * this time around, there may be trouble unless there's an - * implicit tty_unthrottle() call on its way... + * this time around, RX may be starved, so wait until next jiffy. * - * REVISIT we should probably add a timer to keep the tasklet - * from starving ... but it's not clear that case ever happens. + * We may leave non-empty queue only when there is a tty, and + * either it is throttled or there is no more room in flip buffer. */ - if (!list_empty(queue) && tty) { - if (!tty_throttled(tty)) { - if (do_push) - tasklet_schedule(&port->push); - else - pr_warn("ttyGS%d: RX not scheduled?\n", - port->port_num); - } - } + if (!list_empty(queue) && !tty_throttled(tty)) + schedule_delayed_work(&port->push, 1); /* If we're still connected, refill the USB RX queue. */ if (!disconnect && port->port_usb) @@ -607,7 +462,7 @@ static void gs_read_complete(struct usb_ep *ep, struct usb_request *req) /* Queue all received data until the tty layer is ready for it. */ spin_lock(&port->port_lock); list_add_tail(&req->list, &port->read_queue); - tasklet_schedule(&port->push); + schedule_delayed_work(&port->push, 0); spin_unlock(&port->port_lock); } @@ -624,7 +479,7 @@ static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) /* presumably a transient fault */ pr_warn("%s: unexpected %s status %d\n", __func__, ep->name, req->status); - /* FALL THROUGH */ + fallthrough; case 0: /* normal completion */ gs_start_tx(port); @@ -679,7 +534,7 @@ static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head, /** * gs_start_io - start USB I/O streams - * @dev: encapsulates endpoints to use + * @port: port to use * Context: holding port_lock; port_tty and port_usb are non-null * * We only start I/O when something is connected to both sides of @@ -715,19 +570,35 @@ static int gs_start_io(struct gs_port *port) port->n_read = 0; started = gs_start_rx(port); - /* unblock any pending writes into our circular buffer */ if (started) { - tty_wakeup(port->port.tty); + gs_start_tx(port); + /* Unblock any pending writes into our circular buffer, in case + * we didn't in gs_start_tx() */ + tty_port_tty_wakeup(&port->port); } else { - gs_free_requests(ep, head, &port->read_allocated); - gs_free_requests(port->port_usb->in, &port->write_pool, - &port->write_allocated); + /* Free reqs only if we are still connected */ + if (port->port_usb) { + gs_free_requests(ep, head, &port->read_allocated); + gs_free_requests(port->port_usb->in, &port->write_pool, + &port->write_allocated); + } status = -EIO; } return status; } +static int gserial_wakeup_host(struct gserial *gser) +{ + struct usb_function *func = &gser->func; + struct usb_gadget *gadget = func->config->cdev->gadget; + + if (func->func_suspended) + return usb_func_wakeup(func); + else + return usb_gadget_wakeup(gadget); +} + /*-------------------------------------------------------------------------*/ /* TTY Driver */ @@ -741,108 +612,79 @@ static int gs_open(struct tty_struct *tty, struct file *file) { int port_num = tty->index; struct gs_port *port; - int status; - - do { - mutex_lock(&ports[port_num].lock); - port = ports[port_num].port; - if (!port) - status = -ENODEV; - else { - spin_lock_irq(&port->port_lock); - - /* already open? Great. */ - if (port->port.count) { - status = 0; - port->port.count++; - - /* currently opening/closing? wait ... */ - } else if (port->openclose) { - status = -EBUSY; - - /* ... else we do the work */ - } else { - status = -EAGAIN; - port->openclose = true; - } - spin_unlock_irq(&port->port_lock); - } - mutex_unlock(&ports[port_num].lock); + int status = 0; - switch (status) { - default: - /* fully handled */ - return status; - case -EAGAIN: - /* must do the work */ - break; - case -EBUSY: - /* wait for EAGAIN task to finish */ - msleep(1); - /* REVISIT could have a waitchannel here, if - * concurrent open performance is important - */ - break; - } - } while (status != -EAGAIN); + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + if (!port) { + status = -ENODEV; + goto out; + } - /* Do the "real open" */ spin_lock_irq(&port->port_lock); /* allocate circular buffer on first open */ - if (port->port_write_buf.buf_buf == NULL) { + if (!kfifo_initialized(&port->port_write_buf)) { spin_unlock_irq(&port->port_lock); - status = gs_buf_alloc(&port->port_write_buf, WRITE_BUF_SIZE); - spin_lock_irq(&port->port_lock); + /* + * portmaster's mutex still protects from simultaneous open(), + * and close() can't happen, yet. + */ + + status = kfifo_alloc(&port->port_write_buf, + WRITE_BUF_SIZE, GFP_KERNEL); if (status) { pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n", - port->port_num, tty, file); - port->openclose = false; - goto exit_unlock_port; + port_num, tty, file); + goto out; } - } - /* REVISIT if REMOVED (ports[].port NULL), abort the open - * to let rmmod work faster (but this way isn't wrong). - */ + spin_lock_irq(&port->port_lock); + } - /* REVISIT maybe wait for "carrier detect" */ + /* already open? Great. */ + if (port->port.count++) + goto exit_unlock_port; tty->driver_data = port; port->port.tty = tty; - port->port.count = 1; - port->openclose = false; - /* if connected, start the I/O stream */ if (port->port_usb) { - struct gserial *gser = port->port_usb; + /* if port is suspended, wait resume to start I/0 stream */ + if (!port->suspended) { + struct gserial *gser = port->port_usb; - pr_debug("gs_open: start ttyGS%d\n", port->port_num); - gs_start_io(port); + pr_debug("gs_open: start ttyGS%d\n", port->port_num); + gs_start_io(port); - if (gser->connect) - gser->connect(gser); + if (gser->connect) + gser->connect(gser); + } else { + pr_debug("delay start of ttyGS%d\n", port->port_num); + port->start_delayed = true; + } } pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file); - status = 0; - exit_unlock_port: spin_unlock_irq(&port->port_lock); +out: + mutex_unlock(&ports[port_num].lock); return status; } -static int gs_writes_finished(struct gs_port *p) +static int gs_close_flush_done(struct gs_port *p) { int cond; - /* return true on disconnect or empty buffer */ + /* return true on disconnect or empty buffer or if raced with open() */ spin_lock_irq(&p->port_lock); - cond = (p->port_usb == NULL) || !gs_buf_data_avail(&p->port_write_buf); + cond = p->port_usb == NULL || !kfifo_len(&p->port_write_buf) || + p->port.count > 1; spin_unlock_irq(&p->port_lock); return cond; @@ -856,6 +698,7 @@ static void gs_close(struct tty_struct *tty, struct file *file) spin_lock_irq(&port->port_lock); if (port->port.count != 1) { +raced_with_open: if (port->port.count == 0) WARN_ON(1); else @@ -865,41 +708,39 @@ static void gs_close(struct tty_struct *tty, struct file *file) pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file); - /* mark port as closing but in use; we can drop port lock - * and sleep if necessary - */ - port->openclose = true; - port->port.count = 0; - gser = port->port_usb; - if (gser && gser->disconnect) + if (gser && !port->suspended && 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 && gser) { + if (kfifo_len(&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_flush_done(port), GS_CLOSE_TIMEOUT * HZ); spin_lock_irq(&port->port_lock); + + if (port->port.count != 1) + goto raced_with_open; + 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. + * let the push async work fire again until we're re-opened. */ if (gser == NULL) - gs_buf_free(&port->port_write_buf); + kfifo_free(&port->port_write_buf); else - gs_buf_clear(&port->port_write_buf); + kfifo_reset(&port->port_write_buf); + port->start_delayed = false; + port->port.count = 0; port->port.tty = NULL; - port->openclose = false; - pr_debug("gs_close: ttyGS%d (%p,%p) done!\n", port->port_num, tty, file); @@ -908,17 +749,30 @@ exit: spin_unlock_irq(&port->port_lock); } -static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count) +static ssize_t gs_write(struct tty_struct *tty, const u8 *buf, size_t count) { struct gs_port *port = tty->driver_data; unsigned long flags; + int ret = 0; + struct gserial *gser = port->port_usb; - pr_vdebug("gs_write: ttyGS%d (%p) writing %d bytes\n", + pr_vdebug("gs_write: ttyGS%d (%p) writing %zu bytes\n", port->port_num, tty, count); spin_lock_irqsave(&port->port_lock, flags); if (count) - count = gs_buf_put(&port->port_write_buf, buf, count); + count = kfifo_in(&port->port_write_buf, buf, count); + + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return count; + } + spin_lock_irqsave(&port->port_lock, flags); + } + /* treat count == 0 as flush_chars() */ if (port->port_usb) gs_start_tx(port); @@ -927,7 +781,7 @@ static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count) return count; } -static int gs_put_char(struct tty_struct *tty, unsigned char ch) +static int gs_put_char(struct tty_struct *tty, u8 ch) { struct gs_port *port = tty->driver_data; unsigned long flags; @@ -937,7 +791,7 @@ static int gs_put_char(struct tty_struct *tty, unsigned char ch) port->port_num, tty, ch, __builtin_return_address(0)); spin_lock_irqsave(&port->port_lock, flags); - status = gs_buf_put(&port->port_write_buf, &ch, 1); + status = kfifo_put(&port->port_write_buf, ch); spin_unlock_irqrestore(&port->port_lock, flags); return status; @@ -947,43 +801,55 @@ static void gs_flush_chars(struct tty_struct *tty) { struct gs_port *port = tty->driver_data; unsigned long flags; + int ret = 0; + struct gserial *gser = port->port_usb; pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty); spin_lock_irqsave(&port->port_lock, flags); + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return; + } + spin_lock_irqsave(&port->port_lock, flags); + } + if (port->port_usb) gs_start_tx(port); spin_unlock_irqrestore(&port->port_lock, flags); } -static int gs_write_room(struct tty_struct *tty) +static unsigned int gs_write_room(struct tty_struct *tty) { struct gs_port *port = tty->driver_data; unsigned long flags; - int room = 0; + unsigned int room = 0; spin_lock_irqsave(&port->port_lock, flags); if (port->port_usb) - room = gs_buf_space_avail(&port->port_write_buf); + room = kfifo_avail(&port->port_write_buf); spin_unlock_irqrestore(&port->port_lock, flags); - pr_vdebug("gs_write_room: (%d,%p) room=%d\n", + pr_vdebug("gs_write_room: (%d,%p) room=%u\n", port->port_num, tty, room); return room; } -static int gs_chars_in_buffer(struct tty_struct *tty) +static unsigned int gs_chars_in_buffer(struct tty_struct *tty) { struct gs_port *port = tty->driver_data; unsigned long flags; - int chars = 0; + unsigned int chars; spin_lock_irqsave(&port->port_lock, flags); - chars = gs_buf_data_avail(&port->port_write_buf); + chars = kfifo_len(&port->port_write_buf); spin_unlock_irqrestore(&port->port_lock, flags); - pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%d\n", + pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%u\n", port->port_num, tty, chars); return chars; @@ -1001,8 +867,8 @@ static void gs_unthrottle(struct tty_struct *tty) * rts/cts, or other handshaking with the host, but if the * read queue backs up enough we'll be NAKing OUT packets. */ - tasklet_schedule(&port->push); pr_vdebug("ttyGS%d: unthrottle\n", port->port_num); + schedule_delayed_work(&port->push, 0); } spin_unlock_irqrestore(&port->port_lock, flags); } @@ -1025,6 +891,23 @@ static int gs_break_ctl(struct tty_struct *tty, int duration) return status; } +static int gs_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct gs_port *port = tty->driver_data; + struct async_icount cnow; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + cnow = port->icount; + spin_unlock_irqrestore(&port->port_lock, flags); + + icount->rx = cnow.rx; + icount->tx = cnow.tx; + + return 0; +} + static const struct tty_operations gs_tty_ops = { .open = gs_open, .close = gs_close, @@ -1035,6 +918,7 @@ static const struct tty_operations gs_tty_ops = { .chars_in_buffer = gs_chars_in_buffer, .unthrottle = gs_unthrottle, .break_ctl = gs_break_ctl, + .get_icount = gs_get_icount, }; /*-------------------------------------------------------------------------*/ @@ -1043,49 +927,23 @@ static struct tty_driver *gs_tty_driver; #ifdef CONFIG_U_SERIAL_CONSOLE -static struct gscons_info gscons_info; -static struct console gserial_cons; - -static struct usb_request *gs_request_new(struct usb_ep *ep) -{ - struct usb_request *req = usb_ep_alloc_request(ep, GFP_ATOMIC); - if (!req) - return NULL; - - req->buf = kmalloc(ep->maxpacket, GFP_ATOMIC); - if (!req->buf) { - usb_ep_free_request(ep, req); - return NULL; - } - - return req; -} - -static void gs_request_free(struct usb_request *req, struct usb_ep *ep) -{ - if (!req) - return; - - kfree(req->buf); - usb_ep_free_request(ep, req); -} - -static void gs_complete_out(struct usb_ep *ep, struct usb_request *req) +static void gs_console_complete_out(struct usb_ep *ep, struct usb_request *req) { - struct gscons_info *info = &gscons_info; + struct gs_console *cons = req->context; switch (req->status) { default: pr_warn("%s: unexpected %s status %d\n", __func__, ep->name, req->status); + fallthrough; case 0: /* normal completion */ - spin_lock(&info->con_lock); - info->req_busy = 0; - spin_unlock(&info->con_lock); - - wake_up_process(info->console_thread); + spin_lock(&cons->lock); + req->length = 0; + schedule_work(&cons->work); + spin_unlock(&cons->lock); break; + case -ECONNRESET: case -ESHUTDOWN: /* disconnect */ pr_vdebug("%s: %s shutdown\n", __func__, ep->name); @@ -1093,190 +951,253 @@ static void gs_complete_out(struct usb_ep *ep, struct usb_request *req) } } -static int gs_console_connect(int port_num) +static void __gs_console_push(struct gs_console *cons) { - struct gscons_info *info = &gscons_info; - struct gs_port *port; + struct usb_request *req = cons->req; struct usb_ep *ep; + size_t size; - if (port_num != gserial_cons.index) { - pr_err("%s: port num [%d] is not support console\n", - __func__, port_num); - return -ENXIO; - } + if (!req) + return; /* disconnected */ - port = ports[port_num].port; - ep = port->port_usb->in; - if (!info->console_req) { - info->console_req = gs_request_new(ep); - if (!info->console_req) - return -ENOMEM; - info->console_req->complete = gs_complete_out; + if (req->length) + return; /* busy */ + + ep = cons->console.data; + size = kfifo_out(&cons->buf, req->buf, ep->maxpacket); + if (!size) + return; + + if (cons->missed && ep->maxpacket >= 64) { + char buf[64]; + size_t len; + + len = sprintf(buf, "\n[missed %zu bytes]\n", cons->missed); + kfifo_in(&cons->buf, buf, len); + cons->missed = 0; } - info->port = port; - spin_lock(&info->con_lock); - info->req_busy = 0; - spin_unlock(&info->con_lock); - pr_vdebug("port[%d] console connect!\n", port_num); - return 0; + req->length = size; + + spin_unlock_irq(&cons->lock); + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + req->length = 0; + spin_lock_irq(&cons->lock); } -static void gs_console_disconnect(struct usb_ep *ep) +static void gs_console_work(struct work_struct *work) { - struct gscons_info *info = &gscons_info; - struct usb_request *req = info->console_req; + struct gs_console *cons = container_of(work, struct gs_console, work); - gs_request_free(req, ep); - info->console_req = NULL; + spin_lock_irq(&cons->lock); + + __gs_console_push(cons); + + spin_unlock_irq(&cons->lock); } -static int gs_console_thread(void *data) +static void gs_console_write(struct console *co, + const char *buf, unsigned count) { - struct gscons_info *info = &gscons_info; - struct gs_port *port; + struct gs_console *cons = container_of(co, struct gs_console, console); + unsigned long flags; + size_t n; + + spin_lock_irqsave(&cons->lock, flags); + + n = kfifo_in(&cons->buf, buf, count); + if (n < count) + cons->missed += count - n; + + if (cons->req && !cons->req->length) + schedule_work(&cons->work); + + spin_unlock_irqrestore(&cons->lock, flags); +} + +static struct tty_driver *gs_console_device(struct console *co, int *index) +{ + *index = co->index; + return gs_tty_driver; +} + +static int gs_console_connect(struct gs_port *port) +{ + struct gs_console *cons = port->console; struct usb_request *req; struct usb_ep *ep; - int xfer, ret, count, size; - - do { - port = info->port; - set_current_state(TASK_INTERRUPTIBLE); - if (!port || !port->port_usb - || !port->port_usb->in || !info->console_req) - goto sched; - - req = info->console_req; - ep = port->port_usb->in; - - spin_lock_irq(&info->con_lock); - count = gs_buf_data_avail(&info->con_buf); - size = ep->maxpacket; - - if (count > 0 && !info->req_busy) { - set_current_state(TASK_RUNNING); - if (count < size) - size = count; - - xfer = gs_buf_get(&info->con_buf, req->buf, size); - req->length = xfer; - - spin_unlock(&info->con_lock); - ret = usb_ep_queue(ep, req, GFP_ATOMIC); - spin_lock(&info->con_lock); - if (ret < 0) - info->req_busy = 0; - else - info->req_busy = 1; - - spin_unlock_irq(&info->con_lock); - } else { - spin_unlock_irq(&info->con_lock); -sched: - if (kthread_should_stop()) { - set_current_state(TASK_RUNNING); - break; - } - schedule(); - } - } while (1); + + if (!cons) + return 0; + + ep = port->port_usb->in; + req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC); + if (!req) + return -ENOMEM; + req->complete = gs_console_complete_out; + req->context = cons; + req->length = 0; + + spin_lock(&cons->lock); + cons->req = req; + cons->console.data = ep; + spin_unlock(&cons->lock); + + pr_debug("ttyGS%d: console connected!\n", port->port_num); + + schedule_work(&cons->work); return 0; } -static int gs_console_setup(struct console *co, char *options) +static void gs_console_disconnect(struct gs_port *port) { - struct gscons_info *info = &gscons_info; - int status; + struct gs_console *cons = port->console; + struct usb_request *req; + struct usb_ep *ep; - info->port = NULL; - info->console_req = NULL; - info->req_busy = 0; - spin_lock_init(&info->con_lock); + if (!cons) + return; - status = gs_buf_alloc(&info->con_buf, GS_CONSOLE_BUF_SIZE); - if (status) { - pr_err("%s: allocate console buffer failed\n", __func__); - return status; - } + spin_lock(&cons->lock); + + req = cons->req; + ep = cons->console.data; + cons->req = NULL; + + spin_unlock(&cons->lock); + + if (!req) + return; - info->console_thread = kthread_create(gs_console_thread, - co, "gs_console"); - if (IS_ERR(info->console_thread)) { - pr_err("%s: cannot create console thread\n", __func__); - gs_buf_free(&info->con_buf); - return PTR_ERR(info->console_thread); + usb_ep_dequeue(ep, req); + gs_free_req(ep, req); +} + +static int gs_console_init(struct gs_port *port) +{ + struct gs_console *cons; + int err; + + if (port->console) + return 0; + + cons = kzalloc(sizeof(*port->console), GFP_KERNEL); + if (!cons) + return -ENOMEM; + + strcpy(cons->console.name, "ttyGS"); + cons->console.write = gs_console_write; + cons->console.device = gs_console_device; + cons->console.flags = CON_PRINTBUFFER; + cons->console.index = port->port_num; + + INIT_WORK(&cons->work, gs_console_work); + spin_lock_init(&cons->lock); + + err = kfifo_alloc(&cons->buf, GS_CONSOLE_BUF_SIZE, GFP_KERNEL); + if (err) { + pr_err("ttyGS%d: allocate console buffer failed\n", port->port_num); + kfree(cons); + return err; } - wake_up_process(info->console_thread); + + port->console = cons; + register_console(&cons->console); + + spin_lock_irq(&port->port_lock); + if (port->port_usb) + gs_console_connect(port); + spin_unlock_irq(&port->port_lock); return 0; } -static void gs_console_write(struct console *co, - const char *buf, unsigned count) +static void gs_console_exit(struct gs_port *port) { - struct gscons_info *info = &gscons_info; - unsigned long flags; + struct gs_console *cons = port->console; - spin_lock_irqsave(&info->con_lock, flags); - gs_buf_put(&info->con_buf, buf, count); - spin_unlock_irqrestore(&info->con_lock, flags); + if (!cons) + return; + + unregister_console(&cons->console); - wake_up_process(info->console_thread); + spin_lock_irq(&port->port_lock); + if (cons->req) + gs_console_disconnect(port); + spin_unlock_irq(&port->port_lock); + + cancel_work_sync(&cons->work); + kfifo_free(&cons->buf); + kfree(cons); + port->console = NULL; } -static struct tty_driver *gs_console_device(struct console *co, int *index) +ssize_t gserial_set_console(unsigned char port_num, const char *page, size_t count) { - struct tty_driver **p = (struct tty_driver **)co->data; + struct gs_port *port; + bool enable; + int ret; - if (!*p) - return NULL; + ret = kstrtobool(page, &enable); + if (ret) + return ret; - *index = co->index; - return *p; -} + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; -static struct console gserial_cons = { - .name = "ttyGS", - .write = gs_console_write, - .device = gs_console_device, - .setup = gs_console_setup, - .flags = CON_PRINTBUFFER, - .index = -1, - .data = &gs_tty_driver, -}; + if (WARN_ON(port == NULL)) { + ret = -ENXIO; + goto out; + } -static void gserial_console_init(void) -{ - register_console(&gserial_cons); + if (enable) + ret = gs_console_init(port); + else + gs_console_exit(port); +out: + mutex_unlock(&ports[port_num].lock); + + return ret < 0 ? ret : count; } +EXPORT_SYMBOL_GPL(gserial_set_console); -static void gserial_console_exit(void) +ssize_t gserial_get_console(unsigned char port_num, char *page) { - struct gscons_info *info = &gscons_info; + struct gs_port *port; + ssize_t ret; - unregister_console(&gserial_cons); - if (!IS_ERR_OR_NULL(info->console_thread)) - kthread_stop(info->console_thread); - gs_buf_free(&info->con_buf); + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + + if (WARN_ON(port == NULL)) + ret = -ENXIO; + else + ret = sprintf(page, "%u\n", !!port->console); + + mutex_unlock(&ports[port_num].lock); + + return ret; } +EXPORT_SYMBOL_GPL(gserial_get_console); #else -static int gs_console_connect(int port_num) +static int gs_console_connect(struct gs_port *port) { return 0; } -static void gs_console_disconnect(struct usb_ep *ep) +static void gs_console_disconnect(struct gs_port *port) { } -static void gserial_console_init(void) +static int gs_console_init(struct gs_port *port) { + return -ENOSYS; } -static void gserial_console_exit(void) +static void gs_console_exit(struct gs_port *port) { } @@ -1305,7 +1226,7 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) init_waitqueue_head(&port->drain_wait); init_waitqueue_head(&port->close_wait); - tasklet_init(&port->push, gs_rx_push, (unsigned long) port); + INIT_DELAYED_WORK(&port->push, gs_rx_push); INIT_LIST_HEAD(&port->read_pool); INIT_LIST_HEAD(&port->read_queue); @@ -1325,14 +1246,15 @@ static int gs_closed(struct gs_port *port) int cond; spin_lock_irq(&port->port_lock); - cond = (port->port.count == 0) && !port->openclose; + cond = port->port.count == 0; spin_unlock_irq(&port->port_lock); + return cond; } static void gserial_free_port(struct gs_port *port) { - tasklet_kill(&port->push); + cancel_delayed_work_sync(&port->push); /* wait for old opens to finish */ wait_event(port->close_wait, gs_closed(port)); WARN_ON(port->port_usb != NULL); @@ -1345,23 +1267,24 @@ void gserial_free_line(unsigned char port_num) struct gs_port *port; mutex_lock(&ports[port_num].lock); - if (WARN_ON(!ports[port_num].port)) { + if (!ports[port_num].port) { mutex_unlock(&ports[port_num].lock); return; } port = ports[port_num].port; + gs_console_exit(port); ports[port_num].port = NULL; mutex_unlock(&ports[port_num].lock); gserial_free_port(port); tty_unregister_device(gs_tty_driver, port_num); - gserial_console_exit(); } EXPORT_SYMBOL_GPL(gserial_free_line); -int gserial_alloc_line(unsigned char *line_num) +int gserial_alloc_line_no_console(unsigned char *line_num) { struct usb_cdc_line_coding coding; + struct gs_port *port; struct device *tty_dev; int ret; int port_num; @@ -1384,24 +1307,35 @@ int gserial_alloc_line(unsigned char *line_num) /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */ - tty_dev = tty_port_register_device(&ports[port_num].port->port, + port = ports[port_num].port; + tty_dev = tty_port_register_device(&port->port, gs_tty_driver, port_num, NULL); if (IS_ERR(tty_dev)) { - struct gs_port *port; pr_err("%s: failed to register tty for port %d, err %ld\n", __func__, port_num, PTR_ERR(tty_dev)); ret = PTR_ERR(tty_dev); - port = ports[port_num].port; + mutex_lock(&ports[port_num].lock); ports[port_num].port = NULL; + mutex_unlock(&ports[port_num].lock); gserial_free_port(port); goto err; } *line_num = port_num; - gserial_console_init(); err: return ret; } +EXPORT_SYMBOL_GPL(gserial_alloc_line_no_console); + +int gserial_alloc_line(unsigned char *line_num) +{ + int ret = gserial_alloc_line_no_console(line_num); + + if (!ret && !*line_num) + gs_console_init(ports[*line_num].port); + + return ret; +} EXPORT_SYMBOL_GPL(gserial_alloc_line); /** @@ -1480,7 +1414,7 @@ int gserial_connect(struct gserial *gser, u8 port_num) gser->disconnect(gser); } - status = gs_console_connect(port_num); + status = gs_console_connect(port); spin_unlock_irqrestore(&port->port_lock, flags); return status; @@ -1509,20 +1443,26 @@ void gserial_disconnect(struct gserial *gser) if (!port) return; + spin_lock_irqsave(&serial_port_lock, flags); + /* tell the TTY glue not to do I/O here any more */ - spin_lock_irqsave(&port->port_lock, flags); + spin_lock(&port->port_lock); + + gs_console_disconnect(port); /* REVISIT as above: how best to track this? */ port->port_line_coding = gser->port_line_coding; port->port_usb = NULL; gser->ioport = NULL; - if (port->port.count > 0 || port->openclose) { + if (port->port.count > 0) { wake_up_interruptible(&port->drain_wait); if (port->port.tty) tty_hangup(port->port.tty); } - spin_unlock_irqrestore(&port->port_lock, flags); + port->suspended = false; + spin_unlock(&port->port_lock); + spin_unlock_irqrestore(&serial_port_lock, flags); /* disable endpoints, aborting down any active I/O */ usb_ep_disable(gser->out); @@ -1530,8 +1470,8 @@ void gserial_disconnect(struct gserial *gser) /* finally, free any unused/unusable I/O buffers */ spin_lock_irqsave(&port->port_lock, flags); - if (port->port.count == 0 && !port->openclose) - gs_buf_free(&port->port_write_buf); + if (port->port.count == 0) + kfifo_free(&port->port_write_buf); gs_free_requests(gser->out, &port->read_pool, NULL); gs_free_requests(gser->out, &port->read_queue, NULL); gs_free_requests(gser->in, &port->write_pool, NULL); @@ -1539,68 +1479,129 @@ void gserial_disconnect(struct gserial *gser) port->read_allocated = port->read_started = port->write_allocated = port->write_started = 0; - gs_console_disconnect(gser->in); spin_unlock_irqrestore(&port->port_lock, flags); } EXPORT_SYMBOL_GPL(gserial_disconnect); -static int userial_init(void) +void gserial_suspend(struct gserial *gser) +{ + struct gs_port *port; + unsigned long flags; + + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + if (port->write_busy || port->write_started) { + /* Wakeup to host if there are ongoing transfers */ + spin_unlock_irqrestore(&serial_port_lock, flags); + if (!gserial_wakeup_host(gser)) + return; + spin_lock_irqsave(&serial_port_lock, flags); + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); + port->suspended = true; + port->start_delayed = true; + spin_unlock_irqrestore(&port->port_lock, flags); +} +EXPORT_SYMBOL_GPL(gserial_suspend); + +void gserial_resume(struct gserial *gser) { + struct gs_port *port; + unsigned long flags; + + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); + port->suspended = false; + if (!port->start_delayed) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + pr_debug("delayed start ttyGS%d\n", port->port_num); + gs_start_io(port); + if (gser->connect) + gser->connect(gser); + port->start_delayed = false; + spin_unlock_irqrestore(&port->port_lock, flags); +} +EXPORT_SYMBOL_GPL(gserial_resume); + +static int __init userial_init(void) +{ + struct tty_driver *driver; unsigned i; int status; - gs_tty_driver = alloc_tty_driver(MAX_U_SERIAL_PORTS); - if (!gs_tty_driver) - return -ENOMEM; + driver = tty_alloc_driver(MAX_U_SERIAL_PORTS, TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(driver)) + return PTR_ERR(driver); - gs_tty_driver->driver_name = "g_serial"; - gs_tty_driver->name = "ttyGS"; + driver->driver_name = "g_serial"; + driver->name = "ttyGS"; /* uses dynamically assigned dev_t values */ - gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; - gs_tty_driver->subtype = SERIAL_TYPE_NORMAL; - gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; - gs_tty_driver->init_termios = tty_std_termios; + driver->type = TTY_DRIVER_TYPE_SERIAL; + driver->subtype = SERIAL_TYPE_NORMAL; + driver->init_termios = tty_std_termios; /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on * MS-Windows. Otherwise, most of these flags shouldn't affect * anything unless we were to actually hook up to a serial line. */ - gs_tty_driver->init_termios.c_cflag = + driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; - gs_tty_driver->init_termios.c_ispeed = 9600; - gs_tty_driver->init_termios.c_ospeed = 9600; + driver->init_termios.c_ispeed = 9600; + driver->init_termios.c_ospeed = 9600; - tty_set_operations(gs_tty_driver, &gs_tty_ops); + tty_set_operations(driver, &gs_tty_ops); for (i = 0; i < MAX_U_SERIAL_PORTS; i++) mutex_init(&ports[i].lock); /* export the driver ... */ - status = tty_register_driver(gs_tty_driver); + status = tty_register_driver(driver); if (status) { pr_err("%s: cannot register, err %d\n", __func__, status); goto fail; } + gs_tty_driver = driver; + pr_debug("%s: registered %d ttyGS* device%s\n", __func__, MAX_U_SERIAL_PORTS, - (MAX_U_SERIAL_PORTS == 1) ? "" : "s"); + str_plural(MAX_U_SERIAL_PORTS)); return status; fail: - put_tty_driver(gs_tty_driver); - gs_tty_driver = NULL; + tty_driver_kref_put(driver); return status; } module_init(userial_init); -static void userial_cleanup(void) +static void __exit userial_cleanup(void) { tty_unregister_driver(gs_tty_driver); - put_tty_driver(gs_tty_driver); + tty_driver_kref_put(gs_tty_driver); gs_tty_driver = NULL; } module_exit(userial_cleanup); +MODULE_DESCRIPTION("utilities for USB gadget \"serial port\"/TTY support"); MODULE_LICENSE("GPL"); |
