diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 58bec7a215ca..3ec8493ed558 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -231,6 +231,9 @@ config USB_F_QDSS config USB_F_CCID tristate +config USB_F_CDEV + tristate + # this first set of drivers all depend on bulk-capable hardware. config USB_CONFIGFS @@ -555,6 +558,18 @@ config USB_CONFIGFS_F_CCID allowing a userspace component to be able to provide the implementation necessary to interface with the smartcard. +config USB_CONFIGFS_F_CDEV + tristate "USB Serial Character function" + select USB_F_CDEV + depends on USB_CONFIGFS + help + The serial character function is a generic function driver that + exposes a pair of bulk IN and OUT endpoints which are backed by + a character device and mapped to its read/write routines. The + function also supports a single interrupt IN endpoint for + asynchronous notification to the host. This driver is typically + used to support DUN/NMEA functions. + choice tristate "USB Gadget precomposed configurations" default USB_ETH diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index 7c063287a4e1..6dc41b653e55 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -60,3 +60,5 @@ usb_f_qdss-y := f_qdss.o u_qdss.o obj-$(CONFIG_USB_F_QDSS) += usb_f_qdss.o usb_f_ccid-y := f_ccid.o obj-$(CONFIG_USB_F_CCID) += usb_f_ccid.o +usb_f_cdev-y := f_cdev.o +obj-$(CONFIG_USB_F_CDEV) += usb_f_cdev.o diff --git a/drivers/usb/gadget/function/f_cdev.c b/drivers/usb/gadget/function/f_cdev.c new file mode 100644 index 000000000000..e237679fbd0a --- /dev/null +++ b/drivers/usb/gadget/function/f_cdev.c @@ -0,0 +1,1990 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011, 2013-2019, The Linux Foundation. All rights reserved. + * Linux Foundation chooses to take subject only to the GPLv2 license terms, + * and distributes only under these terms. + * + * This code also borrows from drivers/usb/gadget/u_serial.c, which is + * Copyright (C) 2000 - 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2000 Peter Berger (pberger@brimson.com) + * + * f_cdev_read() API implementation is using borrowed code from + * drivers/usb/gadget/legacy/printer.c, which is + * Copyright (C) 2003-2005 David Brownell + * Copyright (C) 2006 Craig W. Nadler + */ + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "at_usb" +#define MODULE_NAME "msm_usb_bridge" +#define NUM_INSTANCE 4 + +#define MAX_CDEV_INST_NAME 15 +#define MAX_CDEV_FUNC_NAME 5 + +#define BRIDGE_RX_QUEUE_SIZE 8 +#define BRIDGE_RX_BUF_SIZE 2048 +#define BRIDGE_TX_QUEUE_SIZE 8 +#define BRIDGE_TX_BUF_SIZE 2048 + +#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */ +#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */ + +struct cserial { + struct usb_function func; + struct usb_ep *in; + struct usb_ep *out; + struct usb_ep *notify; + struct usb_request *notify_req; + struct usb_cdc_line_coding port_line_coding; + u8 pending; + u8 q_again; + u8 data_id; + u16 serial_state; + u16 port_handshake_bits; + /* control signal callbacks*/ + unsigned int (*get_dtr)(struct cserial *p); + unsigned int (*get_rts)(struct cserial *p); + + /* notification callbacks */ + void (*connect)(struct cserial *p); + void (*disconnect)(struct cserial *p); + int (*send_break)(struct cserial *p, int duration); + unsigned int (*send_carrier_detect)(struct cserial *p, + unsigned int val); + unsigned int (*send_ring_indicator)(struct cserial *p, + unsigned int val); + int (*send_modem_ctrl_bits)(struct cserial *p, int ctrl_bits); + + /* notification changes to modem */ + void (*notify_modem)(void *port, int ctrl_bits); +}; + +struct f_cdev { + struct cdev fcdev_cdev; + struct device *dev; + unsigned int port_num; + char name[sizeof(DEVICE_NAME) + 2]; + int minor; + + spinlock_t port_lock; + + wait_queue_head_t open_wq; + wait_queue_head_t read_wq; + + struct list_head read_pool; + struct list_head read_queued; + struct list_head write_pool; + + /* current active USB RX request */ + struct usb_request *current_rx_req; + /* number of pending bytes */ + size_t pending_rx_bytes; + /* current USB RX buffer */ + u8 *current_rx_buf; + + struct cserial port_usb; + +#define ACM_CTRL_DTR 0x01 +#define ACM_CTRL_RTS 0x02 +#define ACM_CTRL_DCD 0x01 +#define ACM_CTRL_DSR 0x02 +#define ACM_CTRL_BRK 0x04 +#define ACM_CTRL_RI 0x08 + + unsigned int cbits_to_modem; + bool cbits_updated; + + struct workqueue_struct *fcdev_wq; + bool is_connected; + bool port_open; + + unsigned long nbytes_from_host; + unsigned long nbytes_to_host; + unsigned long nbytes_to_port_bridge; + unsigned long nbytes_from_port_bridge; + + struct dentry *debugfs_root; + + /* To test remote wakeup using debugfs */ + u8 debugfs_rw_enable; +}; + +struct f_cdev_opts { + struct usb_function_instance func_inst; + struct f_cdev *port; + char *func_name; + u8 port_num; +}; + +static int major, minors; +struct class *fcdev_classp; +static DEFINE_IDA(chardev_ida); +static DEFINE_MUTEX(chardev_ida_lock); + +static int usb_cser_alloc_chardev_region(void); +static void usb_cser_chardev_deinit(void); +static void usb_cser_read_complete(struct usb_ep *ep, struct usb_request *req); +static int usb_cser_connect(struct f_cdev *port); +static void usb_cser_disconnect(struct f_cdev *port); +static struct f_cdev *f_cdev_alloc(char *func_name, int portno); +static void usb_cser_free_req(struct usb_ep *ep, struct usb_request *req); +static void usb_cser_debugfs_exit(struct f_cdev *port); + +static struct usb_interface_descriptor cser_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc cser_header_desc = { + .bLength = sizeof(cser_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor +cser_call_mgmt_descriptor = { + .bLength = sizeof(cser_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + /* .bDataInterface = DYNAMIC */ +}; + +static struct usb_cdc_acm_descriptor cser_descriptor = { + .bLength = sizeof(cser_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc cser_union_desc = { + .bLength = sizeof(cser_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* full speed support: */ +static struct usb_endpoint_descriptor cser_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = 1 << GS_LOG2_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor cser_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor cser_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *cser_fs_function[] = { + (struct usb_descriptor_header *) &cser_interface_desc, + (struct usb_descriptor_header *) &cser_header_desc, + (struct usb_descriptor_header *) &cser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &cser_descriptor, + (struct usb_descriptor_header *) &cser_union_desc, + (struct usb_descriptor_header *) &cser_fs_notify_desc, + (struct usb_descriptor_header *) &cser_fs_in_desc, + (struct usb_descriptor_header *) &cser_fs_out_desc, + NULL, +}; + +/* high speed support: */ +static struct usb_endpoint_descriptor cser_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_LOG2_NOTIFY_INTERVAL+4, +}; + +static struct usb_endpoint_descriptor cser_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor cser_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *cser_hs_function[] = { + (struct usb_descriptor_header *) &cser_interface_desc, + (struct usb_descriptor_header *) &cser_header_desc, + (struct usb_descriptor_header *) &cser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &cser_descriptor, + (struct usb_descriptor_header *) &cser_union_desc, + (struct usb_descriptor_header *) &cser_hs_notify_desc, + (struct usb_descriptor_header *) &cser_hs_in_desc, + (struct usb_descriptor_header *) &cser_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor cser_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor cser_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor cser_ss_bulk_comp_desc = { + .bLength = sizeof(cser_ss_bulk_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_endpoint_descriptor cser_ss_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_LOG2_NOTIFY_INTERVAL+4, +}; + +static struct usb_ss_ep_comp_descriptor cser_ss_notify_comp_desc = { + .bLength = sizeof(cser_ss_notify_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(GS_NOTIFY_MAXPACKET), +}; + +static struct usb_descriptor_header *cser_ss_function[] = { + (struct usb_descriptor_header *) &cser_interface_desc, + (struct usb_descriptor_header *) &cser_header_desc, + (struct usb_descriptor_header *) &cser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &cser_descriptor, + (struct usb_descriptor_header *) &cser_union_desc, + (struct usb_descriptor_header *) &cser_ss_notify_desc, + (struct usb_descriptor_header *) &cser_ss_notify_comp_desc, + (struct usb_descriptor_header *) &cser_ss_in_desc, + (struct usb_descriptor_header *) &cser_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &cser_ss_out_desc, + (struct usb_descriptor_header *) &cser_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ +static struct usb_string cser_string_defs[] = { + [0].s = "CDEV Serial", + { } /* end of list */ +}; + +static struct usb_gadget_strings cser_string_table = { + .language = 0x0409, /* en-us */ + .strings = cser_string_defs, +}; + +static struct usb_gadget_strings *usb_cser_strings[] = { + &cser_string_table, + NULL, +}; + +static inline struct f_cdev *func_to_port(struct usb_function *f) +{ + return container_of(f, struct f_cdev, port_usb.func); +} + +static inline struct f_cdev *cser_to_port(struct cserial *cser) +{ + return container_of(cser, struct f_cdev, port_usb); +} + +static unsigned int convert_acm_sigs_to_uart(unsigned int acm_sig) +{ + unsigned int uart_sig = 0; + + acm_sig &= (ACM_CTRL_DTR | ACM_CTRL_RTS); + if (acm_sig & ACM_CTRL_DTR) + uart_sig |= TIOCM_DTR; + + if (acm_sig & ACM_CTRL_RTS) + uart_sig |= TIOCM_RTS; + + return uart_sig; +} + +static void port_complete_set_line_coding(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_cdev *port = ep->driver_data; + struct usb_composite_dev *cdev = port->port_usb.func.config->cdev; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "port(%s) completion, err %d\n", + port->name, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(port->port_usb.port_line_coding)) { + dev_dbg(&cdev->gadget->dev, "port(%s) short resp, len %d\n", + port->name, req->actual); + usb_ep_set_halt(ep); + } else { + struct usb_cdc_line_coding *value = req->buf; + + port->port_usb.port_line_coding = *value; + } +} + +static void usb_cser_free_func(struct usb_function *f) +{ + /* Do nothing as cser_alloc() doesn't alloc anything. */ +} + +static int +usb_cser_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_cdev *port = func_to_port(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* SET_LINE_CODING ... just read and save what the host sends */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + if (w_length != sizeof(struct usb_cdc_line_coding)) + goto invalid; + + value = w_length; + cdev->gadget->ep0->driver_data = port; + req->complete = port_complete_set_line_coding; + break; + + /* GET_LINE_CODING ... return what host sent, or initial value */ + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + value = min_t(unsigned int, w_length, + sizeof(struct usb_cdc_line_coding)); + memcpy(req->buf, &port->port_usb.port_line_coding, value); + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + + value = 0; + port->port_usb.port_handshake_bits = w_value; + pr_debug("USB_CDC_REQ_SET_CONTROL_LINE_STATE: DTR:%d RST:%d\n", + w_value & ACM_CTRL_DTR ? 1 : 0, + w_value & ACM_CTRL_RTS ? 1 : 0); + if (port->port_usb.notify_modem) + port->port_usb.notify_modem(port, w_value); + + break; + + default: +invalid: + dev_dbg(&cdev->gadget->dev, + "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + dev_dbg(&cdev->gadget->dev, + "port(%s) req%02x.%02x v%04x i%04x l%d\n", + port->name, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + pr_err("port response on (%s), err %d\n", + port->name, value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int usb_cser_set_alt(struct usb_function *f, unsigned int intf, + unsigned int alt) +{ + struct f_cdev *port = func_to_port(f); + struct usb_composite_dev *cdev = f->config->cdev; + int rc = 0; + + if (port->port_usb.notify->driver_data) { + dev_dbg(&cdev->gadget->dev, + "reset port(%s)\n", port->name); + usb_ep_disable(port->port_usb.notify); + } + + if (!port->port_usb.notify->desc) { + if (config_ep_by_speed(cdev->gadget, f, + port->port_usb.notify)) { + port->port_usb.notify->desc = NULL; + return -EINVAL; + } + } + + rc = usb_ep_enable(port->port_usb.notify); + if (rc) { + dev_err(&cdev->gadget->dev, "can't enable %s, result %d\n", + port->port_usb.notify->name, rc); + return rc; + } + port->port_usb.notify->driver_data = port; + + if (port->port_usb.in->driver_data) { + dev_dbg(&cdev->gadget->dev, + "reset port(%s)\n", port->name); + usb_cser_disconnect(port); + } + if (!port->port_usb.in->desc || !port->port_usb.out->desc) { + dev_dbg(&cdev->gadget->dev, + "activate port(%s)\n", port->name); + if (config_ep_by_speed(cdev->gadget, f, port->port_usb.in) || + config_ep_by_speed(cdev->gadget, f, + port->port_usb.out)) { + port->port_usb.in->desc = NULL; + port->port_usb.out->desc = NULL; + return -EINVAL; + } + } + + usb_cser_connect(port); + return rc; +} + +static int usb_cser_func_suspend(struct usb_function *f, u8 options) +{ + bool func_wakeup_allowed; + + func_wakeup_allowed = + ((options & FUNC_SUSPEND_OPT_RW_EN_MASK) != 0); + + f->func_wakeup_allowed = func_wakeup_allowed; + if (options & FUNC_SUSPEND_OPT_SUSP_MASK) { + if (!f->func_is_suspended) + f->func_is_suspended = true; + } else { + if (f->func_is_suspended) + f->func_is_suspended = false; + } + return 0; +} + +static int usb_cser_get_status(struct usb_function *f) +{ + bool remote_wakeup_en_status = f->func_wakeup_allowed ? 1 : 0; + + return (remote_wakeup_en_status << FUNC_WAKEUP_ENABLE_SHIFT) | + (1 << FUNC_WAKEUP_CAPABLE_SHIFT); +} + +static void usb_cser_disable(struct usb_function *f) +{ + struct f_cdev *port = func_to_port(f); + struct usb_composite_dev *cdev = f->config->cdev; + + dev_dbg(&cdev->gadget->dev, + "port(%s) deactivated\n", port->name); + + usb_cser_disconnect(port); + usb_ep_disable(port->port_usb.notify); + port->port_usb.notify->driver_data = NULL; +} + +static int usb_cser_notify(struct f_cdev *port, u8 type, u16 value, + void *data, unsigned int length) +{ + struct usb_ep *ep = port->port_usb.notify; + struct usb_request *req; + struct usb_cdc_notification *notify; + const unsigned int len = sizeof(*notify) + length; + void *buf; + int status; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->is_connected) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_debug("%s: port disconnected\n", __func__); + return -ENODEV; + } + + req = port->port_usb.notify_req; + + req->length = len; + notify = req->buf; + buf = notify + 1; + + notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + notify->bNotificationType = type; + notify->wValue = cpu_to_le16(value); + notify->wIndex = cpu_to_le16(port->port_usb.data_id); + notify->wLength = cpu_to_le16(length); + /* 2 byte data copy */ + memcpy(buf, data, length); + spin_unlock_irqrestore(&port->port_lock, flags); + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status < 0) { + pr_err("port %s can't notify serial state, %d\n", + port->name, status); + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb.pending = false; + spin_unlock_irqrestore(&port->port_lock, flags); + } + + return status; +} + +static int port_notify_serial_state(struct cserial *cser) +{ + struct f_cdev *port = cser_to_port(cser); + int status; + unsigned long flags; + struct usb_composite_dev *cdev = port->port_usb.func.config->cdev; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb.pending) { + port->port_usb.pending = true; + spin_unlock_irqrestore(&port->port_lock, flags); + dev_dbg(&cdev->gadget->dev, "port %d serial state %04x\n", + port->port_num, port->port_usb.serial_state); + status = usb_cser_notify(port, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &port->port_usb.serial_state, + sizeof(port->port_usb.serial_state)); + spin_lock_irqsave(&port->port_lock, flags); + } else { + port->port_usb.q_again = true; + status = 0; + } + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; +} + +static void usb_cser_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_cdev *port = req->context; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb.pending = false; + if (req->status != -ESHUTDOWN && port->port_usb.q_again) { + port->port_usb.q_again = false; + spin_unlock_irqrestore(&port->port_lock, flags); + port_notify_serial_state(&port->port_usb); + spin_lock_irqsave(&port->port_lock, flags); + } + spin_unlock_irqrestore(&port->port_lock, flags); +} +static void dun_cser_connect(struct cserial *cser) +{ + cser->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + port_notify_serial_state(cser); +} + +unsigned int dun_cser_get_dtr(struct cserial *cser) +{ + if (cser->port_handshake_bits & ACM_CTRL_DTR) + return 1; + else + return 0; +} + +unsigned int dun_cser_get_rts(struct cserial *cser) +{ + if (cser->port_handshake_bits & ACM_CTRL_RTS) + return 1; + else + return 0; +} + +unsigned int dun_cser_send_carrier_detect(struct cserial *cser, + unsigned int yes) +{ + u16 state; + + state = cser->serial_state; + state &= ~ACM_CTRL_DCD; + if (yes) + state |= ACM_CTRL_DCD; + + cser->serial_state = state; + return port_notify_serial_state(cser); +} + +unsigned int dun_cser_send_ring_indicator(struct cserial *cser, + unsigned int yes) +{ + u16 state; + + state = cser->serial_state; + state &= ~ACM_CTRL_RI; + if (yes) + state |= ACM_CTRL_RI; + + cser->serial_state = state; + return port_notify_serial_state(cser); +} + +static void dun_cser_disconnect(struct cserial *cser) +{ + cser->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + port_notify_serial_state(cser); +} + +static int dun_cser_send_break(struct cserial *cser, int duration) +{ + u16 state; + + state = cser->serial_state; + state &= ~ACM_CTRL_BRK; + if (duration) + state |= ACM_CTRL_BRK; + + cser->serial_state = state; + return port_notify_serial_state(cser); +} + +static int dun_cser_send_ctrl_bits(struct cserial *cser, int ctrl_bits) +{ + cser->serial_state = ctrl_bits; + return port_notify_serial_state(cser); +} + +static void usb_cser_free_req(struct usb_ep *ep, struct usb_request *req) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + req = NULL; + } +} + +static void usb_cser_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_init(&req->list); + usb_cser_free_req(ep, req); + } +} + +static struct usb_request * +usb_cser_alloc_req(struct usb_ep *ep, unsigned int len, gfp_t flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, flags); + if (!req) { + pr_err("usb alloc request failed\n"); + return 0; + } + + req->length = len; + req->buf = kmalloc(len, flags); + if (!req->buf) { + pr_err("request buf allocation failed\n"); + usb_ep_free_request(ep, req); + return 0; + } + + return req; +} + +static int usb_cser_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_cdev *port = func_to_port(f); + int status; + struct usb_ep *ep; + + if (cser_string_defs[0].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + cser_string_defs[0].id = status; + } + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + port->port_usb.data_id = status; + cser_interface_desc.bInterfaceNumber = status; + + status = -ENODEV; + ep = usb_ep_autoconfig(cdev->gadget, &cser_fs_in_desc); + if (!ep) + goto fail; + port->port_usb.in = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &cser_fs_out_desc); + if (!ep) + goto fail; + port->port_usb.out = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &cser_fs_notify_desc); + if (!ep) + goto fail; + port->port_usb.notify = ep; + ep->driver_data = cdev; + /* allocate notification */ + port->port_usb.notify_req = usb_cser_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, GFP_KERNEL); + if (!port->port_usb.notify_req) + goto fail; + + port->port_usb.notify_req->complete = usb_cser_notify_complete; + port->port_usb.notify_req->context = port; + + cser_hs_in_desc.bEndpointAddress = cser_fs_in_desc.bEndpointAddress; + cser_hs_out_desc.bEndpointAddress = cser_fs_out_desc.bEndpointAddress; + + cser_ss_in_desc.bEndpointAddress = cser_fs_in_desc.bEndpointAddress; + cser_ss_out_desc.bEndpointAddress = cser_fs_out_desc.bEndpointAddress; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + cser_hs_notify_desc.bEndpointAddress = + cser_fs_notify_desc.bEndpointAddress; + } + if (gadget_is_superspeed(c->cdev->gadget)) { + cser_ss_notify_desc.bEndpointAddress = + cser_fs_notify_desc.bEndpointAddress; + } + + status = usb_assign_descriptors(f, cser_fs_function, cser_hs_function, + cser_ss_function, cser_ss_function); + if (status) + goto fail; + + dev_dbg(&cdev->gadget->dev, "usb serial port(%d): %s speed IN/%s OUT/%s\n", + port->port_num, + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + port->port_usb.in->name, port->port_usb.out->name); + return 0; + +fail: + if (port->port_usb.notify_req) + usb_cser_free_req(port->port_usb.notify, + port->port_usb.notify_req); + + if (port->port_usb.notify) + port->port_usb.notify->driver_data = NULL; + if (port->port_usb.out) + port->port_usb.out->driver_data = NULL; + if (port->port_usb.in) + port->port_usb.in->driver_data = NULL; + + pr_err("%s: can't bind, err %d\n", f->name, status); + return status; +} + +static void cser_free_inst(struct usb_function_instance *fi) +{ + struct f_cdev_opts *opts; + + opts = container_of(fi, struct f_cdev_opts, func_inst); + + if (opts->port) { + device_destroy(fcdev_classp, MKDEV(major, opts->port->minor)); + cdev_del(&opts->port->fcdev_cdev); + usb_cser_debugfs_exit(opts->port); + } + usb_cser_chardev_deinit(); + kfree(opts->func_name); + kfree(opts->port); + kfree(opts); +} + +static void usb_cser_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_cdev *port = func_to_port(f); + + usb_free_all_descriptors(f); + usb_cser_free_req(port->port_usb.notify, port->port_usb.notify_req); +} + +static int usb_cser_alloc_requests(struct usb_ep *ep, struct list_head *head, + int num, int size, + void (*cb)(struct usb_ep *ep, struct usb_request *)) +{ + int i; + struct usb_request *req; + + pr_debug("ep:%pK head:%p num:%d size:%d cb:%p\n", + ep, head, num, size, cb); + + for (i = 0; i < num; i++) { + req = usb_cser_alloc_req(ep, size, GFP_ATOMIC); + if (!req) { + pr_debug("req allocated:%d\n", i); + return list_empty(head) ? -ENOMEM : 0; + } + req->complete = cb; + list_add_tail(&req->list, head); + } + + return 0; +} + +static void usb_cser_start_rx(struct f_cdev *port) +{ + struct list_head *pool; + struct usb_ep *ep; + unsigned long flags; + int ret; + + pr_debug("start RX(USB OUT)\n"); + if (!port) { + pr_err("port is null\n"); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + if (!(port->is_connected && port->port_open)) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_debug("can't start rx.\n"); + return; + } + + pool = &port->read_pool; + ep = port->port_usb.out; + + while (!list_empty(pool)) { + struct usb_request *req; + + req = list_entry(pool->next, struct usb_request, list); + list_del_init(&req->list); + req->length = BRIDGE_RX_BUF_SIZE; + req->complete = usb_cser_read_complete; + spin_unlock_irqrestore(&port->port_lock, flags); + ret = usb_ep_queue(ep, req, GFP_KERNEL); + spin_lock_irqsave(&port->port_lock, flags); + if (ret) { + pr_err("port(%d):%pK usb ep(%s) queue failed\n", + port->port_num, port, ep->name); + list_add(&req->list, pool); + break; + } + } + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void usb_cser_read_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_cdev *port = ep->driver_data; + unsigned long flags; + + pr_debug("ep:(%pK)(%s) port:%p req_status:%d req->actual:%u\n", + ep, ep->name, port, req->status, req->actual); + if (!port) { + pr_err("port is null\n"); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_open || req->status || !req->actual) { + list_add_tail(&req->list, &port->read_pool); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + port->nbytes_from_host += req->actual; + list_add_tail(&req->list, &port->read_queued); + spin_unlock_irqrestore(&port->port_lock, flags); + + wake_up(&port->read_wq); +} + +static void usb_cser_write_complete(struct usb_ep *ep, struct usb_request *req) +{ + unsigned long flags; + struct f_cdev *port = ep->driver_data; + + pr_debug("ep:(%pK)(%s) port:%p req_stats:%d\n", + ep, ep->name, port, req->status); + + if (!port) { + pr_err("port is null\n"); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + port->nbytes_to_host += req->actual; + list_add_tail(&req->list, &port->write_pool); + spin_unlock_irqrestore(&port->port_lock, flags); + + switch (req->status) { + default: + pr_debug("unexpected %s status %d\n", ep->name, req->status); + /* FALL THROUGH */ + case 0: + /* normal completion */ + break; + + case -ESHUTDOWN: + /* disconnect */ + pr_debug("%s shutdown\n", ep->name); + break; + } +} + +static void usb_cser_start_io(struct f_cdev *port) +{ + int ret = -ENODEV; + unsigned long flags; + + pr_debug("port: %pK\n", port); + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->is_connected) + goto start_io_out; + + port->current_rx_req = NULL; + port->pending_rx_bytes = 0; + port->current_rx_buf = NULL; + + ret = usb_cser_alloc_requests(port->port_usb.out, + &port->read_pool, + BRIDGE_RX_QUEUE_SIZE, BRIDGE_RX_BUF_SIZE, + usb_cser_read_complete); + if (ret) { + pr_err("unable to allocate out requests\n"); + goto start_io_out; + } + + ret = usb_cser_alloc_requests(port->port_usb.in, + &port->write_pool, + BRIDGE_TX_QUEUE_SIZE, BRIDGE_TX_BUF_SIZE, + usb_cser_write_complete); + if (ret) { + usb_cser_free_requests(port->port_usb.out, &port->read_pool); + pr_err("unable to allocate IN requests\n"); + goto start_io_out; + } + +start_io_out: + spin_unlock_irqrestore(&port->port_lock, flags); + if (ret) + return; + + usb_cser_start_rx(port); +} + +static void usb_cser_stop_io(struct f_cdev *port) +{ + struct usb_ep *in; + struct usb_ep *out; + unsigned long flags; + + pr_debug("port:%pK\n", port); + + in = port->port_usb.in; + out = port->port_usb.out; + + /* disable endpoints, aborting down any active I/O */ + usb_ep_disable(out); + out->driver_data = NULL; + usb_ep_disable(in); + in->driver_data = NULL; + + spin_lock_irqsave(&port->port_lock, flags); + if (port->current_rx_req != NULL) { + kfree(port->current_rx_req->buf); + usb_ep_free_request(out, port->current_rx_req); + } + + port->pending_rx_bytes = 0; + port->current_rx_buf = NULL; + usb_cser_free_requests(out, &port->read_queued); + usb_cser_free_requests(out, &port->read_pool); + usb_cser_free_requests(in, &port->write_pool); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +int f_cdev_open(struct inode *inode, struct file *file) +{ + int ret; + unsigned long flags; + struct f_cdev *port; + + port = container_of(inode->i_cdev, struct f_cdev, fcdev_cdev); + if (!port) { + pr_err("Port is NULL.\n"); + return -EINVAL; + } + + if (port && port->port_open) { + pr_err("port is already opened.\n"); + return -EBUSY; + } + + file->private_data = port; + pr_debug("opening port(%s)(%pK)\n", port->name, port); + ret = wait_event_interruptible(port->open_wq, + port->is_connected); + if (ret) { + pr_debug("open interrupted.\n"); + return ret; + } + + spin_lock_irqsave(&port->port_lock, flags); + port->port_open = true; + spin_unlock_irqrestore(&port->port_lock, flags); + usb_cser_start_rx(port); + + pr_debug("port(%s)(%pK) open is success\n", port->name, port); + + return 0; +} + +int f_cdev_release(struct inode *inode, struct file *file) +{ + unsigned long flags; + struct f_cdev *port; + + port = file->private_data; + if (!port) { + pr_err("port is NULL.\n"); + return -EINVAL; + } + + spin_lock_irqsave(&port->port_lock, flags); + port->port_open = false; + port->cbits_updated = false; + spin_unlock_irqrestore(&port->port_lock, flags); + pr_debug("port(%s)(%pK) is closed.\n", port->name, port); + + return 0; +} + +ssize_t f_cdev_read(struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + unsigned long flags; + struct f_cdev *port; + struct usb_request *req; + struct list_head *pool; + struct usb_request *current_rx_req; + size_t pending_rx_bytes, bytes_copied = 0, size; + u8 *current_rx_buf; + + port = file->private_data; + if (!port) { + pr_err("port is NULL.\n"); + return -EINVAL; + } + + pr_debug("read on port(%s)(%pK) count:%zu\n", port->name, port, count); + spin_lock_irqsave(&port->port_lock, flags); + current_rx_req = port->current_rx_req; + pending_rx_bytes = port->pending_rx_bytes; + current_rx_buf = port->current_rx_buf; + port->current_rx_req = NULL; + port->current_rx_buf = NULL; + port->pending_rx_bytes = 0; + bytes_copied = 0; + + if (list_empty(&port->read_queued) && !pending_rx_bytes) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_debug("%s(): read_queued list is empty.\n", __func__); + goto start_rx; + } + + /* + * Consider below cases: + * 1. If available read buffer size (i.e. count value) is greater than + * available data as part of one USB OUT request buffer, then consider + * copying multiple USB OUT request buffers until read buffer is filled. + * 2. If available read buffer size (i.e. count value) is smaller than + * available data as part of one USB OUT request buffer, then copy this + * buffer data across multiple read() call until whole USB OUT request + * buffer is copied. + */ + while ((pending_rx_bytes || !list_empty(&port->read_queued)) && count) { + if (pending_rx_bytes == 0) { + pool = &port->read_queued; + req = list_first_entry(pool, struct usb_request, list); + list_del_init(&req->list); + current_rx_req = req; + pending_rx_bytes = req->actual; + current_rx_buf = req->buf; + } + + spin_unlock_irqrestore(&port->port_lock, flags); + size = count; + if (size > pending_rx_bytes) + size = pending_rx_bytes; + + pr_debug("pending_rx_bytes:%zu count:%zu size:%zu\n", + pending_rx_bytes, count, size); + size -= copy_to_user(buf, current_rx_buf, size); + port->nbytes_to_port_bridge += size; + bytes_copied += size; + count -= size; + buf += size; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->is_connected) { + list_add_tail(¤t_rx_req->list, &port->read_pool); + spin_unlock_irqrestore(&port->port_lock, flags); + return -EAGAIN; + } + + /* + * partial data available, then update pending_rx_bytes, + * otherwise add USB request back to read_pool for next data. + */ + if (size < pending_rx_bytes) { + pending_rx_bytes -= size; + current_rx_buf += size; + } else { + list_add_tail(¤t_rx_req->list, &port->read_pool); + pending_rx_bytes = 0; + current_rx_req = NULL; + current_rx_buf = NULL; + } + } + + port->pending_rx_bytes = pending_rx_bytes; + port->current_rx_buf = current_rx_buf; + port->current_rx_req = current_rx_req; + spin_unlock_irqrestore(&port->port_lock, flags); + +start_rx: + usb_cser_start_rx(port); + return bytes_copied; +} + +ssize_t f_cdev_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret; + unsigned long flags; + struct f_cdev *port; + struct usb_request *req; + struct list_head *pool; + unsigned int xfer_size; + struct usb_ep *in; + + port = file->private_data; + if (!port) { + pr_err("port is NULL.\n"); + return -EINVAL; + } + + spin_lock_irqsave(&port->port_lock, flags); + pr_debug("write on port(%s)(%pK)\n", port->name, port); + + if (!port->is_connected) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: cable is disconnected.\n", __func__); + return -ENODEV; + } + + if (list_empty(&port->write_pool)) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_debug("%s: Request list is empty.\n", __func__); + return 0; + } + + in = port->port_usb.in; + pool = &port->write_pool; + req = list_first_entry(pool, struct usb_request, list); + list_del_init(&req->list); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_debug("%s: write buf size:%zu\n", __func__, count); + if (count > BRIDGE_TX_BUF_SIZE) + xfer_size = BRIDGE_TX_BUF_SIZE; + else + xfer_size = count; + + ret = copy_from_user(req->buf, buf, xfer_size); + if (ret) { + pr_err("copy_from_user failed: err %d\n", ret); + ret = -EFAULT; + } else { + req->length = xfer_size; + req->zero = 1; + ret = usb_ep_queue(in, req, GFP_KERNEL); + if (ret) { + pr_err("EP QUEUE failed:%d\n", ret); + ret = -EIO; + goto err_exit; + } + spin_lock_irqsave(&port->port_lock, flags); + port->nbytes_from_port_bridge += req->length; + spin_unlock_irqrestore(&port->port_lock, flags); + } + +err_exit: + if (ret) { + spin_lock_irqsave(&port->port_lock, flags); + /* USB cable is connected, add it back otherwise free request */ + if (port->is_connected) + list_add(&req->list, &port->write_pool); + else + usb_cser_free_req(in, req); + spin_unlock_irqrestore(&port->port_lock, flags); + return ret; + } + + return xfer_size; +} + +static unsigned int f_cdev_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + struct f_cdev *port; + unsigned long flags; + + port = file->private_data; + if (port && port->is_connected) { + poll_wait(file, &port->read_wq, wait); + spin_lock_irqsave(&port->port_lock, flags); + if (!list_empty(&port->read_queued)) { + mask |= POLLIN | POLLRDNORM; + pr_debug("sets POLLIN for %s\n", port->name); + } + + if (port->cbits_updated) { + mask |= POLLPRI; + pr_debug("sets POLLPRI for %s\n", port->name); + } + spin_unlock_irqrestore(&port->port_lock, flags); + } else { + pr_err("Failed due to NULL device or disconnected.\n"); + mask = POLLERR; + } + + return mask; +} + +static int f_cdev_tiocmget(struct f_cdev *port) +{ + struct cserial *cser; + unsigned int result = 0; + + if (!port) { + pr_err("port is NULL.\n"); + return -ENODEV; + } + + cser = &port->port_usb; + if (cser->get_dtr) + result |= (cser->get_dtr(cser) ? TIOCM_DTR : 0); + + if (cser->get_rts) + result |= (cser->get_rts(cser) ? TIOCM_RTS : 0); + + if (cser->serial_state & TIOCM_CD) + result |= TIOCM_CD; + + if (cser->serial_state & TIOCM_RI) + result |= TIOCM_RI; + return result; +} + +static int f_cdev_tiocmset(struct f_cdev *port, + unsigned int set, unsigned int clear) +{ + struct cserial *cser; + int status = 0; + + if (!port) { + pr_err("port is NULL.\n"); + return -ENODEV; + } + + cser = &port->port_usb; + if (set & TIOCM_RI) { + if (cser->send_ring_indicator) { + cser->serial_state |= TIOCM_RI; + status = cser->send_ring_indicator(cser, 1); + } + } + if (clear & TIOCM_RI) { + if (cser->send_ring_indicator) { + cser->serial_state &= ~TIOCM_RI; + status = cser->send_ring_indicator(cser, 0); + } + } + if (set & TIOCM_CD) { + if (cser->send_carrier_detect) { + cser->serial_state |= TIOCM_CD; + status = cser->send_carrier_detect(cser, 1); + } + } + if (clear & TIOCM_CD) { + if (cser->send_carrier_detect) { + cser->serial_state &= ~TIOCM_CD; + status = cser->send_carrier_detect(cser, 0); + } + } + + return status; +} + +static long f_cdev_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + int i = 0; + uint32_t val; + struct f_cdev *port; + + port = fp->private_data; + if (!port) { + pr_err("port is null.\n"); + return POLLERR; + } + + switch (cmd) { + case TIOCMBIC: + case TIOCMBIS: + case TIOCMSET: + pr_debug("TIOCMSET on port(%s)%pK\n", port->name, port); + i = get_user(val, (uint32_t *)arg); + if (i) { + pr_err("Error getting TIOCMSET value\n"); + return i; + } + ret = f_cdev_tiocmset(port, val, ~val); + break; + case TIOCMGET: + pr_debug("TIOCMGET on port(%s)%pK\n", port->name, port); + ret = f_cdev_tiocmget(port); + if (ret >= 0) { + ret = put_user(ret, (uint32_t *)arg); + port->cbits_updated = false; + } + break; + default: + pr_err("Received cmd:%d not supported\n", cmd); + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} + +static void usb_cser_notify_modem(void *fport, int ctrl_bits) +{ + int temp; + struct f_cdev *port = fport; + + if (!port) { + pr_err("port is null\n"); + return; + } + + pr_debug("port(%s): ctrl_bits:%x\n", port->name, ctrl_bits); + + temp = convert_acm_sigs_to_uart(ctrl_bits); + + if (temp == port->cbits_to_modem) + return; + + port->cbits_to_modem = temp; + port->cbits_updated = true; + + wake_up(&port->read_wq); +} + +int usb_cser_connect(struct f_cdev *port) +{ + unsigned long flags; + int ret; + struct cserial *cser; + + if (!port) { + pr_err("port is NULL.\n"); + return -ENODEV; + } + + pr_debug("port(%s) (%pK)\n", port->name, port); + + cser = &port->port_usb; + cser->notify_modem = usb_cser_notify_modem; + + ret = usb_ep_enable(cser->in); + if (ret) { + pr_err("usb_ep_enable failed eptype:IN ep:%pK, err:%d\n", + cser->in, ret); + return ret; + } + cser->in->driver_data = port; + + ret = usb_ep_enable(cser->out); + if (ret) { + pr_err("usb_ep_enable failed eptype:OUT ep:%pK, err: %d\n", + cser->out, ret); + cser->in->driver_data = 0; + return ret; + } + cser->out->driver_data = port; + + spin_lock_irqsave(&port->port_lock, flags); + cser->pending = false; + cser->q_again = false; + port->is_connected = true; + spin_unlock_irqrestore(&port->port_lock, flags); + + usb_cser_start_io(port); + wake_up(&port->open_wq); + return 0; +} + +void usb_cser_disconnect(struct f_cdev *port) +{ + unsigned long flags; + + usb_cser_stop_io(port); + + /* lower DTR to modem */ + usb_cser_notify_modem(port, 0); + + spin_lock_irqsave(&port->port_lock, flags); + port->is_connected = false; + port->nbytes_from_host = port->nbytes_to_host = 0; + port->nbytes_to_port_bridge = 0; + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static const struct file_operations f_cdev_fops = { + .owner = THIS_MODULE, + .open = f_cdev_open, + .release = f_cdev_release, + .read = f_cdev_read, + .write = f_cdev_write, + .poll = f_cdev_poll, + .unlocked_ioctl = f_cdev_ioctl, + .compat_ioctl = f_cdev_ioctl, +}; + +static ssize_t cser_rw_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct f_cdev *port = s->private; + u8 input; + struct cserial *cser; + struct usb_function *func; + struct usb_gadget *gadget; + int ret; + + cser = &port->port_usb; + if (!cser) { + pr_err("cser is NULL\n"); + return -EINVAL; + } + + if (!port->is_connected) { + pr_debug("port disconnected\n"); + return -ENODEV; + } + + func = &cser->func; + if (!func) { + pr_err("func is NULL\n"); + return -EINVAL; + } + + if (ubuf == NULL) { + pr_debug("buffer is Null.\n"); + goto err; + } + + ret = kstrtou8_from_user(ubuf, count, 0, &input); + if (ret) { + pr_err("Invalid value. err:%d\n", ret); + goto err; + } + + if (port->debugfs_rw_enable == !!input) { + if (!!input) + pr_debug("RW already enabled\n"); + else + pr_debug("RW already disabled\n"); + goto err; + } + + port->debugfs_rw_enable = !!input; + if (port->debugfs_rw_enable) { + gadget = cser->func.config->cdev->gadget; + if (gadget->speed == USB_SPEED_SUPER && + func->func_is_suspended) { + pr_debug("Calling usb_func_wakeup\n"); + ret = usb_func_wakeup(func); + } else { + pr_debug("Calling usb_gadget_wakeup\n"); + ret = usb_gadget_wakeup(gadget); + } + + if ((ret == -EBUSY) || (ret == -EAGAIN)) + pr_debug("RW delayed due to LPM exit.\n"); + else if (ret) + pr_err("wakeup failed. ret=%d.\n", ret); + } else { + pr_debug("RW disabled.\n"); + } +err: + return count; +} + +static int usb_cser_rw_show(struct seq_file *s, void *unused) +{ + struct f_cdev *port = s->private; + + if (!port) { + pr_err("port is null\n"); + return 0; + } + + seq_printf(s, "%d\n", port->debugfs_rw_enable); + + return 0; +} + +static int debug_cdev_rw_open(struct inode *inode, struct file *f) +{ + return single_open(f, usb_cser_rw_show, inode->i_private); +} + +static const struct file_operations cser_rem_wakeup_fops = { + .open = debug_cdev_rw_open, + .read = seq_read, + .write = cser_rw_write, + .owner = THIS_MODULE, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void usb_cser_debugfs_init(struct f_cdev *port) +{ + port->debugfs_root = debugfs_create_dir(port->name, NULL); + if (IS_ERR(port->debugfs_root)) + return; + + debugfs_create_file("remote_wakeup", 0600, + port->debugfs_root, port, &cser_rem_wakeup_fops); +} + +static void usb_cser_debugfs_exit(struct f_cdev *port) +{ + debugfs_remove_recursive(port->debugfs_root); +} + +static struct f_cdev *f_cdev_alloc(char *func_name, int portno) +{ + int ret; + dev_t dev; + struct device *device; + struct f_cdev *port; + + port = kzalloc(sizeof(struct f_cdev), GFP_KERNEL); + if (!port) { + ret = -ENOMEM; + return ERR_PTR(ret); + } + + mutex_lock(&chardev_ida_lock); + if (ida_is_empty(&chardev_ida)) { + ret = usb_cser_alloc_chardev_region(); + if (ret) { + mutex_unlock(&chardev_ida_lock); + pr_err("alloc chardev failed\n"); + goto err_alloc_chardev; + } + } + + ret = ida_simple_get(&chardev_ida, 0, 0, GFP_KERNEL); + if (ret >= NUM_INSTANCE) { + ida_simple_remove(&chardev_ida, ret); + mutex_unlock(&chardev_ida_lock); + ret = -ENODEV; + goto err_get_ida; + } + + port->port_num = portno; + port->minor = ret; + mutex_unlock(&chardev_ida_lock); + + snprintf(port->name, sizeof(port->name), "%s%d", DEVICE_NAME, portno); + spin_lock_init(&port->port_lock); + + init_waitqueue_head(&port->open_wq); + init_waitqueue_head(&port->read_wq); + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queued); + INIT_LIST_HEAD(&port->write_pool); + + port->fcdev_wq = create_singlethread_workqueue(port->name); + if (!port->fcdev_wq) { + pr_err("Unable to create workqueue fcdev_wq for port:%s\n", + port->name); + ret = -ENOMEM; + goto err_get_ida; + } + + /* create char device */ + cdev_init(&port->fcdev_cdev, &f_cdev_fops); + dev = MKDEV(major, port->minor); + ret = cdev_add(&port->fcdev_cdev, dev, 1); + if (ret) { + pr_err("Failed to add cdev for port(%s)\n", port->name); + goto err_cdev_add; + } + + device = device_create(fcdev_classp, NULL, dev, NULL, port->name); + if (IS_ERR(device)) { + ret = PTR_ERR(device); + goto err_create_dev; + } + + usb_cser_debugfs_init(port); + + pr_info("port_name:%s (%pK) portno:(%d)\n", + port->name, port, port->port_num); + return port; + +err_create_dev: + cdev_del(&port->fcdev_cdev); +err_cdev_add: + destroy_workqueue(port->fcdev_wq); +err_get_ida: + usb_cser_chardev_deinit(); +err_alloc_chardev: + kfree(port); + + return ERR_PTR(ret); +} + +static void usb_cser_chardev_deinit(void) +{ + + if (ida_is_empty(&chardev_ida)) { + + if (major) { + unregister_chrdev_region(MKDEV(major, 0), minors); + major = minors = 0; + } + + if (!IS_ERR_OR_NULL(fcdev_classp)) + class_destroy(fcdev_classp); + } +} + +static int usb_cser_alloc_chardev_region(void) +{ + int ret; + dev_t dev; + + ret = alloc_chrdev_region(&dev, + 0, + NUM_INSTANCE, + MODULE_NAME); + if (ret) { + pr_err("alloc_chrdev_region() failed ret:%i\n", ret); + return ret; + } + + major = MAJOR(dev); + minors = NUM_INSTANCE; + + fcdev_classp = class_create(THIS_MODULE, MODULE_NAME); + if (IS_ERR(fcdev_classp)) { + pr_err("class_create() failed ENOMEM\n"); + ret = -ENOMEM; + } + + return 0; +} + +static inline struct f_cdev_opts *to_f_cdev_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_cdev_opts, + func_inst.group); +} + +static struct f_cdev_opts *to_fi_cdev_opts(struct usb_function_instance *fi) +{ + return container_of(fi, struct f_cdev_opts, func_inst); +} + +static void cserial_attr_release(struct config_item *item) +{ + struct f_cdev_opts *opts = to_f_cdev_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations cserial_item_ops = { + .release = cserial_attr_release, +}; + +static ssize_t usb_cser_status_show(struct config_item *item, char *page) +{ + struct f_cdev *port = to_f_cdev_opts(item)->port; + char *buf; + unsigned long flags; + int temp = 0; + int ret; + + buf = kzalloc(sizeof(char) * 512, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + spin_lock_irqsave(&port->port_lock, flags); + temp += scnprintf(buf + temp, 512 - temp, + "###PORT:%s###\n" + "port_no:%d\n" + "func:%s\n" + "nbytes_to_host: %lu\n" + "nbytes_from_host: %lu\n" + "nbytes_to_port_bridge: %lu\n" + "nbytes_from_port_bridge: %lu\n" + "cbits_to_modem: %u\n" + "Port Opened: %s\n", + port->name, + port->port_num, + to_f_cdev_opts(item)->func_name, + port->nbytes_to_host, + port->nbytes_from_host, + port->nbytes_to_port_bridge, + port->nbytes_from_port_bridge, + port->cbits_to_modem, + (port->port_open ? "Opened" : "Closed")); + spin_unlock_irqrestore(&port->port_lock, flags); + + ret = scnprintf(page, temp, buf); + kfree(buf); + + return ret; +} + +static ssize_t usb_cser_status_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_cdev *port = to_f_cdev_opts(item)->port; + unsigned long flags; + u8 stats; + + if (page == NULL) { + pr_err("Invalid buffer\n"); + return len; + } + + if (kstrtou8(page, 0, &stats) != 0 || stats != 0) { + pr_err("(%u)Wrong value. enter 0 to clear.\n", stats); + return len; + } + + spin_lock_irqsave(&port->port_lock, flags); + port->nbytes_to_host = port->nbytes_from_host = 0; + port->nbytes_to_port_bridge = port->nbytes_from_port_bridge = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + return len; +} + +CONFIGFS_ATTR(usb_cser_, status); +static struct configfs_attribute *cserial_attrs[] = { + &usb_cser_attr_status, + NULL, +}; + +static struct config_item_type cserial_func_type = { + .ct_item_ops = &cserial_item_ops, + .ct_attrs = cserial_attrs, + .ct_owner = THIS_MODULE, +}; + +static int cser_set_inst_name(struct usb_function_instance *f, const char *name) +{ + struct f_cdev_opts *opts = + container_of(f, struct f_cdev_opts, func_inst); + char *ptr, *str; + size_t name_len, str_size; + int ret; + struct f_cdev *port; + + name_len = strlen(name) + 1; + if (name_len > MAX_CDEV_INST_NAME) + return -ENAMETOOLONG; + + /* expect name as cdev.. */ + str = strnchr(name, strlen(name), '.'); + if (!str) { + pr_err("invalid input (%s)\n", name); + return -EINVAL; + } + + /* get function name */ + str_size = name_len - strlen(str); + if (str_size > MAX_CDEV_FUNC_NAME) + return -ENAMETOOLONG; + + ptr = kstrndup(name, str_size - 1, GFP_KERNEL); + if (!ptr) { + pr_err("error:%ld\n", PTR_ERR(ptr)); + return -ENOMEM; + } + + opts->func_name = ptr; + + /* get port number */ + str = strrchr(name, '.'); + if (!str) { + pr_err("err: port number not found\n"); + return -EINVAL; + } + pr_debug("str:%s\n", str); + + *str = '\0'; + str++; + + ret = kstrtou8(str, 0, &opts->port_num); + if (ret) { + pr_err("erro: not able to get port number\n"); + return -EINVAL; + } + + pr_debug("gser: port_num:%d func_name:%s\n", + opts->port_num, opts->func_name); + + port = f_cdev_alloc(opts->func_name, opts->port_num); + if (IS_ERR(port)) { + pr_err("Failed to create cdev port(%d)\n", opts->port_num); + return -ENOMEM; + } + + opts->port = port; + + /* For DUN functionality only sets control signal handling */ + if (!strcmp(opts->func_name, "dun")) { + port->port_usb.connect = dun_cser_connect; + port->port_usb.get_dtr = dun_cser_get_dtr; + port->port_usb.get_rts = dun_cser_get_rts; + port->port_usb.send_carrier_detect = + dun_cser_send_carrier_detect; + port->port_usb.send_ring_indicator = + dun_cser_send_ring_indicator; + port->port_usb.send_modem_ctrl_bits = dun_cser_send_ctrl_bits; + port->port_usb.disconnect = dun_cser_disconnect; + port->port_usb.send_break = dun_cser_send_break; + } + + return 0; +} + +static struct usb_function_instance *cser_alloc_inst(void) +{ + struct f_cdev_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = cser_free_inst; + opts->func_inst.set_inst_name = cser_set_inst_name; + + config_group_init_type_name(&opts->func_inst.group, "", + &cserial_func_type); + return &opts->func_inst; +} + +static struct usb_function *cser_alloc(struct usb_function_instance *fi) +{ + struct f_cdev_opts *opts = to_fi_cdev_opts(fi); + struct f_cdev *port = opts->port; + + port->port_usb.func.name = "cser"; + port->port_usb.func.strings = usb_cser_strings; + port->port_usb.func.bind = usb_cser_bind; + port->port_usb.func.unbind = usb_cser_unbind; + port->port_usb.func.set_alt = usb_cser_set_alt; + port->port_usb.func.disable = usb_cser_disable; + port->port_usb.func.setup = usb_cser_setup; + port->port_usb.func.func_suspend = usb_cser_func_suspend; + port->port_usb.func.get_status = usb_cser_get_status; + port->port_usb.func.free_func = usb_cser_free_func; + + return &port->port_usb.func; +} + +DECLARE_USB_FUNCTION_INIT(cser, cser_alloc_inst, cser_alloc); +MODULE_DESCRIPTION("USB Serial Character Driver"); +MODULE_LICENSE("GPL v2");