diff options
Diffstat (limited to 'drivers/usb/gadget/function/u_serial.c')
-rw-r--r-- | drivers/usb/gadget/function/u_serial.c | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c new file mode 100644 index 0000000000..2ce3f1c791 --- /dev/null +++ b/drivers/usb/gadget/function/u_serial.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * u_serial.c - utilities for USB gadget "serial port"/TTY support + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This code also borrows from usbserial.c, which is + * 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) + */ + +/* #define VERBOSE_DEBUG */ + +#include <common.h> +#include <complete.h> +#include <usb/cdc.h> +#include <kfifo.h> +#include <clock.h> +#include <linux/err.h> +#include <dma.h> + +#include "u_serial.h" + + +/* + * This component encapsulates the TTY layer glue needed to provide basic + * "serial port" functionality through the USB gadget stack. Each such + * port is exposed through a /dev/ttyGS* node. + * + * After this module has been loaded, the individual TTY port can be requested + * (gserial_alloc_line()) and it will stay available until they are removed + * (gserial_free_line()). Each one may be connected to a USB function + * (gserial_connect), or disconnected (with gserial_disconnect) when the USB + * host issues a config change event. Data can only flow when the port is + * connected to the host. + * + * A given TTY port can be made available in multiple configurations. + * For example, each one might expose a ttyGS0 node which provides a + * login application. In one case that might use CDC ACM interface 0, + * while another configuration might use interface 3 for that. The + * work to handle that (including descriptor management) is not part + * of this component. + * + * Configurations may expose more than one TTY port. For example, if + * ttyGS0 provides login service, then ttyGS1 might provide dialer access + * for a telephone or fax link. And ttyGS2 might be something that just + * needs a simple byte stream interface for some messaging protocol that + * is managed in userspace ... OBEX, PTP, and MTP have been mentioned. + */ + +#define PREFIX "ttyGS" + +/* + * gserial is the lifecycle interface, used by USB functions + * gs_port is the I/O nexus, used by the tty driver + * 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}(). each gserial + * instance can wrap its own USB control protocol. + * gserial->ioport == usb_ep->driver_data ... gs_port + * gs_port->port_usb ... gserial + * + * gs_port <---> tty_struct ... links will be null when the TTY file + * isn't opened; managed by gs_open()/gs_close() + * gserial->port_tty ... tty_struct + * tty_struct->driver_data ... gserial + */ + +/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the + * next layer of buffering. For TX that's a circular buffer; for RX + * consider it a NOP. A third layer is provided by the TTY code. + */ +#define QUEUE_SIZE 16 +#define WRITE_BUF_SIZE 8192 /* TX only */ +#define RECV_FIFO_SIZE (1024 * 8) + +/* circular buffer */ +struct gs_buf { + unsigned buf_size; + char *buf_buf; + char *buf_get; + char *buf_put; +}; + +/* + * The port structure holds info for each port, one for each minor number + * (and thus for each /dev/ node). + */ +struct gs_port { + struct gserial *port_usb; + struct console_device cdev; + struct kfifo *recv_fifo; + + u8 port_num; + + struct list_head read_pool; + unsigned read_nb_queued; + + struct list_head write_pool; + + /* REVISIT this state ... */ + struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ +}; + +static struct portmaster { + struct gs_port *port; +} ports[MAX_U_SERIAL_PORTS]; + +#define GS_CLOSE_TIMEOUT 15 /* seconds */ + +static unsigned gs_start_rx(struct gs_port *port) +{ + struct list_head *pool = &port->read_pool; + struct usb_ep *out = port->port_usb->out; + unsigned started = 0; + + while (!list_empty(pool) && + ((out->maxpacket * (port->read_nb_queued + 1) + + kfifo_len(port->recv_fifo)) < RECV_FIFO_SIZE)) { + struct usb_request *req; + int status; + + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + req->length = out->maxpacket; + + /* drop lock while we call out; the controller driver + * may need to call us back (e.g. for disconnect) + */ + port->read_nb_queued++; + status = usb_ep_queue(out, req); + + if (status) { + pr_debug("%s: %s %s err %d\n", + __func__, "queue", out->name, status); + list_add(&req->list, pool); + break; + } + started++; + + /* abort immediately after disconnect */ + if (!port->port_usb) + break; + } + return started; +} + +/*-------------------------------------------------------------------------*/ + +static void gs_read_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gs_port *port = ep->driver_data; + + if (req->status == -ESHUTDOWN) + return; + + kfifo_put(port->recv_fifo, req->buf, req->actual); + list_add_tail(&req->list, &port->read_pool); + port->read_nb_queued--; + + gs_start_rx(port); +} + +/*-------------------------------------------------------------------------*/ + +/* I/O glue between TTY (upper) and USB function (lower) driver layers */ + +static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gs_port *port = ep->driver_data; + + list_add(&req->list, &port->write_pool); + + switch (req->status) { + default: + /* presumably a transient fault */ + pr_warning("%s: unexpected %s status %d\n", + __func__, ep->name, req->status); + /* FALL THROUGH */ + case 0: + /* normal completion */ + break; + + case -ESHUTDOWN: + /* disconnect */ + pr_vdebug("%s: %s shutdown\n", __func__, ep->name); + break; + } +} + +/* + * gs_alloc_req + * + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or NULL if there is an error. + */ +struct usb_request * +gs_alloc_req(struct usb_ep *ep, unsigned len) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep); + + if (req != NULL) { + req->length = len; + req->buf = dma_alloc(len); + } + + return req; +} +EXPORT_SYMBOL_GPL(gs_alloc_req); + +/* + * gs_free_req + * + * Free a usb_request and its buffer. + */ +void gs_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} +EXPORT_SYMBOL_GPL(gs_free_req); + +static void gs_free_requests(struct usb_ep *ep, struct list_head *head) +{ + struct usb_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + gs_free_req(ep, req); + } +} + +static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head, + void (*fn)(struct usb_ep *, struct usb_request *)) +{ + int i; + struct usb_request *req; + + /* Pre-allocate up to QUEUE_SIZE transfers, but if we can't + * do quite that many this time, don't fail ... we just won't + * be as speedy as we might otherwise be. + */ + for (i = 0; i < QUEUE_SIZE; i++) { + req = gs_alloc_req(ep, ep->maxpacket); + if (!req) + return list_empty(head) ? -ENOMEM : 0; + req->complete = fn; + list_add_tail(&req->list, head); + } + return 0; +} + +/** + * gs_start_io - start USB I/O streams + * @dev: encapsulates endpoints 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 + * this port. If nothing is listening on the host side, we may + * be pointlessly filling up our TX buffers and FIFO. + */ +static int gs_start_io(struct gs_port *port) +{ + struct list_head *head = &port->read_pool; + struct usb_ep *ep = port->port_usb->out; + int status; + unsigned started; + + /* Allocate RX and TX I/O buffers. We can't easily do this much + * earlier (with GFP_KERNEL) because the requests are coupled to + * endpoints, as are the packet sizes we'll be using. Different + * configurations may use different endpoints with a given port; + * and high speed vs full speed changes packet sizes too. + */ + status = gs_alloc_requests(ep, head, gs_read_complete); + if (status) + return status; + + status = gs_alloc_requests(port->port_usb->in, &port->write_pool, + gs_write_complete); + if (status) { + gs_free_requests(ep, head); + return status; + } + + /* queue read requests */ + port->read_nb_queued = 0; + started = gs_start_rx(port); + + /* unblock any pending writes into our circular buffer */ + if (!started) { + gs_free_requests(ep, head); + gs_free_requests(port->port_usb->in, &port->write_pool); + status = -EIO; + } + + return status; +} + +/*-------------------------------------------------------------------------*/ + +static int +gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) +{ + struct gs_port *port; + int ret = 0; + + if (ports[port_num].port) { + ret = -EBUSY; + goto out; + } + + port = kzalloc(sizeof(struct gs_port), GFP_KERNEL); + if (port == NULL) { + ret = -ENOMEM; + goto out; + } + + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->write_pool); + + port->port_num = port_num; + port->port_line_coding = *coding; + + ports[port_num].port = port; +out: + return ret; +} + +static void gserial_free_port(struct gs_port *port) +{ + kfree(port); +} + +void gserial_free_line(unsigned char port_num) +{ + struct gs_port *port; + + if (WARN_ON(!ports[port_num].port)) + return; + + port = ports[port_num].port; + ports[port_num].port = NULL; + + gserial_free_port(port); +} +EXPORT_SYMBOL_GPL(gserial_free_line); + +int gserial_alloc_line(unsigned char *line_num) +{ + struct usb_cdc_line_coding coding; + int ret; + int port_num; + + coding.dwDTERate = cpu_to_le32(9600); + coding.bCharFormat = 8; + coding.bParityType = USB_CDC_NO_PARITY; + coding.bDataBits = USB_CDC_1_STOP_BITS; + + for (port_num = 0; port_num < MAX_U_SERIAL_PORTS; port_num++) { + ret = gs_port_alloc(port_num, &coding); + if (ret == -EBUSY) + continue; + if (ret) + return ret; + break; + } + if (ret) + return ret; + + /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */ + + *line_num = port_num; + + return ret; +} +EXPORT_SYMBOL_GPL(gserial_alloc_line); + +static void serial_putc(struct console_device *cdev, char c) +{ + struct gs_port *port = container_of(cdev, + struct gs_port, cdev); + struct list_head *pool = &port->write_pool; + struct usb_ep *in; + struct usb_request *req; + int status; + uint64_t to; + + if (list_empty(pool)) + return; + in = port->port_usb->in; + req = list_entry(pool->next, struct usb_request, list); + + req->length = 1; + list_del(&req->list); + + *(unsigned char *)req->buf = c; + status = usb_ep_queue(in, req); + + to = get_time_ns(); + while (status >= 0 && list_empty(pool)) { + status = usb_gadget_poll(); + if (is_timeout(to, 300 * MSECOND)) + break; + } +} + +static int serial_tstc(struct console_device *cdev) +{ + struct gs_port *port = container_of(cdev, + struct gs_port, cdev); + + gs_start_rx(port); + return (kfifo_len(port->recv_fifo) == 0) ? 0 : 1; +} + +static int serial_getc(struct console_device *cdev) +{ + struct gs_port *port = container_of(cdev, + struct gs_port, cdev); + unsigned char ch; + uint64_t to; + + if (!port->port_usb) + return -EIO; + to = get_time_ns(); + while (kfifo_getc(port->recv_fifo, &ch)) { + usb_gadget_poll(); + if (is_timeout(to, 300 * MSECOND)) + goto timeout; + } + + gs_start_rx(port); + return ch; +timeout: + gs_start_rx(port); + return -ETIMEDOUT; +} + +static void serial_flush(struct console_device *cdev) +{ +} + +static int serial_setbaudrate(struct console_device *cdev, int baudrate) +{ + return 0; +} + +/** + * gserial_connect - notify TTY I/O glue that USB link is active + * @gser: the function, set up with endpoints and descriptors + * @port_num: which port is active + * Context: any (usually from irq) + * + * This is called activate endpoints and let the TTY layer know that + * the connection is active ... not unlike "carrier detect". It won't + * necessarily start I/O queues; unless the TTY is held open by any + * task, there would be no point. However, the endpoints will be + * activated so the USB host can perform I/O, subject to basic USB + * hardware flow control. + * + * Caller needs to have set up the endpoints and USB function in @dev + * before calling this, as well as the appropriate (speed-specific) + * endpoint descriptors, and also have allocate @port_num by calling + * @gserial_alloc_line(). + * + * Returns negative errno or zero. + * On success, ep->driver_data will be overwritten. + */ +int gserial_connect(struct gserial *gser, u8 port_num) +{ + struct gs_port *port; + int status; + struct console_device *cdev; + + if (port_num >= MAX_U_SERIAL_PORTS) + return -ENXIO; + + port = ports[port_num].port; + if (!port) { + pr_err("serial line %d not allocated.\n", port_num); + return -EINVAL; + } + if (port->port_usb) { + pr_err("serial line %d is in use.\n", port_num); + return -EBUSY; + } + + /* activate the endpoints */ + status = usb_ep_enable(gser->in); + if (status < 0) + return status; + gser->in->driver_data = port; + + status = usb_ep_enable(gser->out); + if (status < 0) + goto fail_out; + gser->out->driver_data = port; + + /* then tell the tty glue that I/O can work */ + gser->ioport = port; + port->port_usb = gser; + + /* REVISIT unclear how best to handle this state... + * we don't really couple it with the Linux TTY. + */ + gser->port_line_coding = port->port_line_coding; + + port->recv_fifo = kfifo_alloc(RECV_FIFO_SIZE); + + /*printf("gserial_connect: start ttyGS%d\n", port->port_num);*/ + gs_start_io(port); + if (gser->connect) + gser->connect(gser); + + cdev = &port->cdev; + cdev->tstc = serial_tstc; + cdev->putc = serial_putc; + cdev->getc = serial_getc; + cdev->flush = serial_flush; + cdev->setbrg = serial_setbaudrate; + cdev->devname = "usbserial"; + cdev->devid = DEVICE_ID_SINGLE; + + status = console_register(cdev); + if (status) + goto fail_out; + + if (IS_ENABLED(CONFIG_CONSOLE_FULL)) + console_set_active(cdev, CONSOLE_STDIN | CONSOLE_STDOUT | + CONSOLE_STDERR); + + /* REVISIT if waiting on "carrier detect", signal. */ + + /* if it's already open, start I/O ... and notify the serial + * protocol about open/close status (connect/disconnect). + */ + if (1) { + pr_debug("gserial_connect: start ttyGS%d\n", port->port_num); + if (gser->connect) + gser->connect(gser); + } else { + if (gser->disconnect) + gser->disconnect(gser); + } + + return status; + +fail_out: + usb_ep_disable(gser->in); + gser->in->driver_data = NULL; + return status; +} +EXPORT_SYMBOL_GPL(gserial_connect); + +/** + * gserial_disconnect - notify TTY I/O glue that USB link is inactive + * @gser: the function, on which gserial_connect() was called + * Context: any (usually from irq) + * + * This is called to deactivate endpoints and let the TTY layer know + * that the connection went inactive ... not unlike "hangup". + * + * On return, the state is as if gserial_connect() had never been called; + * there is no active USB I/O on these endpoints. + */ +void gserial_disconnect(struct gserial *gser) +{ + struct gs_port *port = gser->ioport; + struct console_device *cdev; + + if (!port) + return; + + cdev = &port->cdev; + + /* tell the TTY glue not to do I/O here any more */ + console_unregister(cdev); + + /* REVISIT as above: how best to track this? */ + port->port_line_coding = gser->port_line_coding; + + port->port_usb = NULL; + gser->ioport = NULL; + + /* disable endpoints, aborting down any active I/O */ + usb_ep_disable(gser->out); + gser->out->driver_data = NULL; + + usb_ep_disable(gser->in); + gser->in->driver_data = NULL; + + /* finally, free any unused/unusable I/O buffers */ + gs_free_requests(gser->out, &port->read_pool); + gs_free_requests(gser->in, &port->write_pool); + + kfifo_free(port->recv_fifo); +} |