summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/function/u_serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/function/u_serial.c')
-rw-r--r--drivers/usb/gadget/function/u_serial.c605
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);
+}