diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 87a97af03561..1d9b5ad04786 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -222,6 +222,30 @@ config USB_F_ACC config USB_F_AUDIO_SRC tristate +config USB_F_CDEV + tristate + +config USB_F_CCID + tristate + +config USB_F_QDSS + tristate + +config USB_F_GSI + tristate + +config USB_F_FS_IPC_LOGGING + tristate "Enable IPC logging for FunctionFS via f_fs_ipc_log" + depends on IPC_LOGGING + depends on ARM64 + help + Enables additional debug messages for FunctionFS driver with the help + of f_fs_ipc_log module and output via IPC Logging mechanism. This can + be useful when troubleshooting transfer stalls or other general + failures and determine if the issue is in the kernel gadget or the + userspace client. Separate IPC log contexts are created for each + function instance at mount time. + # this first set of drivers all depend on bulk-capable hardware. config USB_CONFIGFS @@ -517,6 +541,50 @@ config USB_CONFIGFS_F_TCM Both protocols can work on USB2.0 and USB3.0. UAS utilizes the USB 3.0 feature called streams support. +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. + +config USB_CONFIGFS_F_CCID + tristate "USB CCID function" + select USB_F_CCID + depends on USB_CONFIGFS + help + The Chip Card Interface Device (CCID) function implements a USB + interface that exposes a standard CSCID class that consists of a + pair of bulk IN and OUT endpoints and a single interrupt IN + endpoint. This driver provides a character device interface + allowing a userspace component to be able to provide the + implementation necessary to interface with the smartcard. + +config USB_CONFIGFS_F_QDSS + tristate "USB QDSS function" + select USB_F_QDSS + depends on USB_CONFIGFS + help + USB QDSS function driver to get hwtracing related data over + USB. USB QDSS function driver which allows communication + between USB BAM and QDSS BAM for QDSS debug functionality + over USB. + +config USB_CONFIGFS_F_GSI + tristate "USB GSI function" + select USB_F_GSI + depends on USB_CONFIGFS + help + Generic function driver to support h/w acceleration to IPA + over GSI. This driver provides USB RMNET/RNDIS/ECM/MBIM/DPL + related functionalities using GSI hardware accelerated data + path and control path. + source "drivers/usb/gadget/legacy/Kconfig" endif # USB_GADGET diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index dd33a1243342..a4431185ac84 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -54,3 +54,15 @@ usb_f_accessory-y := f_accessory.o obj-$(CONFIG_USB_F_ACC) += usb_f_accessory.o usb_f_audio_source-y := f_audio_source.o obj-$(CONFIG_USB_F_AUDIO_SRC) += usb_f_audio_source.o +obj-$(CONFIG_USB_F_FS_IPC_LOGGING) += f_fs_ipc_log.o +usb_f_cdev-y := f_cdev.o +obj-$(CONFIG_USB_F_CDEV) += usb_f_cdev.o +usb_f_ccid-y := f_ccid.o +obj-$(CONFIG_USB_F_CCID) += usb_f_ccid.o +usb_f_qdss-y := f_qdss.o u_qdss.o +obj-$(CONFIG_USB_F_QDSS) += usb_f_qdss.o +usb_f_gsi-y := f_gsi.o +ifeq ($(CONFIG_USB_F_RNDIS),) +usb_f_gsi-y += rndis.o +endif +obj-$(CONFIG_USB_F_GSI) += usb_f_gsi.o diff --git a/drivers/usb/gadget/function/f_ccid.c b/drivers/usb/gadget/function/f_ccid.c new file mode 100644 index 000000000000..093675f89782 --- /dev/null +++ b/drivers/usb/gadget/function/f_ccid.c @@ -0,0 +1,1239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * f_ccid.c -- CCID function Driver + * + * Copyright (c) 2011, 2013, 2017, 2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "f_ccid.h" + +#define BULK_IN_BUFFER_SIZE sizeof(struct ccid_bulk_in_header) +#define BULK_OUT_BUFFER_SIZE 1024 +#define CTRL_BUF_SIZE 4 +#define FUNCTION_NAME "ccid" +#define MAX_INST_NAME_LEN 40 +#define CCID_CTRL_DEV_NAME "ccid_ctrl" +#define CCID_BULK_DEV_NAME "ccid_bulk" +#define CCID_NOTIFY_INTERVAL 5 +#define CCID_NOTIFY_MAXPACKET 4 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +struct ccid_ctrl_dev { + atomic_t opened; + struct list_head tx_q; + wait_queue_head_t tx_wait_q; + unsigned char buf[CTRL_BUF_SIZE]; + int tx_ctrl_done; + struct cdev cdev; +}; + +struct ccid_bulk_dev { + atomic_t error; + atomic_t opened; + atomic_t rx_req_busy; + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; + struct list_head tx_idle; + struct cdev cdev; +}; + +struct ccid_opts { + struct usb_function_instance func_inst; + struct f_ccid *ccid; +}; + +struct f_ccid { + struct usb_function function; + int ifc_id; + spinlock_t lock; + atomic_t online; + /* usb eps*/ + struct usb_ep *notify; + struct usb_ep *in; + struct usb_ep *out; + struct usb_request *notify_req; + struct ccid_ctrl_dev ctrl_dev; + struct ccid_bulk_dev bulk_dev; + int dtr_state; +}; + +#define MAX_INSTANCES 4 + +static int major; +static struct class *ccid_class; +static DEFINE_IDA(ccid_ida); +static DEFINE_MUTEX(ccid_ida_lock); + +static inline struct f_ccid *ctrl_dev_to_ccid(struct ccid_ctrl_dev *d) +{ + return container_of(d, struct f_ccid, ctrl_dev); +} + +static inline struct f_ccid *bulk_dev_to_ccid(struct ccid_bulk_dev *d) +{ + return container_of(d, struct f_ccid, bulk_dev); +} + +/* Interface Descriptor: */ +static struct usb_interface_descriptor ccid_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_CSCID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, +}; +/* CCID Class Descriptor */ +static struct usb_ccid_class_descriptor ccid_class_desc = { + .bLength = sizeof(ccid_class_desc), + .bDescriptorType = CCID_DECRIPTOR_TYPE, + .bcdCCID = CCID1_10, + .bMaxSlotIndex = 0, + /* This value indicates what voltages the CCID can supply to slots */ + .bVoltageSupport = VOLTS_3_0, + .dwProtocols = PROTOCOL_TO, + /* Default ICC clock frequency in KHz */ + .dwDefaultClock = 3580, + /* Maximum supported ICC clock frequency in KHz */ + .dwMaximumClock = 3580, + .bNumClockSupported = 0, + /* Default ICC I/O data rate in bps */ + .dwDataRate = 9600, + /* Maximum supported ICC I/O data rate in bps */ + .dwMaxDataRate = 9600, + .bNumDataRatesSupported = 0, + .dwMaxIFSD = 0, + .dwSynchProtocols = 0, + .dwMechanical = 0, + /* This value indicates what intelligent features the CCID has */ + .dwFeatures = CCID_FEATURES_EXC_SAPDU | + CCID_FEATURES_AUTO_PNEGO | + CCID_FEATURES_AUTO_BAUD | + CCID_FEATURES_AUTO_CLOCK | + CCID_FEATURES_AUTO_VOLT | + CCID_FEATURES_AUTO_ACTIV | + CCID_FEATURES_AUTO_PCONF, + /* extended APDU level Message Length */ + .dwMaxCCIDMessageLength = 0x200, + .bClassGetResponse = 0x0, + .bClassEnvelope = 0x0, + .wLcdLayout = 0, + .bPINSupport = 0, + .bMaxCCIDBusySlots = 1 +}; +/* Full speed support: */ +static struct usb_endpoint_descriptor ccid_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(CCID_NOTIFY_MAXPACKET), + .bInterval = 1 << CCID_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor ccid_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor ccid_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_descriptor_header *ccid_fs_descs[] = { + (struct usb_descriptor_header *) &ccid_interface_desc, + (struct usb_descriptor_header *) &ccid_class_desc, + (struct usb_descriptor_header *) &ccid_fs_notify_desc, + (struct usb_descriptor_header *) &ccid_fs_in_desc, + (struct usb_descriptor_header *) &ccid_fs_out_desc, + NULL, +}; + +/* High speed support: */ +static struct usb_endpoint_descriptor ccid_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(CCID_NOTIFY_MAXPACKET), + .bInterval = CCID_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor ccid_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor ccid_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *ccid_hs_descs[] = { + (struct usb_descriptor_header *) &ccid_interface_desc, + (struct usb_descriptor_header *) &ccid_class_desc, + (struct usb_descriptor_header *) &ccid_hs_notify_desc, + (struct usb_descriptor_header *) &ccid_hs_in_desc, + (struct usb_descriptor_header *) &ccid_hs_out_desc, + NULL, +}; + +/* Super speed support: */ +static struct usb_endpoint_descriptor ccid_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(CCID_NOTIFY_MAXPACKET), + .bInterval = CCID_NOTIFY_INTERVAL + 4, +}; + +static struct usb_ss_ep_comp_descriptor ccid_ss_notify_comp_desc = { + .bLength = sizeof(ccid_ss_notify_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor ccid_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ccid_ss_in_comp_desc = { + .bLength = sizeof(ccid_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor ccid_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ccid_ss_out_comp_desc = { + .bLength = sizeof(ccid_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ccid_ss_descs[] = { + (struct usb_descriptor_header *) &ccid_interface_desc, + (struct usb_descriptor_header *) &ccid_class_desc, + (struct usb_descriptor_header *) &ccid_ss_notify_desc, + (struct usb_descriptor_header *) &ccid_ss_notify_comp_desc, + (struct usb_descriptor_header *) &ccid_ss_in_desc, + (struct usb_descriptor_header *) &ccid_ss_in_comp_desc, + (struct usb_descriptor_header *) &ccid_ss_out_desc, + (struct usb_descriptor_header *) &ccid_ss_out_comp_desc, + NULL, +}; + +static inline struct f_ccid *func_to_ccid(struct usb_function *f) +{ + return container_of(f, struct f_ccid, function); +} + +static void ccid_req_put(struct f_ccid *ccid_dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&ccid_dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&ccid_dev->lock, flags); +} + +static struct usb_request *ccid_req_get(struct f_ccid *ccid_dev, + struct list_head *head) +{ + unsigned long flags; + struct usb_request *req = NULL; + + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!list_empty(head)) { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&ccid_dev->lock, flags); + return req; +} + +static void ccid_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + switch (req->status) { + case -ECONNRESET: + case -ESHUTDOWN: + case 0: + break; + default: + pr_err("CCID notify ep error %d\n", req->status); + } +} + +static void ccid_bulk_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ccid *ccid_dev = req->context; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + + if (req->status != 0) + atomic_set(&bulk_dev->error, 1); + + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + wake_up(&bulk_dev->write_wq); +} + +static void ccid_bulk_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ccid *ccid_dev = req->context; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + + if (req->status != 0) + atomic_set(&bulk_dev->error, 1); + + bulk_dev->rx_done = 1; + wake_up(&bulk_dev->read_wq); +} + +static struct usb_request * +ccid_request_alloc(struct usb_ep *ep, size_t len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, kmalloc_flags); + + if (req != NULL) { + req->length = len; + req->buf = kmalloc(len, kmalloc_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + + return req ? req : ERR_PTR(-ENOMEM); +} + +static void ccid_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static int +ccid_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_ccid *ccid_dev = container_of(f, struct f_ccid, function); + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int ret = -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); + + if (!atomic_read(&ccid_dev->online)) + return -ENOTCONN; + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | CCIDGENERICREQ_ABORT: + if (w_length != 0) + goto invalid; + ctrl_dev->buf[0] = CCIDGENERICREQ_ABORT; + ctrl_dev->buf[1] = w_value & 0xFF; + ctrl_dev->buf[2] = (w_value >> 8) & 0xFF; + ctrl_dev->buf[3] = 0x00; + ctrl_dev->tx_ctrl_done = 1; + wake_up(&ctrl_dev->tx_wait_q); + ret = 0; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | CCIDGENERICREQ_GET_CLOCK_FREQUENCIES: + *(u32 *) req->buf = + cpu_to_le32(ccid_class_desc.dwDefaultClock); + ret = min_t(u32, w_length, + sizeof(ccid_class_desc.dwDefaultClock)); + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | CCIDGENERICREQ_GET_DATA_RATES: + *(u32 *) req->buf = cpu_to_le32(ccid_class_desc.dwDataRate); + ret = min_t(u32, w_length, sizeof(ccid_class_desc.dwDataRate)); + break; + + default: +invalid: + pr_debug("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 (ret >= 0) { + pr_debug("ccid req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->length = ret; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + pr_err("ccid ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static void ccid_function_disable(struct usb_function *f) +{ + struct f_ccid *ccid_dev = func_to_ccid(f); + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + + /* Disable endpoints */ + usb_ep_disable(ccid_dev->notify); + usb_ep_disable(ccid_dev->in); + usb_ep_disable(ccid_dev->out); + + ccid_dev->dtr_state = 0; + atomic_set(&ccid_dev->online, 0); + /* Wake up threads */ + wake_up(&bulk_dev->write_wq); + wake_up(&bulk_dev->read_wq); + wake_up(&ctrl_dev->tx_wait_q); + +} + +static int +ccid_function_set_alt(struct usb_function *f, unsigned int intf, + unsigned int alt) +{ + struct f_ccid *ccid_dev = func_to_ccid(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret = 0; + + /* choose the descriptors and enable endpoints */ + ret = config_ep_by_speed(cdev->gadget, f, ccid_dev->notify); + if (ret) { + ccid_dev->notify->desc = NULL; + pr_err("%s: config_ep_by_speed failed for ep#%s, err#%d\n", + __func__, ccid_dev->notify->name, ret); + return ret; + } + ret = usb_ep_enable(ccid_dev->notify); + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, ccid_dev->notify->name, ret); + return ret; + } + ccid_dev->notify->driver_data = ccid_dev; + + ret = config_ep_by_speed(cdev->gadget, f, ccid_dev->in); + if (ret) { + ccid_dev->in->desc = NULL; + pr_err("%s: config_ep_by_speed failed for ep#%s, err#%d\n", + __func__, ccid_dev->in->name, ret); + goto disable_ep_notify; + } + ret = usb_ep_enable(ccid_dev->in); + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, ccid_dev->in->name, ret); + goto disable_ep_notify; + } + + ret = config_ep_by_speed(cdev->gadget, f, ccid_dev->out); + if (ret) { + ccid_dev->out->desc = NULL; + pr_err("%s: config_ep_by_speed failed for ep#%s, err#%d\n", + __func__, ccid_dev->out->name, ret); + goto disable_ep_in; + } + ret = usb_ep_enable(ccid_dev->out); + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, ccid_dev->out->name, ret); + goto disable_ep_in; + } + ccid_dev->dtr_state = 1; + atomic_set(&ccid_dev->online, 1); + return ret; + +disable_ep_in: + usb_ep_disable(ccid_dev->in); +disable_ep_notify: + usb_ep_disable(ccid_dev->notify); + ccid_dev->notify->driver_data = NULL; + return ret; +} + +static void ccid_function_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct f_ccid *ccid_dev = func_to_ccid(f); + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct usb_request *req; + + /* Free endpoint related requests */ + ccid_request_free(ccid_dev->notify_req, ccid_dev->notify); + if (!atomic_read(&bulk_dev->rx_req_busy)) + ccid_request_free(bulk_dev->rx_req, ccid_dev->out); + while ((req = ccid_req_get(ccid_dev, &bulk_dev->tx_idle))) + ccid_request_free(req, ccid_dev->in); + + usb_free_all_descriptors(f); +} + +static int ccid_function_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct f_ccid *ccid_dev = func_to_ccid(f); + struct usb_ep *ep; + struct usb_composite_dev *cdev = c->cdev; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct usb_request *req; + int ret = -ENODEV; + int i; + + ccid_dev->ifc_id = usb_interface_id(c, f); + if (ccid_dev->ifc_id < 0) { + pr_err("%s: unable to allocate ifc id, err:%d\n", + __func__, ccid_dev->ifc_id); + return ccid_dev->ifc_id; + } + ccid_interface_desc.bInterfaceNumber = ccid_dev->ifc_id; + + ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_notify_desc); + if (!ep) { + pr_err("%s: usb epnotify autoconfig failed\n", __func__); + return -ENODEV; + } + ccid_dev->notify = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_in_desc); + if (!ep) { + pr_err("%s: usb epin autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_in_fail; + } + ccid_dev->in = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_out_desc); + if (!ep) { + pr_err("%s: usb epout autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_out_fail; + } + ccid_dev->out = ep; + ep->driver_data = cdev; + + /* + * support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + ccid_hs_in_desc.bEndpointAddress = ccid_fs_in_desc.bEndpointAddress; + ccid_hs_out_desc.bEndpointAddress = ccid_fs_out_desc.bEndpointAddress; + ccid_hs_notify_desc.bEndpointAddress = + ccid_fs_notify_desc.bEndpointAddress; + + + ccid_ss_in_desc.bEndpointAddress = ccid_fs_in_desc.bEndpointAddress; + ccid_ss_out_desc.bEndpointAddress = ccid_fs_out_desc.bEndpointAddress; + ccid_ss_notify_desc.bEndpointAddress = + ccid_fs_notify_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, ccid_fs_descs, ccid_hs_descs, + ccid_ss_descs, ccid_ss_descs); + if (ret) + goto assign_desc_fail; + + pr_debug("%s: CCID %s Speed, IN:%s OUT:%s\n", __func__, + gadget_is_dualspeed(cdev->gadget) ? "dual" : "full", + ccid_dev->in->name, ccid_dev->out->name); + + ccid_dev->notify_req = ccid_request_alloc(ccid_dev->notify, + sizeof(struct usb_ccid_notification), GFP_KERNEL); + if (IS_ERR(ccid_dev->notify_req)) { + pr_err("%s: unable to allocate memory for notify req\n", + __func__); + goto notify_alloc_fail; + } + ccid_dev->notify_req->complete = ccid_notify_complete; + ccid_dev->notify_req->context = ccid_dev; + + /* now allocate requests for our endpoints */ + req = ccid_request_alloc(ccid_dev->out, BULK_OUT_BUFFER_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) { + pr_err("%s: unable to allocate memory for out req\n", + __func__); + ret = PTR_ERR(req); + goto out_alloc_fail; + } + req->complete = ccid_bulk_complete_out; + req->context = ccid_dev; + bulk_dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = ccid_request_alloc(ccid_dev->in, BULK_IN_BUFFER_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) { + pr_err("%s: unable to allocate memory for in req\n", + __func__); + ret = PTR_ERR(req); + goto in_alloc_fail; + } + req->complete = ccid_bulk_complete_in; + req->context = ccid_dev; + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + } + + return 0; + +in_alloc_fail: + ccid_request_free(bulk_dev->rx_req, ccid_dev->out); +out_alloc_fail: + ccid_request_free(ccid_dev->notify_req, ccid_dev->notify); +notify_alloc_fail: + usb_free_all_descriptors(f); +assign_desc_fail: + ccid_dev->out->driver_data = NULL; + ccid_dev->out = NULL; +ep_auto_out_fail: + ccid_dev->in->driver_data = NULL; + ccid_dev->in = NULL; +ep_auto_in_fail: + ccid_dev->notify->driver_data = NULL; + ccid_dev->notify = NULL; + + return ret; +} + +static int ccid_bulk_open(struct inode *inode, struct file *fp) +{ + struct ccid_bulk_dev *bulk_dev = container_of(inode->i_cdev, + struct ccid_bulk_dev, cdev); + struct f_ccid *ccid_dev = bulk_dev_to_ccid(bulk_dev); + unsigned long flags; + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + + if (atomic_read(&bulk_dev->opened)) { + pr_debug("%s: bulk device is already opened\n", __func__); + return -EBUSY; + } + atomic_set(&bulk_dev->opened, 1); + /* clear the error latch */ + atomic_set(&bulk_dev->error, 0); + spin_lock_irqsave(&ccid_dev->lock, flags); + fp->private_data = ccid_dev; + spin_unlock_irqrestore(&ccid_dev->lock, flags); + + return 0; +} + +static int ccid_bulk_release(struct inode *ip, struct file *fp) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + + atomic_set(&bulk_dev->opened, 0); + return 0; +} + +static ssize_t ccid_bulk_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct usb_request *req; + int r = count, xfer, len; + int ret; + unsigned long flags; + + pr_debug("%s: %zu bytes\n", __func__, count); + + if (count > BULK_OUT_BUFFER_SIZE) { + pr_err("%s: max_buffer_size:%d given_pkt_size:%zu\n", + __func__, BULK_OUT_BUFFER_SIZE, count); + return -ENOMEM; + } + + if (atomic_read(&bulk_dev->error)) { + r = -EIO; + pr_err("%s bulk_dev_error\n", __func__); + goto done; + } + + len = ALIGN(count, ccid_dev->out->maxpacket); +requeue_req: + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!atomic_read(&ccid_dev->online)) { + spin_unlock_irqrestore(&ccid_dev->lock, flags); + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + /* queue a request */ + req = bulk_dev->rx_req; + req->length = len; + bulk_dev->rx_done = 0; + spin_unlock_irqrestore(&ccid_dev->lock, flags); + ret = usb_ep_queue(ccid_dev->out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + pr_err("%s usb ep queue failed\n", __func__); + atomic_set(&bulk_dev->error, 1); + goto done; + } + /* wait for a request to complete */ + ret = wait_event_interruptible(bulk_dev->read_wq, bulk_dev->rx_done || + atomic_read(&bulk_dev->error) || + !atomic_read(&ccid_dev->online)); + if (ret < 0) { + atomic_set(&bulk_dev->error, 1); + r = ret; + usb_ep_dequeue(ccid_dev->out, req); + goto done; + } + if (!atomic_read(&bulk_dev->error)) { + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!atomic_read(&ccid_dev->online)) { + spin_unlock_irqrestore(&ccid_dev->lock, flags); + pr_debug("%s: USB cable not connected\n", __func__); + r = -ENODEV; + goto done; + } + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) { + spin_unlock_irqrestore(&ccid_dev->lock, flags); + goto requeue_req; + } + if (req->actual > count) + pr_err("%s More data received(%d) than required(%zu)\n", + __func__, req->actual, count); + xfer = (req->actual < count) ? req->actual : count; + atomic_set(&bulk_dev->rx_req_busy, 1); + spin_unlock_irqrestore(&ccid_dev->lock, flags); + + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + + spin_lock_irqsave(&ccid_dev->lock, flags); + atomic_set(&bulk_dev->rx_req_busy, 0); + if (!atomic_read(&ccid_dev->online)) { + ccid_request_free(bulk_dev->rx_req, ccid_dev->out); + spin_unlock_irqrestore(&ccid_dev->lock, flags); + pr_debug("%s: USB cable not connected\n", __func__); + r = -ENODEV; + goto done; + } else { + r = xfer; + } + spin_unlock_irqrestore(&ccid_dev->lock, flags); + } else { + r = -EIO; + } +done: + pr_debug("%s returning %d\n", __func__, r); + return r; +} + +static ssize_t ccid_bulk_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_bulk_dev *bulk_dev = &ccid_dev->bulk_dev; + struct usb_request *req = 0; + int r = count; + int ret; + unsigned long flags; + + pr_debug("%s: %zu bytes\n", __func__, count); + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + + if (!count) { + pr_err("%s: zero length ctrl pkt\n", __func__); + return -ENODEV; + } + if (count > BULK_IN_BUFFER_SIZE) { + pr_err("%s: max_buffer_size:%zu given_pkt_size:%zu\n", + __func__, BULK_IN_BUFFER_SIZE, count); + return -ENOMEM; + } + + + /* get an idle tx request to use */ + ret = wait_event_interruptible(bulk_dev->write_wq, + ((req = ccid_req_get(ccid_dev, &bulk_dev->tx_idle)) || + atomic_read(&bulk_dev->error))); + + if (ret < 0) { + r = ret; + goto done; + } + + if (!req || atomic_read(&bulk_dev->error)) { + pr_err(" %s dev->error\n", __func__); + r = -EIO; + goto done; + } + if (copy_from_user(req->buf, buf, count)) { + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", + __func__); + ccid_request_free(req, ccid_dev->in); + r = -ENODEV; + } else { + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + r = -EFAULT; + } + goto done; + } + req->length = count; + ret = usb_ep_queue(ccid_dev->in, req, GFP_KERNEL); + if (ret < 0) { + pr_debug("%s: xfer error %d\n", __func__, ret); + atomic_set(&bulk_dev->error, 1); + ccid_req_put(ccid_dev, &bulk_dev->tx_idle, req); + r = -EIO; + spin_lock_irqsave(&ccid_dev->lock, flags); + if (!atomic_read(&ccid_dev->online)) { + spin_unlock_irqrestore(&ccid_dev->lock, flags); + pr_debug("%s: USB cable not connected\n", + __func__); + while ((req = ccid_req_get(ccid_dev, + &bulk_dev->tx_idle))) + ccid_request_free(req, ccid_dev->in); + r = -ENODEV; + } + spin_unlock_irqrestore(&ccid_dev->lock, flags); + goto done; + } +done: + pr_debug("%s returning %d\n", __func__, r); + return r; +} + +static const struct file_operations ccid_bulk_fops = { + .owner = THIS_MODULE, + .read = ccid_bulk_read, + .write = ccid_bulk_write, + .open = ccid_bulk_open, + .release = ccid_bulk_release, +}; + +static int ccid_ctrl_open(struct inode *inode, struct file *fp) +{ + struct ccid_ctrl_dev *ctrl_dev = container_of(inode->i_cdev, + struct ccid_ctrl_dev, cdev); + struct f_ccid *ccid_dev = ctrl_dev_to_ccid(ctrl_dev); + unsigned long flags; + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + if (atomic_read(&ctrl_dev->opened)) { + pr_debug("%s: ctrl device is already opened\n", __func__); + return -EBUSY; + } + atomic_set(&ctrl_dev->opened, 1); + spin_lock_irqsave(&ccid_dev->lock, flags); + fp->private_data = ccid_dev; + spin_unlock_irqrestore(&ccid_dev->lock, flags); + + return 0; +} + + +static int ccid_ctrl_release(struct inode *inode, struct file *fp) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + + atomic_set(&ctrl_dev->opened, 0); + + return 0; +} + +static ssize_t ccid_ctrl_read(struct file *fp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct ccid_ctrl_dev *ctrl_dev = &ccid_dev->ctrl_dev; + int ret = 0; + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + if (count > CTRL_BUF_SIZE) + count = CTRL_BUF_SIZE; + + ret = wait_event_interruptible(ctrl_dev->tx_wait_q, + ctrl_dev->tx_ctrl_done || + !atomic_read(&ccid_dev->online)); + if (ret < 0) + return ret; + ctrl_dev->tx_ctrl_done = 0; + + if (!atomic_read(&ccid_dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + ret = copy_to_user(buf, ctrl_dev->buf, count); + if (ret) + return -EFAULT; + + return count; +} + +static long +ccid_ctrl_ioctl(struct file *fp, unsigned int cmd, u_long arg) +{ + struct f_ccid *ccid_dev = fp->private_data; + struct usb_request *req = ccid_dev->notify_req; + struct usb_ccid_notification *ccid_notify = req->buf; + void __user *argp = (void __user *)arg; + int ret = 0; + + switch (cmd) { + case CCID_NOTIFY_CARD: + if (copy_from_user(ccid_notify, argp, + sizeof(struct usb_ccid_notification))) + return -EFAULT; + req->length = 2; + break; + case CCID_NOTIFY_HWERROR: + if (copy_from_user(ccid_notify, argp, + sizeof(struct usb_ccid_notification))) + return -EFAULT; + req->length = 4; + break; + case CCID_READ_DTR: + if (copy_to_user((int *)arg, &ccid_dev->dtr_state, sizeof(int))) + return -EFAULT; + return 0; + } + ret = usb_ep_queue(ccid_dev->notify, ccid_dev->notify_req, GFP_KERNEL); + if (ret < 0) { + pr_err("ccid notify ep enqueue error %d\n", ret); + return ret; + } + return 0; +} + +static const struct file_operations ccid_ctrl_fops = { + .owner = THIS_MODULE, + .open = ccid_ctrl_open, + .release = ccid_ctrl_release, + .read = ccid_ctrl_read, + .unlocked_ioctl = ccid_ctrl_ioctl, +}; + +static int ccid_cdev_init(struct cdev *cdev, const struct file_operations *fops, + const char *name) +{ + struct device *dev; + int ret, minor; + + minor = ida_simple_get(&ccid_ida, 0, MAX_INSTANCES, GFP_KERNEL); + if (minor < 0) { + pr_err("%s: No more minor numbers left! rc:%d\n", __func__, + minor); + return minor; + } + + cdev_init(cdev, fops); + ret = cdev_add(cdev, MKDEV(major, minor), 1); + if (ret) { + pr_err("Failed to add cdev for (%s)\n", name); + goto err_cdev_add; + } + + dev = device_create(ccid_class, NULL, MKDEV(major, minor), NULL, name); + if (IS_ERR(dev)) { + ret = PTR_ERR(dev); + goto err_create_dev; + } + + return 0; + +err_create_dev: + cdev_del(cdev); +err_cdev_add: + ida_simple_remove(&ccid_ida, minor); + return ret; +} + +static void ccid_cdev_free(struct cdev *cdev) +{ + int minor = MINOR(cdev->dev); + + device_destroy(ccid_class, cdev->dev); + cdev_del(cdev); + ida_simple_remove(&ccid_ida, minor); +} + +static void ccid_free_func(struct usb_function *f) +{ } + +static int ccid_bind_config(struct f_ccid *ccid_dev) +{ + ccid_dev->function.name = FUNCTION_NAME; + ccid_dev->function.bind = ccid_function_bind; + ccid_dev->function.unbind = ccid_function_unbind; + ccid_dev->function.set_alt = ccid_function_set_alt; + ccid_dev->function.setup = ccid_function_setup; + ccid_dev->function.disable = ccid_function_disable; + ccid_dev->function.free_func = ccid_free_func; + + return 0; +} + +static int ccid_alloc_chrdev_region(void) +{ + int ret; + dev_t dev; + + ccid_class = class_create(THIS_MODULE, "ccid_usb"); + if (IS_ERR(ccid_class)) { + ret = PTR_ERR(ccid_class); + ccid_class = NULL; + pr_err("%s: class_create() failed:%d\n", __func__, ret); + return ret; + } + + ret = alloc_chrdev_region(&dev, 0, MAX_INSTANCES, "ccid_usb"); + if (ret) { + pr_err("%s: alloc_chrdev_region() failed:%d\n", __func__, ret); + class_destroy(ccid_class); + ccid_class = NULL; + return ret; + } + + major = MAJOR(dev); + + return 0; +} + +static void ccid_free_chrdev_region(void) +{ + mutex_lock(&ccid_ida_lock); + if (ida_is_empty(&ccid_ida)) { + if (major) { + unregister_chrdev_region(MKDEV(major, 0), + MAX_INSTANCES); + major = 0; + } + + if (ccid_class) { + class_destroy(ccid_class); + ccid_class = NULL; + } + } + mutex_unlock(&ccid_ida_lock); +} + +static struct f_ccid *ccid_setup(void) +{ + struct f_ccid *ccid_dev; + int ret; + + ccid_dev = kzalloc(sizeof(*ccid_dev), GFP_KERNEL); + if (!ccid_dev) { + ret = -ENOMEM; + goto error; + } + + spin_lock_init(&ccid_dev->lock); + INIT_LIST_HEAD(&ccid_dev->ctrl_dev.tx_q); + init_waitqueue_head(&ccid_dev->ctrl_dev.tx_wait_q); + init_waitqueue_head(&ccid_dev->bulk_dev.read_wq); + init_waitqueue_head(&ccid_dev->bulk_dev.write_wq); + INIT_LIST_HEAD(&ccid_dev->bulk_dev.tx_idle); + + mutex_lock(&ccid_ida_lock); + if (ida_is_empty(&ccid_ida)) { + ret = ccid_alloc_chrdev_region(); + if (ret) { + mutex_unlock(&ccid_ida_lock); + goto err_chrdev; + } + } + mutex_unlock(&ccid_ida_lock); + + ret = ccid_cdev_init(&ccid_dev->ctrl_dev.cdev, &ccid_ctrl_fops, + CCID_CTRL_DEV_NAME); + if (ret) { + pr_err("%s: ccid_ctrl_device_init failed, err:%d\n", + __func__, ret); + goto err_ctrl_init; + } + + ret = ccid_cdev_init(&ccid_dev->bulk_dev.cdev, &ccid_bulk_fops, + CCID_BULK_DEV_NAME); + if (ret) { + pr_err("%s: ccid_bulk_device_init failed, err:%d\n", + __func__, ret); + goto err_bulk_init; + } + + return ccid_dev; +err_bulk_init: + ccid_cdev_free(&ccid_dev->ctrl_dev.cdev); +err_ctrl_init: + ccid_free_chrdev_region(); +err_chrdev: + kfree(ccid_dev); +error: + pr_err("ccid gadget driver failed to initialize\n"); + return ERR_PTR(ret); +} + +static inline struct ccid_opts *to_ccid_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct ccid_opts, + func_inst.group); +} + +static void ccid_attr_release(struct config_item *item) +{ + struct ccid_opts *opts = to_ccid_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations ccid_item_ops = { + .release = ccid_attr_release, +}; + +static struct config_item_type ccid_func_type = { + .ct_item_ops = &ccid_item_ops, + .ct_owner = THIS_MODULE, +}; + +static int ccid_set_inst_name(struct usb_function_instance *fi, + const char *name) +{ + int name_len; + struct f_ccid *ccid; + struct ccid_opts *opts = container_of(fi, struct ccid_opts, func_inst); + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ccid = ccid_setup(); + if (IS_ERR(ccid)) + return PTR_ERR(ccid); + + opts->ccid = ccid; + + return 0; +} + +static void ccid_free_inst(struct usb_function_instance *f) +{ + struct ccid_opts *opts = container_of(f, struct ccid_opts, func_inst); + + if (!opts->ccid) + return; + + ccid_cdev_free(&opts->ccid->ctrl_dev.cdev); + ccid_cdev_free(&opts->ccid->bulk_dev.cdev); + ccid_free_chrdev_region(); + + kfree(opts->ccid); + kfree(opts); +} + + +static struct usb_function_instance *ccid_alloc_inst(void) +{ + struct ccid_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.set_inst_name = ccid_set_inst_name; + opts->func_inst.free_func_inst = ccid_free_inst; + config_group_init_type_name(&opts->func_inst.group, "", + &ccid_func_type); + + return &opts->func_inst; +} + +static struct usb_function *ccid_alloc(struct usb_function_instance *fi) +{ + struct ccid_opts *opts; + int ret; + + opts = container_of(fi, struct ccid_opts, func_inst); + + ret = ccid_bind_config(opts->ccid); + if (ret) + return ERR_PTR(ret); + + return &opts->ccid->function; +} + +DECLARE_USB_FUNCTION_INIT(ccid, ccid_alloc_inst, ccid_alloc); +MODULE_DESCRIPTION("USB CCID function Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_ccid.h b/drivers/usb/gadget/function/f_ccid.h new file mode 100644 index 000000000000..1fe626b8cf73 --- /dev/null +++ b/drivers/usb/gadget/function/f_ccid.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011, 2017 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __F_CCID_H +#define __F_CCID_H + +#define PROTOCOL_TO 0x01 +#define PROTOCOL_T1 0x02 +#define ABDATA_SIZE 512 + +/* define for dwFeatures for Smart Card Device Class Descriptors */ +/* No special characteristics */ +#define CCID_FEATURES_NADA 0x00000000 +/* Automatic parameter configuration based on ATR data */ +#define CCID_FEATURES_AUTO_PCONF 0x00000002 +/* Automatic activation of ICC on inserting */ +#define CCID_FEATURES_AUTO_ACTIV 0x00000004 +/* Automatic ICC voltage selection */ +#define CCID_FEATURES_AUTO_VOLT 0x00000008 +/* Automatic ICC clock frequency change */ +#define CCID_FEATURES_AUTO_CLOCK 0x00000010 +/* Automatic baud rate change */ +#define CCID_FEATURES_AUTO_BAUD 0x00000020 +/*Automatic parameters negotiation made by the CCID */ +#define CCID_FEATURES_AUTO_PNEGO 0x00000040 +/* Automatic PPS made by the CCID according to the active parameters */ +#define CCID_FEATURES_AUTO_PPS 0x00000080 +/* CCID can set ICC in clock stop mode */ +#define CCID_FEATURES_ICCSTOP 0x00000100 +/* NAD value other than 00 accepted (T=1 protocol in use) */ +#define CCID_FEATURES_NAD 0x00000200 +/* Automatic IFSD exchange as first exchange (T=1 protocol in use) */ +#define CCID_FEATURES_AUTO_IFSD 0x00000400 +/* TPDU level exchanges with CCID */ +#define CCID_FEATURES_EXC_TPDU 0x00010000 +/* Short APDU level exchange with CCID */ +#define CCID_FEATURES_EXC_SAPDU 0x00020000 +/* Short and Extended APDU level exchange with CCID */ +#define CCID_FEATURES_EXC_APDU 0x00040000 +/* USB Wake up signaling supported on card insertion and removal */ +#define CCID_FEATURES_WAKEUP 0x00100000 + +#define CCID_NOTIFY_CARD _IOW('C', 1, struct usb_ccid_notification) +#define CCID_NOTIFY_HWERROR _IOW('C', 2, struct usb_ccid_notification) +#define CCID_READ_DTR _IOR('C', 3, int) + +struct usb_ccid_notification { + __u8 buf[4]; +} __packed; + +struct ccid_bulk_in_header { + __u8 bMessageType; + __u32 wLength; + __u8 bSlot; + __u8 bSeq; + __u8 bStatus; + __u8 bError; + __u8 bSpecific; + __u8 abData[ABDATA_SIZE]; + __u8 bSizeToSend; +} __packed; + +struct ccid_bulk_out_header { + __u8 bMessageType; + __u32 wLength; + __u8 bSlot; + __u8 bSeq; + __u8 bSpecific_0; + __u8 bSpecific_1; + __u8 bSpecific_2; + __u8 APDU[ABDATA_SIZE]; +} __packed; +#endif diff --git a/drivers/usb/gadget/function/f_cdev.c b/drivers/usb/gadget/function/f_cdev.c new file mode 100644 index 000000000000..bc8961fbb16a --- /dev/null +++ b/drivers/usb/gadget/function/f_cdev.c @@ -0,0 +1,2028 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011, 2013-2021, The Linux Foundation. All rights reserved. + * Linux Foundation chooses to take subject only to the GPLv2 license terms, + * and distributes only under these terms. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * + * 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; + + /* function suspend status */ + bool func_is_suspended; + bool func_wakeup_allowed; + + 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; + u8 proto; +}; + +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 = USB_SUBCLASS_VENDOR_SPEC, + /* .bInterfaceProtocol = DYNAMIC */ + /* .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) +{ + struct f_cdev *port = func_to_port(f); + + port->func_wakeup_allowed = + !!(options & (USB_INTRF_FUNC_SUSPEND_RW >> 8)); + port->func_is_suspended = options & (USB_INTRF_FUNC_SUSPEND_LP >> 8); + + return 0; +} + +static int usb_cser_get_status(struct usb_function *f) +{ +#ifdef CONFIG_USB_FUNC_WAKEUP_SUPPORTED + struct f_cdev *port = func_to_port(f); + + return (port->func_wakeup_allowed ? USB_INTRF_STAT_FUNC_RW : 0) | + USB_INTRF_STAT_FUNC_RW_CAP; +#else + return 0; +#endif +} + +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; + struct f_cdev_opts *opts = + container_of(f->fi, struct f_cdev_opts, func_inst); + + 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; + cser_interface_desc.bInterfaceProtocol = opts->proto; + + 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) { + cdev_device_del(&opts->port->fcdev_cdev, &opts->port->dev); + mutex_lock(&chardev_ida_lock); + ida_simple_remove(&chardev_ida, opts->port->minor); + mutex_unlock(&chardev_ida_lock); + usb_cser_debugfs_exit(opts->port); + put_device(&opts->port->dev); + } + + usb_cser_chardev_deinit(); + kfree(opts->func_name); + kfree(opts); +} + +static void usb_cser_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_cdev *port = func_to_port(f); + + if (port->is_connected) + usb_cser_disable(f); + + /* Reset string id */ + cser_string_defs[0].id = 0; + + 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; + int ret; + + 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) { + list_add_tail(&req->list, &port->read_pool); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + if (req->status || !req->actual) { + /* + * ECONNRESET/EPIPE can be returned when host issues clear + * EP halt, restart OUT requests if so. + */ + if (req->status == -ECONNRESET || + req->status == -EPIPE) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = usb_ep_queue(ep, req, GFP_KERNEL); + if (!ret) + return; + spin_lock_irqsave(&port->port_lock, flags); + } + + 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); + fallthrough; + 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); + get_device(&port->dev); + if (port->port_open) { + pr_err("port is already opened.\n"); + put_device(&port->dev); + 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"); + put_device(&port->dev); + 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; + 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); + put_device(&port->dev); + + 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 && + port->func_is_suspended) { + ret = -EPERM; +#ifdef CONFIG_USB_FUNC_WAKEUP_SUPPORTED + pr_debug("Calling usb_func_wakeup\n"); + ret = usb_func_wakeup(func); +#endif + } 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 void cdev_device_release(struct device *dev) +{ + struct f_cdev *port = container_of(dev, struct f_cdev, dev); + + pr_debug("Free cdev port(%d)\n", port->port_num); + kfree(port); +} + +static struct f_cdev *f_cdev_alloc(char *func_name, int portno) +{ + int ret; + 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); + device_initialize(&port->dev); + port->dev.class = fcdev_classp; + port->dev.parent = NULL; + port->dev.release = cdev_device_release; + port->dev.devt = MKDEV(major, port->minor); + dev_set_name(&port->dev, port->name); + ret = cdev_device_add(&port->fcdev_cdev, &port->dev); + if (ret) { + pr_err("Failed to add cdev for port(%s)\n", port->name); + goto err_cdev_add; + } + + usb_cser_debugfs_init(port); + + pr_info("port_name:%s (%pK) portno:(%d)\n", + port->name, port, port->port_num); + return port; + +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; + opts->proto = 0x40; + } else { + opts->proto = 0x60; + } + + 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"); diff --git a/drivers/usb/gadget/function/f_fs_ipc_log.c b/drivers/usb/gadget/function/f_fs_ipc_log.c new file mode 100644 index 000000000000..55689e5785bc --- /dev/null +++ b/drivers/usb/gadget/function/f_fs_ipc_log.c @@ -0,0 +1,904 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include "u_fs.h" + +/* Copied from f_fs.c */ +struct ffs_io_data { + bool aio; + bool read; + + struct kiocb *kiocb; + struct iov_iter data; + const void *to_free; + char *buf; + + struct mm_struct *mm; + struct work_struct work; + + struct usb_ep *ep; + struct usb_request *req; + struct sg_table sgt; + bool use_sg; + + struct ffs_data *ffs; +}; + +/* Copied from f_fs.c */ +struct ffs_epfile { + struct mutex mutex; + struct ffs_data *ffs; + struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct dentry *dentry; + struct ffs_buffer *read_buffer; +#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) + char name[5]; + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + unsigned char _pad; +}; + +/* Copied from f_fs.c */ +struct ffs_ep { + struct usb_ep *ep; /* P: ffs->eps_lock */ + struct usb_request *req; /* P: epfile->mutex */ + /* [0]: full speed, [1]: high speed, [2]: super speed */ + struct usb_endpoint_descriptor *descs[3]; + + u8 num; + + int status; /* P: epfile->mutex */ +}; + +/* Copied from f_fs.c */ +struct ffs_sb_fill_data { + struct ffs_file_perms perms; + umode_t root_mode; + const char *dev_name; + bool no_disconnect; + struct ffs_data *ffs_data; +}; + +/* Copied from f_fs.c */ +struct ffs_function { + struct usb_configuration *conf; + struct usb_gadget *gadget; + struct ffs_data *ffs; + struct ffs_ep *eps; + u8 eps_revmap[16]; + short *interfaces_nums; + struct usb_function function; +}; + +/* Copied from f_fs.c */ +enum ffs_os_desc_type { + FFS_OS_DESC, FFS_OS_DESC_EXT_COMPAT, FFS_OS_DESC_EXT_PROP +}; + +#define kprobe_log(context, fmt, ...) \ + ipc_log_string(context, "%s: " fmt, \ + get_kretprobe(ri)->kp.symbol_name, ##__VA_ARGS__) + +#define MAX_IPC_INSTANCES 9 + +/* per-probe private data */ +struct kprobe_data { + void *x0; + void *x1; + void *x2; +}; + +struct ipc_log_work { + struct ffs_data *ffs; + const char *dev_name; + struct work_struct ctxt_work; +}; + +/* per-device IPC log data */ +struct ipc_log { + void *context; + struct ffs_data *ffs; +}; + +static struct workqueue_struct *ipc_wq; +static struct ipc_log ipc_log_s[MAX_IPC_INSTANCES]; + +/* Number of devices for f_fs driver */ +static int num_devices; + +static int ipc_inst_exists(struct ffs_data *ffs) +{ + int i = 0; + + for (i = 0; i < num_devices; i++) + if (ipc_log_s[i].ffs == ffs) + return i; + return -ENODEV; +} + +static void create_ipc_context_work(struct work_struct *w) +{ + struct ipc_log_work *ipc_w = container_of(w, struct ipc_log_work, + ctxt_work); + char ipcname[24] = "usb_ffs_"; + void *ctx; + + if (num_devices >= MAX_IPC_INSTANCES) { + pr_err("Can't create any more FFS log contexts\n"); + goto exit; + } + + if (ipc_inst_exists(ipc_w->ffs) >= 0) + goto exit; + + strlcat(ipcname, ipc_w->dev_name, sizeof(ipcname)); + ctx = ipc_log_context_create(10, ipcname, 0); + if (IS_ERR_OR_NULL(ctx)) { + pr_err("%s: Could not create IPC log context for device %s\n", + __func__, ipc_w->dev_name); + goto exit; + } + + ipc_log_s[num_devices].context = ctx; + ipc_log_s[num_devices].ffs = ipc_w->ffs; + num_devices++; + +exit: + kfree(ipc_w); +} + +static void create_ipc_context(const char *dev_name, struct ffs_data *ffs) +{ + struct ipc_log_work *ipc_w; + + ipc_w = kzalloc(sizeof(*ipc_w), GFP_ATOMIC); + if (!ipc_w) + return; + + INIT_WORK(&ipc_w->ctxt_work, create_ipc_context_work); + ipc_w->dev_name = ffs->dev_name; + ipc_w->ffs = ffs; + + queue_work(ipc_wq, &ipc_w->ctxt_work); +} + +static void *get_ipc_context(struct ffs_data *ffs) +{ + int idx = 0; + + idx = ipc_inst_exists(ffs); + if (idx >= 0) + return ipc_log_s[idx].context; + + /* not found, create a new one now */ + create_ipc_context(ffs->dev_name, ffs); + return NULL; +} + +static int entry_ffs_user_copy_worker(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct work_struct *work = (struct work_struct *)regs->regs[0]; + struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, work); + int ret = io_data->req->status ? io_data->req->status : + io_data->req->actual; + struct ffs_data *ffs = io_data->ffs; + void *context = get_ipc_context(ffs); + + data->x0 = work; + kprobe_log(context, "enter: ret %d for %s", + ret, io_data->read ? "read" : "write"); + return 0; +} + +static int entry_ffs_epfile_io(struct kretprobe_instance *ri, struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = (struct file *)regs->regs[0]; + struct ffs_io_data *io_data = (struct ffs_io_data *)regs->regs[1]; + struct ffs_epfile *epfile = file->private_data; + void *context = get_ipc_context(epfile->ffs); + + data->x0 = file; + data->x1 = io_data; + kprobe_log(context, "enter: %s about to queue %zd bytes", + epfile->name, iov_iter_count(&io_data->data)); + return 0; +} + +static int exit_ffs_epfile_io(struct kretprobe_instance *ri, struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = data->x0; + struct ffs_epfile *epfile = file->private_data; + unsigned long ret = regs_return_value(regs); + void *context = get_ipc_context(epfile->ffs); + + kprobe_log(context, "exit: %s ret %zd", epfile->name, ret); + return 0; +} + +static int entry_ffs_epfile_async_io_complete(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct usb_request *req = (struct usb_request *)regs->regs[1]; + struct ffs_io_data *io_data = req->context; + void *context = get_ipc_context(io_data->ffs); + + kprobe_log(context, "enter"); + return 0; +} + +static int entry_ffs_epfile_write_iter(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct kiocb *kiocb = (struct kiocb *)regs->regs[0]; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + void *context = get_ipc_context(epfile->ffs); + + data->x0 = kiocb; + kprobe_log(context, "enter"); + return 0; +} + +static int exit_ffs_epfile_write_iter(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct kiocb *kiocb = data->x0; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + void *context = get_ipc_context(epfile->ffs); + unsigned long ret = regs_return_value(regs); + + if (ret != -ENOMEM && ret != -EIOCBQUEUED) + kprobe_log(context, "exit: ret %zd", ret); + return 0; +} + +static int entry_ffs_epfile_read_iter(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct kiocb *kiocb = (struct kiocb *)regs->regs[0]; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + void *context = get_ipc_context(epfile->ffs); + + data->x0 = kiocb; + kprobe_log(context, "enter"); + return 0; +} + +static int exit_ffs_epfile_read_iter(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct kiocb *kiocb = data->x0; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + void *context = get_ipc_context(epfile->ffs); + unsigned long ret = regs_return_value(regs); + + kprobe_log(context, "exit: ret %zd", ret); + return 0; +} + +static int entry_ffs_data_put(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct ffs_data *ffs = (struct ffs_data *)regs->regs[0]; + void *context = get_ipc_context(ffs); + unsigned int refcount = refcount_read(&ffs->ref); + + kprobe_log(context, "ref %u", refcount); + + if (refcount == 1) { + ipc_log_context_destroy(context); + context = NULL; + } + return 0; +} + +static int entry_ffs_sb_fill(struct kretprobe_instance *ri, struct pt_regs *regs) +{ + struct fs_context *fc = (struct fs_context *)regs->regs[1]; + struct ffs_sb_fill_data *data = fc->fs_private; + + create_ipc_context(fc->source, data->ffs_data); + + return 0; +} + +static int entry___ffs_ep0_queue_wait(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct ffs_data *ffs = (struct ffs_data *)regs->regs[0]; + void *context = get_ipc_context(ffs); + + data->x0 = ffs; + kprobe_log(context, "enter: state %d setup_state %d flags %lu", + ffs->state, ffs->setup_state, ffs->flags); + return 0; +} + +static int exit___ffs_ep0_queue_wait(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct ffs_data *ffs = data->x0; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "exit: state %d setup_state %d flags %lu", + ffs->state, ffs->setup_state, ffs->flags); + return 0; +} + +static int entry_ffs_ep0_write(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = (struct file *)regs->regs[0]; + struct ffs_data *ffs = file->private_data; + unsigned int len = (unsigned int)regs->regs[2]; + void *context = get_ipc_context(ffs); + + data->x0 = file; + kprobe_log(context, "enter:len %zu state %d setup_state %d flags %lu", + len, ffs->state, ffs->setup_state, ffs->flags); + return 0; +} + +static int exit_ffs_ep0_write(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = data->x0; + struct ffs_data *ffs = file->private_data; + void *context = get_ipc_context(ffs); + unsigned long ret = regs_return_value(regs); + + if (ret != -EIDRM && ret != -EINVAL) + kprobe_log(context, + "exit:ret %zd state %d setup_state %d flags %lu", + ret, ffs->state, ffs->setup_state, ffs->flags); + + return 0; +} + +static int entry_ffs_ep0_read(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = (struct file *)regs->regs[0]; + size_t len = (size_t)regs->regs[2]; + struct ffs_data *ffs = file->private_data; + void *context = get_ipc_context(ffs); + size_t n; + + data->x0 = file; + n = min((len / sizeof(struct usb_functionfs_event)), (size_t)ffs->ev.count); + kprobe_log(context, "enter:len %zu state %d setup_state %d flags %lu n %zu", + len, ffs->state, ffs->setup_state, ffs->flags, n); + return 0; +} + +static int exit_ffs_ep0_read(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = data->x0; + struct ffs_data *ffs = file->private_data; + void *context = get_ipc_context(ffs); + unsigned long ret = regs_return_value(regs); + + kprobe_log(context, "exit:ret %d state %d setup_state %d flags %lu", + ret, ffs->state, ffs->setup_state, ffs->flags); + return 0; +} + +static int entry_ffs_ep0_open(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct inode *inode = (struct inode *)regs->regs[0]; + struct ffs_data *ffs = inode->i_private; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "state %d setup_state %d flags %lu opened %d", + ffs->state, ffs->setup_state, ffs->flags, + atomic_read(&ffs->opened)); + return 0; +} + +static int entry_ffs_ep0_release(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct file *file = (struct file *)regs->regs[1]; + struct ffs_data *ffs = file->private_data; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "state %d setup_state %d flags %lu opened %d", + ffs->state, ffs->setup_state, ffs->flags, + atomic_read(&ffs->opened)); + return 0; +} + +static int entry_ffs_ep0_ioctl(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct file *file = (struct file *)regs->regs[0]; + struct ffs_data *ffs = file->private_data; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "state %d setup_state %d flags %lu opened %d", + ffs->state, ffs->setup_state, ffs->flags, + atomic_read(&ffs->opened)); + return 0; +} + +static int entry_ffs_ep0_poll(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = (struct file *)regs->regs[0]; + struct ffs_data *ffs = file->private_data; + void *context = get_ipc_context(ffs); + + data->x0 = file; + kprobe_log(context, "state %d setup_state %d flags %lu opened %d", + ffs->state, ffs->setup_state, ffs->flags, + atomic_read(&ffs->opened)); + return 0; +} + +static int exit_ffs_ep0_poll(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + unsigned long ret = regs_return_value(regs); + struct file *file = data->x0; + struct ffs_data *ffs = file->private_data; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "exit: mask %u", ret); + return 0; +} + +static int entry_ffs_epfile_open(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct inode *inode = (struct inode *)regs->regs[0]; + struct ffs_epfile *epfile = inode->i_private; + void *context = get_ipc_context(epfile->ffs); + + kprobe_log(context, "%s: state %d setup_state %d flag %lu opened %u", + epfile->name, epfile->ffs->state, epfile->ffs->setup_state, + epfile->ffs->flags, atomic_read(&epfile->ffs->opened)); + return 0; +} + +static int entry_ffs_aio_cancel(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct kiocb *kiocb = (struct kiocb *)regs->regs[0]; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + void *context = get_ipc_context(epfile->ffs); + + data->x0 = kiocb; + kprobe_log(context, "enter:state %d setup_state %d flag %lu", + epfile->ffs->state, epfile->ffs->setup_state, + epfile->ffs->flags); + return 0; +} + +static int exit_ffs_aio_cancel(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + unsigned long ret = regs_return_value(regs); + struct kiocb *kiocb = data->x0; + struct ffs_epfile *epfile = kiocb->ki_filp->private_data; + void *context = get_ipc_context(epfile->ffs); + + kprobe_log(context, "exit: mask %u", ret); + return 0; +} + +static int entry_ffs_epfile_release(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct inode *inode = (struct inode *)regs->regs[0]; + struct ffs_epfile *epfile = inode->i_private; + void *context = get_ipc_context(epfile->ffs); + + kprobe_log(context, "%s: state %d setup_state %d flag %lu opened %u", + epfile->name, epfile->ffs->state, epfile->ffs->setup_state, + epfile->ffs->flags, atomic_read(&epfile->ffs->opened)); + return 0; +} + +static int entry_ffs_epfile_ioctl(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct file *file = (struct file *)regs->regs[0]; + unsigned int code = (unsigned int)regs->regs[1]; + unsigned long value = (unsigned long)regs->regs[2]; + struct ffs_epfile *epfile = file->private_data; + void *context = get_ipc_context(epfile->ffs); + + data->x0 = file; + kprobe_log(context, + "%s: code 0x%08x value %#lx state %d setup_state %d flag %lu", + epfile->name, code, value, epfile->ffs->state, + epfile->ffs->setup_state, epfile->ffs->flags); + return 0; +} + +static int exit_ffs_epfile_ioctl(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + unsigned long ret = regs_return_value(regs); + struct file *file = data->x0; + struct ffs_epfile *epfile = file->private_data; + void *context = get_ipc_context(epfile->ffs); + + kprobe_log(context, "exit: %s: ret %d\n", epfile->name, ret); + return 0; +} + +static int entry_ffs_data_opened(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct ffs_data *ffs = (struct ffs_data *)regs->regs[0]; + void *context = get_ipc_context(ffs); + + kprobe_log(context, + "enter: state %d setup_state %d flag %lu opened %d ref %d", + ffs->state, ffs->setup_state, ffs->flags, + atomic_read(&ffs->opened), refcount_read(&ffs->ref)); + return 0; +} + +static int entry_ffs_data_closed(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct ffs_data *ffs = (struct ffs_data *)regs->regs[0]; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "state %d setup_state %d flag %lu opened %d", + ffs->state, ffs->setup_state, ffs->flags, + atomic_read(&ffs->opened)); + return 0; +} + +static int entry_ffs_data_clear(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct ffs_data *ffs = (struct ffs_data *)regs->regs[0]; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "enter: state %d setup_state %d flag %lu", + ffs->state, ffs->setup_state, ffs->flags); + return 0; +} + +static int entry_functionfs_bind(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct ffs_data *ffs = (struct ffs_data *)regs->regs[0]; + void *context = get_ipc_context(ffs); + + data->x0 = ffs; + kprobe_log(context, "enter: state %d setup_state %d flag %lu", + ffs->state, ffs->setup_state, ffs->flags); + return 0; +} + +static int exit_functionfs_bind(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct ffs_data *ffs = data->x0; + void *context = get_ipc_context(ffs); + unsigned long ret = regs_return_value(regs); + + kprobe_log(context, "functionfs_bind returned %d", ret); + return 0; +} + +static int entry_ffs_func_eps_disable(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct ffs_function *func = (struct ffs_function *)regs->regs[0]; + void *context = get_ipc_context(func->ffs); + + kprobe_log(context, "enter: state %d setup_state %d flag %lu", + func->ffs->state, func->ffs->setup_state, func->ffs->flags); + return 0; +} + +static int entry_ffs_func_bind(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct usb_function *f = (struct usb_function *)regs->regs[1]; + struct f_fs_opts *ffs_opts = + container_of(f->fi, struct f_fs_opts, func_inst); + /* func->ffs not set yet; get ffs_data via ffs_opts instead */ + struct ffs_data *ffs = ffs_opts->dev->ffs_data; + void *context; + + if (!ffs) + return 0; + + context = get_ipc_context(ffs); + data->x0 = ffs; + kprobe_log(context, "enter"); + kprobe_log(context, "_ffs_func_bind", + "enter: state %d setup_state %d flag %lu", ffs->state, + ffs->setup_state, ffs->flags); + return 0; +} + +static int exit_ffs_func_bind(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + int ret = (int)regs_return_value(regs); + struct ffs_data *ffs = data->x0; + void *context; + + if (!ffs) + return 0; + + context = get_ipc_context(ffs); + if (ret < 0) + kprobe_log(context, "exit: ret %d", ret); + return 0; +} + +static int entry_ffs_reset_work(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct work_struct *work = (struct work_struct *)regs->regs[0]; + struct ffs_data *ffs = container_of(work, struct ffs_data, reset_work); + void *context = get_ipc_context(ffs); + + kprobe_log(context, "enter"); + return 0; +} + +static int entry_ffs_func_set_alt(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct usb_function *f = (struct usb_function *)regs->regs[0]; + struct ffs_function *func = container_of(f, struct ffs_function, function); + unsigned int alt = (unsigned int)regs->regs[2]; + void *context = get_ipc_context(func->ffs); + + data->x0 = func; + kprobe_log(context, "enter: alt %d", (int)alt); + return 0; +} + +static int entry_ffs_func_disable(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct usb_function *f = (struct usb_function *)regs->regs[0]; + struct ffs_function *func = container_of(f, struct ffs_function, function); + void *context = get_ipc_context(func->ffs); + + kprobe_log(context, "enter"); + return 0; +} + +static int entry_ffs_func_setup(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct usb_function *f = (struct usb_function *)regs->regs[0]; + struct ffs_function *func = container_of(f, struct ffs_function, function); + struct usb_ctrlrequest *creq = (struct usb_ctrlrequest *)regs->regs[1]; + struct ffs_data *ffs = func->ffs; + void *context = get_ipc_context(func->ffs); + + kprobe_log(context, + "enter: state %d reqtype=%02x req=%02x wv=%04x wi=%04x wl=%04x", + ffs->state, creq->bRequestType, creq->bRequest, + le16_to_cpu(creq->wValue), le16_to_cpu(creq->wIndex), + le16_to_cpu(creq->wLength)); + return 0; +} + +static int entry_ffs_func_suspend(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct usb_function *f = (struct usb_function *)regs->regs[0]; + struct ffs_function *func = container_of(f, struct ffs_function, function); + void *context = get_ipc_context(func->ffs); + + kprobe_log(context, "enter"); + return 0; +} + +static int entry_ffs_func_resume(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct usb_function *f = (struct usb_function *)regs->regs[0]; + struct ffs_function *func = container_of(f, struct ffs_function, function); + void *context = get_ipc_context(func->ffs); + + kprobe_log(context, "enter"); + return 0; +} + +static int entry_ffs_func_unbind(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct usb_function *f = (struct usb_function *)regs->regs[1]; + struct ffs_function *func = container_of(f, struct ffs_function, function); + struct ffs_data *ffs = func->ffs; + struct f_fs_opts *opts = + container_of(f->fi, struct f_fs_opts, func_inst); + void *context = get_ipc_context(ffs); + + data->x1 = ffs; + kprobe_log(context, "enter: state %d setup_state %d flag %lu refcnt %u", + ffs->state, ffs->setup_state, ffs->flags, opts->refcnt); + return 0; +} + +static int exit_ffs_func_unbind(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct ffs_data *ffs = data->x1; + void *context = get_ipc_context(ffs); + + kprobe_log(context, "exit: state %d setup_state %d flag %lu", + ffs->state, ffs->setup_state, ffs->flags); + return 0; +} + +static int entry_ffs_closed(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct ffs_data *ffs = (struct ffs_data *)regs->regs[0]; + void *context = get_ipc_context(ffs); + + data->x0 = ffs; + kprobe_log(context, "enter"); + return 0; +} + +static int exit_ffs_closed(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct ffs_data *ffs = data->x0; + struct ffs_dev *ffs_obj = ffs->private_data; + void *context = get_ipc_context(ffs); + + if (test_bit(FFS_FL_BOUND, &ffs->flags)) + kprobe_log(context, "unreg gadget done"); + else if (!ffs_obj || !ffs_obj->opts || ffs_obj->opts->no_configfs || + !ffs_obj->opts->func_inst.group.cg_item.ci_parent + || !kref_read(&ffs_obj->opts->func_inst.group.cg_item.ci_kref)) + kprobe_log(context, "exit error"); + return 0; +} + +#define ENTRY_EXIT(name) {\ + .handler = exit_##name,\ + .entry_handler = entry_##name,\ + .data_size = sizeof(struct kprobe_data),\ + .maxactive = MAX_IPC_INSTANCES,\ + .kp.symbol_name = #name,\ +} + +#define ENTRY(name) {\ + .entry_handler = entry_##name,\ + .data_size = sizeof(struct kprobe_data),\ + .maxactive = MAX_IPC_INSTANCES,\ + .kp.symbol_name = #name,\ +} + +static struct kretprobe ffsprobes[] = { + ENTRY(ffs_user_copy_worker), + ENTRY_EXIT(ffs_epfile_io), + ENTRY(ffs_epfile_async_io_complete), + ENTRY_EXIT(ffs_epfile_write_iter), + ENTRY_EXIT(ffs_epfile_read_iter), + ENTRY(ffs_data_put), + ENTRY(ffs_sb_fill), + ENTRY_EXIT(__ffs_ep0_queue_wait), + ENTRY_EXIT(ffs_ep0_write), + ENTRY_EXIT(ffs_ep0_read), + ENTRY(ffs_ep0_open), + ENTRY(ffs_ep0_release), + ENTRY(ffs_ep0_ioctl), + ENTRY_EXIT(ffs_ep0_poll), + ENTRY(ffs_epfile_open), + ENTRY_EXIT(ffs_aio_cancel), + ENTRY(ffs_epfile_release), + ENTRY_EXIT(ffs_epfile_ioctl), + ENTRY(ffs_data_opened), + ENTRY(ffs_data_closed), + ENTRY(ffs_data_clear), + ENTRY_EXIT(functionfs_bind), + ENTRY(ffs_func_eps_disable), + ENTRY_EXIT(ffs_func_bind), + ENTRY(ffs_reset_work), + ENTRY(ffs_func_set_alt), + ENTRY(ffs_func_disable), + ENTRY(ffs_func_setup), + ENTRY(ffs_func_suspend), + ENTRY(ffs_func_resume), + ENTRY_EXIT(ffs_func_unbind), + ENTRY_EXIT(ffs_closed) +}; + +static int __init kretprobe_init(void) +{ + int ret; + int i; + + ipc_wq = alloc_ordered_workqueue("ipc_wq", 0); + if (!ipc_wq) { + pr_err("%s: Unable to create workqueue ipc_wq\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < ARRAY_SIZE(ffsprobes); i++) { + ret = register_kretprobe(&ffsprobes[i]); + if (ret < 0) { + pr_err("register_kretprobe failed at %s, returned %d\n", + ffsprobes[i].kp.symbol_name, ret); + } + } + return 0; +} + +static void __exit kretprobe_exit(void) +{ + int i; + + destroy_workqueue(ipc_wq); + for (i = 0; i < num_devices; i++) { + if (ipc_log_s[i].ffs) { + ipc_log_context_destroy(ipc_log_s[i].context); + ipc_log_s[i].context = NULL; + } + } + + for (i = 0; i < ARRAY_SIZE(ffsprobes); i++) { + unregister_kretprobe(&ffsprobes[i]); + + /* nmissed > 0 suggests that maxactive was set too low. */ + if (ffsprobes[i].nmissed > 0) + pr_info("Missed probing %d instances of %s\n", + ffsprobes[i].nmissed, + ffsprobes[i].kp.symbol_name); + } +} + +module_init(kretprobe_init) +module_exit(kretprobe_exit) +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_gsi.c b/drivers/usb/gadget/function/f_gsi.c new file mode 100644 index 000000000000..a9e04938aade --- /dev/null +++ b/drivers/usb/gadget/function/f_gsi.c @@ -0,0 +1,3740 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include "f_gsi.h" +#include "rndis.h" + +static struct usb_gsi_debugfs { + struct dentry *debugfs_root; + bool qti_packet_debug; +} debugfs; + +static struct workqueue_struct *ipa_usb_wq; +static struct gsi_inst_status { + struct mutex gsi_lock; + bool inst_exist; + struct gsi_opts *opts; +} inst_status[IPA_USB_MAX_TETH_PROT_SIZE]; + +#define MAX_CDEV_INSTANCES 3 + +static int major; +static struct class *gsi_class; +static DEFINE_IDA(gsi_ida); + +/* Deregister misc device and free instance structures */ +static void gsi_inst_clean(struct gsi_opts *opts); +static void gsi_rndis_ipa_reset_trigger(struct gsi_data_port *d_port); +static int gsi_ctrl_send_notification(struct f_gsi *gsi); +static struct gsi_ctrl_pkt *gsi_ctrl_pkt_alloc(unsigned int len, gfp_t flags); +static void gsi_ctrl_pkt_free(struct gsi_ctrl_pkt *pkt); + +static inline bool usb_gsi_remote_wakeup_allowed(struct usb_function *f) +{ + bool remote_wakeup_allowed = true; + struct f_gsi *gsi = func_to_gsi(f); + + if (f->config->cdev->gadget->speed >= USB_SPEED_SUPER) + remote_wakeup_allowed = gsi->func_wakeup_allowed; + + log_event_dbg("%s: remote_wakeup_allowed:%s", __func__, + (remote_wakeup_allowed ? "true" : "false")); + return remote_wakeup_allowed; +} + +static void post_event(struct gsi_data_port *port, u8 event) +{ + unsigned long flags; + struct f_gsi *gsi = d_port_to_gsi(port); + + spin_lock_irqsave(&port->evt_q.q_lock, flags); + + port->evt_q.tail++; + /* Check for wraparound and make room */ + port->evt_q.tail = port->evt_q.tail % MAXQUEUELEN; + + /* Check for overflow */ + if (port->evt_q.tail == port->evt_q.head) { + log_event_err("%s: event queue overflow error", __func__); + spin_unlock_irqrestore(&port->evt_q.q_lock, flags); + return; + } + /* Add event to queue */ + port->evt_q.event[port->evt_q.tail] = event; + spin_unlock_irqrestore(&port->evt_q.q_lock, flags); +} + +static void __maybe_unused post_event_to_evt_queue(struct gsi_data_port *port, + u8 event) +{ + post_event(port, event); + queue_delayed_work(port->ipa_usb_wq, &port->usb_ipa_w, 0); +} + +static u8 read_event(struct gsi_data_port *port) +{ + u8 event; + unsigned long flags; + struct f_gsi *gsi = d_port_to_gsi(port); + + spin_lock_irqsave(&port->evt_q.q_lock, flags); + if (port->evt_q.head == port->evt_q.tail) { + log_event_dbg("%s: event queue empty", __func__); + spin_unlock_irqrestore(&port->evt_q.q_lock, flags); + return EVT_NONE; + } + + port->evt_q.head++; + /* Check for wraparound and make room */ + port->evt_q.head = port->evt_q.head % MAXQUEUELEN; + + event = port->evt_q.event[port->evt_q.head]; + spin_unlock_irqrestore(&port->evt_q.q_lock, flags); + + return event; +} + +static u8 peek_event(struct gsi_data_port *port) +{ + u8 event; + unsigned long flags; + u8 peek_index = 0; + struct f_gsi *gsi = d_port_to_gsi(port); + + spin_lock_irqsave(&port->evt_q.q_lock, flags); + if (port->evt_q.head == port->evt_q.tail) { + log_event_dbg("%s: event queue empty", __func__); + spin_unlock_irqrestore(&port->evt_q.q_lock, flags); + return EVT_NONE; + } + + peek_index = (port->evt_q.head + 1) % MAXQUEUELEN; + event = port->evt_q.event[peek_index]; + spin_unlock_irqrestore(&port->evt_q.q_lock, flags); + + return event; +} + +static void __maybe_unused reset_event_queue(struct gsi_data_port *port) +{ + unsigned long flags; + + spin_lock_irqsave(&port->evt_q.q_lock, flags); + port->evt_q.head = port->evt_q.tail = MAXQUEUELEN - 1; + memset(&port->evt_q.event[0], EVT_NONE, MAXQUEUELEN); + spin_unlock_irqrestore(&port->evt_q.q_lock, flags); +} + +static int gsi_wakeup_host(struct f_gsi *gsi) +{ + + int ret; + struct usb_gadget *gadget; + struct usb_function *func; + + func = &gsi->function; + gadget = gsi->function.config->cdev->gadget; + + log_event_dbg("Entering %s", __func__); + + if (!gadget) { + log_event_err("FAILED: d_port->cdev->gadget == NULL"); + return -ENODEV; + } + + /* + * In Super-Speed mode, remote wakeup is not allowed for suspended + * functions which have been disallowed by the host to issue Function + * Remote Wakeup. + * Note - We deviate here from the USB 3.0 spec and allow + * non-suspended functions to issue remote-wakeup even if they were not + * allowed to do so by the host. This is done in order to support non + * fully USB 3.0 compatible hosts. + */ + if ((gadget->speed >= USB_SPEED_SUPER) && (gsi->func_is_suspended)) { + ret = -EOPNOTSUPP; +#if IS_ENABLED(CONFIG_USB_FUNC_WAKEUP_SUPPORTED) + log_event_dbg("%s: Calling usb_func_wakeup", __func__); + ret = usb_func_wakeup(func); +#endif + } else { + log_event_dbg("%s: Calling usb_gadget_wakeup", __func__); + ret = usb_gadget_wakeup(gadget); + } + + if ((ret == -EBUSY) || (ret == -EAGAIN)) + log_event_dbg("RW delayed due to LPM exit."); + else if (ret) + log_event_err("wakeup failed. ret=%d.", ret); + + return ret; +} + +static void gsi_rw_timer_func(struct timer_list *t) +{ + struct f_gsi *gsi = from_timer(gsi, t, gsi_rw_timer); + + if (!atomic_read(&gsi->connected)) { + log_event_dbg("%s: gsi not connected.. bail-out\n", __func__); + gsi->debugfs_rw_timer_enable = 0; + return; + } + + log_event_dbg("%s: calling gsi_wakeup_host\n", __func__); + gsi_wakeup_host(gsi); + + if (gsi->debugfs_rw_timer_enable) { + log_event_dbg("%s: re-arm the timer\n", __func__); + mod_timer(&gsi->gsi_rw_timer, + jiffies + msecs_to_jiffies(gsi->gsi_rw_timer_interval)); + } +} + +static struct f_gsi *get_connected_gsi(void) +{ + struct f_gsi *connected_gsi; + bool gsi_connected = false; + int i; + + for (i = 0; i < IPA_USB_MAX_TETH_PROT_SIZE; i++) { + if (inst_status[i].opts) + connected_gsi = inst_status[i].opts->gsi; + else + continue; + + if (connected_gsi && atomic_read(&connected_gsi->connected)) { + gsi_connected = true; + break; + } + } + + if (!gsi_connected) + connected_gsi = NULL; + + return connected_gsi; +} + +#define DEFAULT_RW_TIMER_INTERVAL 500 /* in ms */ +static ssize_t usb_gsi_rw_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct f_gsi *gsi; + u8 input; + int ret; + + gsi = get_connected_gsi(); + if (!gsi) { + log_event_dbg("%s: gsi not connected\n", __func__); + goto err; + } + + if (ubuf == NULL) { + log_event_dbg("%s: buffer is Null.\n", __func__); + goto err; + } + + ret = kstrtou8_from_user(ubuf, count, 0, &input); + if (ret) { + log_event_err("%s: Invalid value. err:%d\n", __func__, ret); + goto err; + } + + if (gsi->debugfs_rw_timer_enable == !!input) { + if (!!input) + log_event_dbg("%s: RW already enabled\n", __func__); + else + log_event_dbg("%s: RW already disabled\n", __func__); + goto err; + } + + gsi->debugfs_rw_timer_enable = !!input; + + if (gsi->debugfs_rw_timer_enable) { + mod_timer(&gsi->gsi_rw_timer, jiffies + + msecs_to_jiffies(gsi->gsi_rw_timer_interval)); + log_event_dbg("%s: timer initialized\n", __func__); + } else { + del_timer_sync(&gsi->gsi_rw_timer); + log_event_dbg("%s: timer deleted\n", __func__); + } + +err: + return count; +} + +static int usb_gsi_rw_show(struct seq_file *s, void *unused) +{ + + struct f_gsi *gsi; + + gsi = get_connected_gsi(); + if (!gsi) { + log_event_dbg("%s: gsi not connected\n", __func__); + return 0; + } + + seq_printf(s, "%d\n", gsi->debugfs_rw_timer_enable); + + return 0; +} + +static int usb_gsi_rw_open(struct inode *inode, struct file *f) +{ + return single_open(f, usb_gsi_rw_show, inode->i_private); +} + +static const struct file_operations fops_usb_gsi_rw = { + .open = usb_gsi_rw_open, + .read = seq_read, + .write = usb_gsi_rw_write, + .owner = THIS_MODULE, + .llseek = seq_lseek, + .release = seq_release, +}; + +static ssize_t usb_gsi_rw_timer_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct f_gsi *gsi; + u16 timer_val; + int ret; + + gsi = get_connected_gsi(); + if (!gsi) { + log_event_dbg("%s: gsi not connected\n", __func__); + goto err; + } + + if (ubuf == NULL) { + log_event_dbg("%s: buffer is NULL.\n", __func__); + goto err; + } + + ret = kstrtou16_from_user(ubuf, count, 0, &timer_val); + if (ret) { + log_event_err("%s: Invalid value. err:%d\n", __func__, ret); + goto err; + } + + if (timer_val <= 0 || timer_val > 10000) { + log_event_err("%s: value must be > 0 and < 10000.\n", __func__); + goto err; + } + + gsi->gsi_rw_timer_interval = timer_val; +err: + return count; +} + +static int usb_gsi_rw_timer_show(struct seq_file *s, void *unused) +{ + struct f_gsi *gsi; + + gsi = get_connected_gsi(); + if (!gsi) { + log_event_dbg("%s: gsi not connected\n", __func__); + return 0; + } + + seq_printf(s, "%ums\n", gsi->gsi_rw_timer_interval); + + return 0; +} + +static int usb_gsi_rw_timer_open(struct inode *inode, struct file *f) +{ + return single_open(f, usb_gsi_rw_timer_show, inode->i_private); +} + +static const struct file_operations fops_usb_gsi_rw_timer = { + .open = usb_gsi_rw_timer_open, + .read = seq_read, + .write = usb_gsi_rw_timer_write, + .owner = THIS_MODULE, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int usb_gsi_debugfs_init(void) +{ + debugfs.debugfs_root = debugfs_create_dir("usb_gsi", NULL); + if (!debugfs.debugfs_root) + return -ENOMEM; + + debugfs_create_file("remote_wakeup_enable", 0600, + debugfs.debugfs_root, + inst_status, &fops_usb_gsi_rw); + debugfs_create_file("remote_wakeup_interval", 0600, + debugfs.debugfs_root, + inst_status, + &fops_usb_gsi_rw_timer); + debugfs_create_bool("log_ctrl_packets", 0644, debugfs.debugfs_root, + &debugfs.qti_packet_debug); + + return 0; +} + +static void usb_gsi_debugfs_exit(void) +{ + debugfs_remove_recursive(debugfs.debugfs_root); +} + +/* + * Callback for when network interface is up and userspace is ready + * to answer DHCP requests or remote wakeup. + */ +static int ipa_usb_notify_cb(enum ipa_usb_notify_event event, + void *driver_data) +{ + struct f_gsi *gsi = driver_data; + unsigned long flags; + struct gsi_ctrl_pkt *cpkt_notify_connect, *cpkt_notify_speed; + + if (!gsi) { + log_event_err("%s: invalid driver data", __func__); + return -EINVAL; + } + + spin_lock_irqsave(&gsi->d_port.lock, flags); + + switch (event) { + case IPA_USB_DEVICE_READY: + + if (gsi->d_port.net_ready_trigger) { + spin_unlock_irqrestore(&gsi->d_port.lock, flags); + log_event_dbg("%s: Already triggered", __func__); + return 1; + } + + log_event_err("%s: Set net_ready_trigger", __func__); + gsi->d_port.net_ready_trigger = true; + + if (gsi->prot_id == IPA_USB_ECM) { + cpkt_notify_connect = gsi_ctrl_pkt_alloc(0, GFP_ATOMIC); + if (IS_ERR(cpkt_notify_connect)) { + spin_unlock_irqrestore(&gsi->d_port.lock, + flags); + log_event_dbg("%s: err cpkt_notify_connect\n", + __func__); + return -ENOMEM; + } + cpkt_notify_connect->type = GSI_CTRL_NOTIFY_CONNECT; + + cpkt_notify_speed = gsi_ctrl_pkt_alloc(0, GFP_ATOMIC); + if (IS_ERR(cpkt_notify_speed)) { + spin_unlock_irqrestore(&gsi->d_port.lock, + flags); + gsi_ctrl_pkt_free(cpkt_notify_connect); + log_event_dbg("%s: err cpkt_notify_speed\n", + __func__); + return -ENOMEM; + } + cpkt_notify_speed->type = GSI_CTRL_NOTIFY_SPEED; + spin_lock(&gsi->c_port.lock); + list_add_tail(&cpkt_notify_connect->list, + &gsi->c_port.cpkt_resp_q); + list_add_tail(&cpkt_notify_speed->list, + &gsi->c_port.cpkt_resp_q); + spin_unlock(&gsi->c_port.lock); + gsi_ctrl_send_notification(gsi); + } + + /* + * Do not post EVT_CONNECTED for RNDIS. + * Data path for RNDIS is enabled on EVT_HOST_READY. + */ + if (gsi->prot_id != IPA_USB_RNDIS) { + post_event(&gsi->d_port, EVT_IPA_READY); + queue_delayed_work(gsi->d_port.ipa_usb_wq, + &gsi->d_port.usb_ipa_w, 0); + } + break; + + case IPA_USB_REMOTE_WAKEUP: + gsi_wakeup_host(gsi); + break; + + case IPA_USB_SUSPEND_COMPLETED: + post_event(&gsi->d_port, EVT_IPA_SUSPEND); + queue_delayed_work(gsi->d_port.ipa_usb_wq, + &gsi->d_port.usb_ipa_w, 0); + break; + } + + spin_unlock_irqrestore(&gsi->d_port.lock, flags); + return 1; +} + +static int ipa_connect_channels(struct gsi_data_port *d_port) +{ + int ret; + struct f_gsi *gsi = d_port_to_gsi(d_port); + struct ipa_usb_xdci_chan_params *in_params = + &d_port->ipa_in_channel_params; + struct ipa_usb_xdci_chan_params *out_params = + &d_port->ipa_out_channel_params; + struct ipa_usb_xdci_connect_params *conn_params = + &d_port->ipa_conn_pms; + struct usb_composite_dev *cdev = gsi->function.config->cdev; + struct gsi_channel_info gsi_channel_info; + struct ipa_req_chan_out_params ipa_in_channel_out_params; + struct ipa_req_chan_out_params ipa_out_channel_out_params; + + log_event_dbg("IN: num_bufs:=%zu, buf_len=%zu\n", + d_port->in_request.num_bufs, d_port->in_request.buf_len); + + ret = usb_gsi_ep_op(d_port->in_ep, &d_port->in_request, + GSI_EP_OP_PREPARE_TRBS); + if (ret) { + log_event_err("%s: GSI_EP_OP_PREPARE_TRBS failed: %d\n", + __func__, ret); + return ret; + } + + ret = usb_gsi_ep_op(d_port->in_ep, &d_port->in_request, + GSI_EP_OP_STARTXFER); + if (ret) { + log_event_err("%s: GSI_EP_OP_STARTXFER failed: %d\n", + __func__, ret); + goto free_trb_ep_in; + } + + d_port->in_xfer_rsc_index = usb_gsi_ep_op(d_port->in_ep, NULL, + GSI_EP_OP_GET_XFER_IDX); + + memset(in_params, 0x0, sizeof(*in_params)); + gsi_channel_info.ch_req = &d_port->in_request; + usb_gsi_ep_op(d_port->in_ep, (void *)&gsi_channel_info, + GSI_EP_OP_GET_CH_INFO); + + log_event_dbg("%s: USB GSI IN OPS Completed", __func__); + in_params->client = + (gsi->prot_id != IPA_USB_DIAG) ? IPA_CLIENT_USB_CONS : + IPA_CLIENT_USB_DPL_CONS; + in_params->ipa_ep_cfg.mode.mode = IPA_BASIC; + in_params->teth_prot = gsi->prot_id; + in_params->gevntcount_low_addr = + gsi_channel_info.gevntcount_low_addr; + in_params->gevntcount_hi_addr = + gsi_channel_info.gevntcount_hi_addr; + in_params->dir = GSI_CHAN_DIR_FROM_GSI; + in_params->xfer_ring_len = gsi_channel_info.xfer_ring_len; + in_params->xfer_scratch.last_trb_addr_iova = + gsi_channel_info.last_trb_addr; + in_params->xfer_ring_base_addr_iova = + gsi_channel_info.xfer_ring_base_addr; + in_params->data_buff_base_len = d_port->in_request.buf_len * + d_port->in_request.num_bufs; + in_params->data_buff_base_addr_iova = d_port->in_request.dma; + in_params->sgt_xfer_rings = &d_port->in_request.sgt_trb_xfer_ring; + in_params->sgt_data_buff = &d_port->in_request.sgt_data_buff; + log_event_dbg("%s(): IN: sgt_xfer_rings:%pK sgt_data_buff:%pK\n", + __func__, in_params->sgt_xfer_rings, in_params->sgt_data_buff); + in_params->xfer_scratch.const_buffer_size = + gsi_channel_info.const_buffer_size; + in_params->xfer_scratch.depcmd_low_addr = + gsi_channel_info.depcmd_low_addr; + in_params->xfer_scratch.depcmd_hi_addr = + gsi_channel_info.depcmd_hi_addr; + + if (d_port->out_ep) { + log_event_dbg("OUT: num_bufs:=%zu, buf_len=%zu\n", + d_port->out_request.num_bufs, + d_port->out_request.buf_len); + ret = usb_gsi_ep_op(d_port->out_ep, &d_port->out_request, + GSI_EP_OP_PREPARE_TRBS); + if (ret) { + log_event_err("%s: GSI_EP_OP_PREPARE_TRBS failed: %d\n", + __func__, ret); + goto end_xfer_ep_in; + } + + ret = usb_gsi_ep_op(d_port->out_ep, &d_port->out_request, + GSI_EP_OP_STARTXFER); + if (ret) { + log_event_err("%s: GSI_EP_OP_STARTXFER failed: %d\n", + __func__, ret); + goto free_trb_ep_out; + } + + d_port->out_xfer_rsc_index = + usb_gsi_ep_op(d_port->out_ep, + NULL, GSI_EP_OP_GET_XFER_IDX); + memset(out_params, 0x0, sizeof(*out_params)); + gsi_channel_info.ch_req = &d_port->out_request; + usb_gsi_ep_op(d_port->out_ep, (void *)&gsi_channel_info, + GSI_EP_OP_GET_CH_INFO); + log_event_dbg("%s: USB GSI OUT OPS Completed", __func__); + out_params->client = IPA_CLIENT_USB_PROD; + out_params->ipa_ep_cfg.mode.mode = IPA_BASIC; + out_params->teth_prot = gsi->prot_id; + out_params->gevntcount_low_addr = + gsi_channel_info.gevntcount_low_addr; + out_params->gevntcount_hi_addr = + gsi_channel_info.gevntcount_hi_addr; + out_params->dir = GSI_CHAN_DIR_TO_GSI; + out_params->xfer_ring_len = + gsi_channel_info.xfer_ring_len; + out_params->xfer_ring_base_addr_iova = + gsi_channel_info.xfer_ring_base_addr; + out_params->data_buff_base_len = d_port->out_request.buf_len * + d_port->out_request.num_bufs; + out_params->data_buff_base_addr_iova = + d_port->out_request.dma; + out_params->sgt_xfer_rings = + &d_port->out_request.sgt_trb_xfer_ring; + out_params->sgt_data_buff = &d_port->out_request.sgt_data_buff; + log_event_dbg("%s(): OUT: sgt_xfer_rings:%pK sgt_data_buff:%pK\n", + __func__, out_params->sgt_xfer_rings, + out_params->sgt_data_buff); + + out_params->xfer_scratch.last_trb_addr_iova = + gsi_channel_info.last_trb_addr; + out_params->xfer_scratch.const_buffer_size = + gsi_channel_info.const_buffer_size; + out_params->xfer_scratch.depcmd_low_addr = + gsi_channel_info.depcmd_low_addr; + out_params->xfer_scratch.depcmd_hi_addr = + gsi_channel_info.depcmd_hi_addr; + } + + /* Populate connection params */ + conn_params->max_pkt_size = + (cdev->gadget->speed >= USB_SPEED_SUPER) ? + IPA_USB_SUPER_SPEED_1024B : IPA_USB_HIGH_SPEED_512B; + conn_params->ipa_to_usb_xferrscidx = + d_port->in_xfer_rsc_index; + conn_params->usb_to_ipa_xferrscidx = + d_port->out_xfer_rsc_index; + conn_params->usb_to_ipa_xferrscidx_valid = + (gsi->prot_id != IPA_USB_DIAG) ? true : false; + conn_params->ipa_to_usb_xferrscidx_valid = true; + conn_params->teth_prot = gsi->prot_id; + conn_params->teth_prot_params.max_xfer_size_bytes_to_dev = 23700; + conn_params->teth_prot_params.max_xfer_size_bytes_to_dev + = d_port->out_aggr_size; + conn_params->teth_prot_params.max_xfer_size_bytes_to_host + = d_port->in_aggr_size; + conn_params->teth_prot_params.max_packet_number_to_dev = + DEFAULT_MAX_PKT_PER_XFER; + conn_params->max_supported_bandwidth_mbps = + (cdev->gadget->speed >= USB_SPEED_SUPER) ? 3600 : 400; + + memset(&ipa_in_channel_out_params, 0x0, + sizeof(ipa_in_channel_out_params)); + memset(&ipa_out_channel_out_params, 0x0, + sizeof(ipa_out_channel_out_params)); + + log_event_dbg("%s: Calling xdci_connect", __func__); + ret = ipa_usb_xdci_connect(out_params, in_params, + &ipa_out_channel_out_params, + &ipa_in_channel_out_params, + conn_params); + if (ret) { + log_event_err("%s: IPA connect failed %d", __func__, ret); + goto end_xfer_ep_out; + } + log_event_dbg("%s: xdci_connect done", __func__); + + log_event_dbg("%s: IN CH HDL %x", __func__, + ipa_in_channel_out_params.clnt_hdl); + log_event_dbg("%s: IN CH DBL addr %x", __func__, + ipa_in_channel_out_params.db_reg_phs_addr_lsb); + + log_event_dbg("%s: OUT CH HDL %x", __func__, + ipa_out_channel_out_params.clnt_hdl); + log_event_dbg("%s: OUT CH DBL addr %x", __func__, + ipa_out_channel_out_params.db_reg_phs_addr_lsb); + + d_port->in_channel_handle = ipa_in_channel_out_params.clnt_hdl; + d_port->in_request.db_reg_phs_addr_lsb = + ipa_in_channel_out_params.db_reg_phs_addr_lsb; + d_port->in_request.db_reg_phs_addr_msb = + ipa_in_channel_out_params.db_reg_phs_addr_msb; + + if (gsi->prot_id != IPA_USB_DIAG) { + d_port->out_channel_handle = + ipa_out_channel_out_params.clnt_hdl; + d_port->out_request.db_reg_phs_addr_lsb = + ipa_out_channel_out_params.db_reg_phs_addr_lsb; + d_port->out_request.db_reg_phs_addr_msb = + ipa_out_channel_out_params.db_reg_phs_addr_msb; + } + + return ret; + +end_xfer_ep_out: + if (d_port->out_ep) + usb_gsi_ep_op(d_port->out_ep, NULL, + GSI_EP_OP_ENDXFER); +free_trb_ep_out: + if (d_port->out_ep) + usb_gsi_ep_op(d_port->out_ep, &d_port->out_request, + GSI_EP_OP_FREE_TRBS); +end_xfer_ep_in: + usb_gsi_ep_op(d_port->in_ep, NULL, + GSI_EP_OP_ENDXFER); +free_trb_ep_in: + usb_gsi_ep_op(d_port->in_ep, &d_port->in_request, + GSI_EP_OP_FREE_TRBS); + return ret; +} + +static void ipa_data_path_enable(struct gsi_data_port *d_port) +{ + struct f_gsi *gsi = d_port_to_gsi(d_port); + bool block_db = false; + + log_event_dbg("IN: db_reg_phs_addr_lsb = %x", + gsi->d_port.in_request.db_reg_phs_addr_lsb); + usb_gsi_ep_op(gsi->d_port.in_ep, + &gsi->d_port.in_request, + GSI_EP_OP_STORE_DBL_INFO); + + if (gsi->d_port.out_ep) { + log_event_dbg("OUT: db_reg_phs_addr_lsb = %x", + gsi->d_port.out_request.db_reg_phs_addr_lsb); + usb_gsi_ep_op(gsi->d_port.out_ep, + &gsi->d_port.out_request, + GSI_EP_OP_STORE_DBL_INFO); + + usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request, + GSI_EP_OP_ENABLE_GSI); + } + + /* Unblock doorbell to GSI */ + usb_gsi_ep_op(d_port->in_ep, (void *)&block_db, + GSI_EP_OP_SET_CLR_BLOCK_DBL); + + usb_gsi_ep_op(gsi->d_port.in_ep, &gsi->d_port.in_request, + GSI_EP_OP_RING_DB); + + if (gsi->d_port.out_ep) + usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request, + GSI_EP_OP_RING_DB); +} + +static void ipa_data_path_disable(struct gsi_data_port *d_port) +{ + struct f_gsi *gsi = d_port_to_gsi(d_port); + bool block_db = true; + + log_event_dbg("%s: Disable eps", __func__); + + if (gsi->d_port.in_ep) + usb_gsi_ep_op(gsi->d_port.in_ep, + &gsi->d_port.in_request, GSI_EP_OP_DISABLE); + + if (gsi->d_port.out_ep) + usb_gsi_ep_op(gsi->d_port.out_ep, + &gsi->d_port.out_request, GSI_EP_OP_DISABLE); + + if (gsi->d_port.in_ep) + usb_gsi_ep_op(d_port->in_ep, (void *)&block_db, + GSI_EP_OP_SET_CLR_BLOCK_DBL); +} + +static void ipa_disconnect_channel(struct gsi_data_port *d_port) +{ + int ret; + struct f_gsi *gsi = d_port_to_gsi(d_port); + + log_event_dbg("%s: Calling xdci_disconnect", __func__); + + ret = ipa_usb_xdci_disconnect(gsi->d_port.out_channel_handle, + gsi->d_port.in_channel_handle, gsi->prot_id); + if (ret) + log_event_err("%s: IPA disconnect failed %d", + __func__, ret); + + log_event_dbg("%s: xdci_disconnect done", __func__); + + /* invalidate channel handles*/ + gsi->d_port.in_channel_handle = -EINVAL; + gsi->d_port.out_channel_handle = -EINVAL; + + if (gsi->d_port.in_ep) { + usb_gsi_ep_op(gsi->d_port.in_ep, &gsi->d_port.in_request, + GSI_EP_OP_FREE_TRBS); + msm_ep_set_mode(gsi->d_port.in_ep, USB_EP_NONE); + } + + if (gsi->d_port.out_ep) { + usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request, + GSI_EP_OP_FREE_TRBS); + msm_ep_set_mode(gsi->d_port.out_ep, USB_EP_NONE); + } +} + +static int ipa_suspend_work_handler(struct gsi_data_port *d_port) +{ + int ret = 0; + bool block_db, f_suspend; + struct f_gsi *gsi = d_port_to_gsi(d_port); + struct usb_function *f = &gsi->function; + + f_suspend = gsi->func_wakeup_allowed; + log_event_dbg("%s: f_suspend:%d", __func__, f_suspend); + + if (!usb_gsi_ep_op(gsi->d_port.in_ep, (void *) &f_suspend, + GSI_EP_OP_CHECK_FOR_SUSPEND)) { + ret = -EFAULT; + block_db = false; + usb_gsi_ep_op(d_port->in_ep, (void *)&block_db, + GSI_EP_OP_SET_CLR_BLOCK_DBL); + goto done; + } + + log_event_dbg("%s: Calling xdci_suspend", __func__); + ret = ipa_usb_xdci_suspend(gsi->d_port.out_channel_handle, + gsi->d_port.in_channel_handle, gsi->prot_id, + usb_gsi_remote_wakeup_allowed(f)); + if (!ret) { + d_port->sm_state = STATE_SUSPENDED; + log_event_dbg("%s: STATE SUSPENDED", __func__); + goto done; + } + + if (ret == -EFAULT) { + block_db = false; + usb_gsi_ep_op(d_port->in_ep, (void *)&block_db, + GSI_EP_OP_SET_CLR_BLOCK_DBL); + gsi_wakeup_host(gsi); + } else if (ret == -EINPROGRESS) { + d_port->sm_state = STATE_SUSPEND_IN_PROGRESS; + } else { + log_event_err("%s: Error %d for %d", __func__, ret, + gsi->prot_id); + } +done: + log_event_dbg("%s: xdci_suspend ret %d", __func__, ret); + return ret; +} + +static void ipa_resume_work_handler(struct gsi_data_port *d_port) +{ + bool block_db; + struct f_gsi *gsi = d_port_to_gsi(d_port); + int ret; + + log_event_dbg("%s: Calling xdci_resume", __func__); + + ret = ipa_usb_xdci_resume(gsi->d_port.out_channel_handle, + gsi->d_port.in_channel_handle, + gsi->prot_id); + if (ret) + log_event_dbg("%s: xdci_resume ret %d", __func__, ret); + + log_event_dbg("%s: xdci_resume done", __func__); + + block_db = false; + usb_gsi_ep_op(d_port->in_ep, (void *)&block_db, + GSI_EP_OP_SET_CLR_BLOCK_DBL); +} + +static int gsi_ep_enable(struct f_gsi *gsi) +{ + int ret; + + if (gsi->d_port.in_ep && !gsi->d_port.in_ep->desc) { + ret = config_ep_by_speed(gsi->d_port.gadget, &gsi->function, + gsi->d_port.in_ep); + if (ret) + return ret; + + log_event_dbg("%s: Enable IN ep", __func__); + ret = usb_gsi_ep_op(gsi->d_port.in_ep, + &gsi->d_port.in_request, GSI_EP_OP_CONFIG); + if (ret) + return ret; + } + + if (gsi->d_port.out_ep && !gsi->d_port.out_ep->desc) { + ret = config_ep_by_speed(gsi->d_port.gadget, &gsi->function, + gsi->d_port.out_ep); + if (ret) + return ret; + + log_event_dbg("%s: Enable OUT ep", __func__); + ret = usb_gsi_ep_op(gsi->d_port.out_ep, + &gsi->d_port.out_request, GSI_EP_OP_CONFIG); + if (ret) { + if (gsi->d_port.in_ep) + usb_gsi_ep_op(gsi->d_port.in_ep, + &gsi->d_port.in_request, + GSI_EP_OP_DISABLE); + return ret; + } + } + + return 0; +} + +static void ipa_work_handler(struct work_struct *w) +{ + struct gsi_data_port *d_port = container_of(w, struct gsi_data_port, + usb_ipa_w.work); + u8 event; + int ret = 0; + struct usb_gadget *gadget = d_port->gadget; + struct device *dev; + struct device *gad_dev; + struct f_gsi *gsi = d_port_to_gsi(d_port); + bool block_db; + + event = read_event(d_port); + + log_event_dbg("%s: event = %x sm_state %x", __func__, + event, d_port->sm_state); + + if (gadget) { + dev = &gadget->dev; + if (!dev || !dev->parent) { + log_event_err("%s(): dev or dev->parent is NULL.\n", + __func__); + return; + } + gad_dev = dev->parent; + } else { + log_event_err("%s(): gadget is NULL.\n", __func__); + return; + } + + switch (d_port->sm_state) { + case STATE_UNINITIALIZED: + break; + case STATE_INITIALIZED: + if (event == EVT_SET_ALT) { + if (!atomic_read(&gsi->connected)) { + log_event_err("USB cable not connected\n"); + break; + } + + usb_gadget_autopm_get(d_port->gadget); + log_event_dbg("%s: get = %d", __func__, + atomic_read(&gad_dev->power.usage_count)); + + /* Configure EPs for GSI */ + ret = gsi_ep_enable(gsi); + if (ret) { + log_event_err("%s:ep enable err %d", __func__, + ret); + usb_composite_setup_continue(gsi->d_port.cdev); + usb_gadget_autopm_put_async(d_port->gadget); + break; + } + + usb_composite_setup_continue(gsi->d_port.cdev); + + /* + * Skip rest for RNDIS and wait for EVT_HOST_READY + * which is invoked when the host sends the + * GEN_CURRENT_PACKET_FILTER message. + */ + if (gsi->prot_id == IPA_USB_RNDIS) { + d_port->sm_state = STATE_HOST_NRDY; + usb_gadget_autopm_put_async(d_port->gadget); + break; + } + + ret = ipa_connect_channels(d_port); + if (ret) { + log_event_err("%s: ipa_connect_channels failed\n", + __func__); + ipa_data_path_disable(d_port); + usb_gadget_autopm_put_async(d_port->gadget); + d_port->sm_state = STATE_INITIALIZED; + break; + } + + d_port->sm_state = STATE_WAIT_FOR_IPA_RDY; + log_event_dbg("%s: ST_INIT_EVT_SET_ALT", + __func__); + } + break; + case STATE_WAIT_FOR_IPA_RDY: + if (event == EVT_IPA_READY) { + if (peek_event(d_port) == EVT_SUSPEND) { + log_event_dbg("%s: ST_WAIT_IPARDY_EVT_SUSPEND", + __func__); + break; + } + ipa_data_path_enable(d_port); + d_port->sm_state = STATE_CONNECTED; + log_event_dbg("%s: ST_WAIT_IPARDY_EVT_IPARDY %d", + __func__, __LINE__); + } else if (event == EVT_SUSPEND) { + if (peek_event(d_port) == EVT_DISCONNECTED) { + read_event(d_port); + ipa_data_path_disable(d_port); + ipa_disconnect_channel(d_port); + d_port->sm_state = STATE_INITIALIZED; + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_WAIT_IPARDY_EVT_SUS_DIS", + __func__); + log_event_dbg("%s: put_async1 = %d", __func__, + atomic_read( + &gad_dev->power.usage_count)); + break; + } + ret = ipa_suspend_work_handler(d_port); + if (!ret) { + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_WAIT_IPARDY_EVT_SUS", + __func__); + log_event_dbg("%s: put_async2 = %d", __func__, + atomic_read( + &gad_dev->power.usage_count)); + } + } else if (event == EVT_DISCONNECTED) { + ipa_data_path_disable(d_port); + ipa_disconnect_channel(d_port); + d_port->sm_state = STATE_INITIALIZED; + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_WAIT_IPARDY_EVT_DIS", + __func__); + log_event_dbg("%s: put_async3 = %d", + __func__, atomic_read( + &gad_dev->power.usage_count)); + } + break; + case STATE_CONNECTED: + if (event == EVT_DISCONNECTED || event == EVT_HOST_NRDY) { + if (peek_event(d_port) == EVT_HOST_READY) { + read_event(d_port); + log_event_dbg("%s: NO_OP NRDY_RDY", __func__); + break; + } + + if (event == EVT_HOST_NRDY) { + log_event_dbg("%s: ST_CON_HOST_NRDY\n", + __func__); + block_db = true; + /* stop USB ringing doorbell to GSI(OUT_EP) */ + usb_gsi_ep_op(d_port->in_ep, (void *)&block_db, + GSI_EP_OP_SET_CLR_BLOCK_DBL); + gsi_rndis_ipa_reset_trigger(d_port); + usb_gsi_ep_op(d_port->in_ep, NULL, + GSI_EP_OP_ENDXFER); + usb_gsi_ep_op(d_port->out_ep, NULL, + GSI_EP_OP_ENDXFER); + /* + * don't disable endpoints for RNDIS flow + * control enable + */ + ipa_disconnect_channel(d_port); + d_port->sm_state = STATE_HOST_NRDY; + log_event_dbg("%s: ST_CON_EVT_HNRDY", __func__); + } else { + ipa_data_path_disable(d_port); + ipa_disconnect_channel(d_port); + d_port->sm_state = STATE_INITIALIZED; + log_event_dbg("%s: ST_CON_EVT_DIS", __func__); + } + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: put_async4 = %d", + __func__, atomic_read( + &gad_dev->power.usage_count)); + } else if (event == EVT_SUSPEND) { + if (peek_event(d_port) == EVT_DISCONNECTED) { + read_event(d_port); + ipa_data_path_disable(d_port); + ipa_disconnect_channel(d_port); + d_port->sm_state = STATE_INITIALIZED; + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_CON_EVT_SUS_DIS", + __func__); + log_event_dbg("%s: put_async5 = %d", + __func__, atomic_read( + &gad_dev->power.usage_count)); + break; + } + ret = ipa_suspend_work_handler(d_port); + if (!ret) { + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_CON_EVT_SUS", + __func__); + log_event_dbg("%s: put_async6 = %d", + __func__, atomic_read( + &gad_dev->power.usage_count)); + } + } + break; + case STATE_HOST_NRDY: + if (event == EVT_DISCONNECTED) { + usb_gadget_autopm_get(d_port->gadget); + ipa_data_path_disable(d_port); + d_port->sm_state = STATE_INITIALIZED; + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_HOST_NRDY_EVT_DIS", __func__); + log_event_dbg("%s: put_async = %d", + __func__, atomic_read( + &gad_dev->power.usage_count)); + } else if (event == EVT_HOST_READY) { + /* + * When in a composition such as RNDIS + ADB, + * RNDIS host sends a GEN_CURRENT_PACKET_FILTER msg + * to enable/disable flow control eg. during RNDIS + * adaptor disable/enable from device manager. + * In the case of the msg to disable flow control, + * connect IPA channels and enable data path. + * EVT_HOST_READY is posted to the state machine + * in the handler for this msg. + */ + usb_gadget_autopm_get(d_port->gadget); + log_event_dbg("%s: get = %d", __func__, + atomic_read(&gad_dev->power.usage_count)); + + ret = ipa_connect_channels(d_port); + if (ret) { + log_event_err("%s: ipa_connect_channels failed\n", + __func__); + usb_gadget_autopm_put_async(d_port->gadget); + break; + } + + ipa_data_path_enable(d_port); + d_port->sm_state = STATE_CONNECTED; + log_event_dbg("%s: ST_HOST_NRDY_EVT_HRDY_", __func__); + } + break; + case STATE_SUSPEND_IN_PROGRESS: + if (event == EVT_IPA_SUSPEND) { + d_port->sm_state = STATE_SUSPENDED; + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_SUS_IN_PROG_EVT_IPA_SUS", + __func__); + log_event_dbg("%s: put_async6 = %d", + __func__, atomic_read( + &gad_dev->power.usage_count)); + } else if (event == EVT_RESUMED) { + ipa_resume_work_handler(d_port); + d_port->sm_state = STATE_CONNECTED; + /* + * Increment usage count here to disallow gadget + * parent suspend. This counter will decrement + * after IPA disconnect is done in disconnect work + * (due to cable disconnect) or in suspended state. + */ + usb_gadget_autopm_get_noresume(d_port->gadget); + log_event_dbg("%s: ST_SUS_IN_PROG_EVT_RES", __func__); + log_event_dbg("%s: get_nores1 = %d", __func__, + atomic_read( + &gad_dev->power.usage_count)); + } else if (event == EVT_DISCONNECTED) { + ipa_data_path_disable(d_port); + ipa_disconnect_channel(d_port); + d_port->sm_state = STATE_INITIALIZED; + usb_gadget_autopm_put_async(d_port->gadget); + log_event_dbg("%s: ST_SUS_IN_PROG_EVT_DIS", __func__); + log_event_dbg("%s: put_async7 = %d", __func__, + atomic_read( + &gad_dev->power.usage_count)); + } + break; + + case STATE_SUSPENDED: + if (event == EVT_RESUMED) { + usb_gadget_autopm_get(d_port->gadget); + log_event_dbg("%s: ST_SUS_EVT_RES", __func__); + log_event_dbg("%s: get = %d", __func__, + atomic_read(&gad_dev->power.usage_count)); + ipa_resume_work_handler(d_port); + d_port->sm_state = STATE_CONNECTED; + } else if (event == EVT_DISCONNECTED) { + usb_gadget_autopm_get(d_port->gadget); + ipa_data_path_disable(d_port); + ipa_disconnect_channel(d_port); + d_port->sm_state = STATE_INITIALIZED; + log_event_dbg("%s: ST_SUS_EVT_DIS", __func__); + usb_gadget_autopm_put_async(d_port->gadget); + } + break; + default: + log_event_dbg("%s: Invalid state to SM", __func__); + } + + if (peek_event(d_port) != EVT_NONE) { + log_event_dbg("%s: New events to process", __func__); + queue_delayed_work(d_port->ipa_usb_wq, &d_port->usb_ipa_w, 0); + } +} + +static struct gsi_ctrl_pkt *gsi_ctrl_pkt_alloc(unsigned int len, gfp_t flags) +{ + struct gsi_ctrl_pkt *pkt; + + pkt = kzalloc(sizeof(struct gsi_ctrl_pkt), flags); + if (!pkt) + return ERR_PTR(-ENOMEM); + + pkt->buf = kmalloc(len, flags); + if (!pkt->buf) { + kfree(pkt); + return ERR_PTR(-ENOMEM); + } + pkt->len = len; + + return pkt; +} + +static void gsi_ctrl_pkt_free(struct gsi_ctrl_pkt *pkt) +{ + if (pkt) { + kfree(pkt->buf); + kfree(pkt); + } +} + +static void gsi_ctrl_clear_cpkt_queues(struct f_gsi *gsi, bool skip_req_q) +{ + struct gsi_ctrl_pkt *cpkt = NULL; + struct list_head *act, *tmp; + unsigned long flags; + + spin_lock_irqsave(&gsi->c_port.lock, flags); + if (skip_req_q) + goto clean_resp_q; + + list_for_each_safe(act, tmp, &gsi->c_port.cpkt_req_q) { + cpkt = list_entry(act, struct gsi_ctrl_pkt, list); + list_del(&cpkt->list); + gsi_ctrl_pkt_free(cpkt); + } +clean_resp_q: + list_for_each_safe(act, tmp, &gsi->c_port.cpkt_resp_q) { + cpkt = list_entry(act, struct gsi_ctrl_pkt, list); + list_del(&cpkt->list); + gsi_ctrl_pkt_free(cpkt); + } + spin_unlock_irqrestore(&gsi->c_port.lock, flags); +} + +static int gsi_ctrl_send_cpkt_tomodem(struct f_gsi *gsi, void *buf, size_t len) +{ + unsigned long flags; + struct gsi_ctrl_port *c_port = &gsi->c_port; + struct gsi_ctrl_pkt *cpkt; + + spin_lock_irqsave(&c_port->lock, flags); + /* drop cpkt if port is not open */ + if (!gsi->c_port.is_open) { + log_event_dbg("%s: ctrl device %s is not open", + __func__, gsi->c_port.name); + c_port->cpkt_drop_cnt++; + spin_unlock_irqrestore(&c_port->lock, flags); + return -ENODEV; + } + + cpkt = gsi_ctrl_pkt_alloc(len, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + log_event_err("%s: Reset func pkt allocation failed", __func__); + spin_unlock_irqrestore(&c_port->lock, flags); + return -ENOMEM; + } + + memcpy(cpkt->buf, buf, len); + cpkt->len = len; + + list_add_tail(&cpkt->list, &c_port->cpkt_req_q); + c_port->host_to_modem++; + spin_unlock_irqrestore(&c_port->lock, flags); + + log_event_dbg("%s: Wake up read queue", __func__); + wake_up(&c_port->read_wq); + + return 0; +} + +static int gsi_ctrl_dev_open(struct inode *ip, struct file *fp) +{ + struct gsi_ctrl_port *c_port = container_of(ip->i_cdev, + struct gsi_ctrl_port, cdev); + struct f_gsi *gsi; + struct gsi_inst_status *inst_cur; + + if (!c_port) { + pr_err_ratelimited("%s: gsi ctrl port %pK\n", __func__, c_port); + return -ENODEV; + } + + gsi = container_of(c_port, struct f_gsi, c_port); + inst_cur = &inst_status[gsi->prot_id]; + log_event_dbg("%s: open ctrl dev %s", __func__, c_port->name); + + mutex_lock(&inst_cur->gsi_lock); + + fp->private_data = &gsi->prot_id; + + if (!inst_cur->inst_exist) { + mutex_unlock(&inst_cur->gsi_lock); + log_event_err("%s: [prot_id = %d], GSI instance freed already\n", + __func__, gsi->prot_id); + return -ENODEV; + } + + if (c_port->is_open) { + mutex_unlock(&inst_cur->gsi_lock); + log_event_dbg("%s: Already opened\n", __func__); + return -EBUSY; + } + + c_port->is_open = true; + + mutex_unlock(&inst_cur->gsi_lock); + + return 0; +} + +static int gsi_ctrl_dev_release(struct inode *ip, struct file *fp) +{ + enum ipa_usb_teth_prot prot_id = + *(enum ipa_usb_teth_prot *)(fp->private_data); + struct gsi_inst_status *inst_cur = &inst_status[prot_id]; + struct f_gsi *gsi; + + mutex_lock(&inst_cur->gsi_lock); + + if (unlikely(!inst_cur->inst_exist)) { + if (inst_cur->opts) { + /* GSI instance clean up */ + gsi_inst_clean(inst_cur->opts); + inst_cur->opts = NULL; + } + mutex_unlock(&inst_cur->gsi_lock); + pr_err_ratelimited("%s: prot_id:%d: delayed free memory\n", + __func__, prot_id); + return -ENODEV; + } + + inst_cur->opts->gsi->c_port.is_open = false; + gsi = inst_cur->opts->gsi; + mutex_unlock(&inst_cur->gsi_lock); + + log_event_dbg("close ctrl dev %s\n", + inst_cur->opts->gsi->c_port.name); + + return 0; +} + +static ssize_t +gsi_ctrl_dev_read(struct file *fp, char __user *buf, size_t count, loff_t *pos) +{ + struct gsi_ctrl_port *c_port; + struct gsi_ctrl_pkt *cpkt = NULL; + enum ipa_usb_teth_prot prot_id = + *(enum ipa_usb_teth_prot *)(fp->private_data); + struct gsi_inst_status *inst_cur = &inst_status[prot_id]; + struct f_gsi *gsi; + unsigned long flags; + int ret = 0; + + pr_debug("%s: Enter %zu\n", __func__, count); + + mutex_lock(&inst_cur->gsi_lock); + if (unlikely(!inst_cur->inst_exist)) { + mutex_unlock(&inst_cur->gsi_lock); + pr_err_ratelimited("%s: free_inst is called and being freed\n", + __func__); + return -ENODEV; + } + mutex_unlock(&inst_cur->gsi_lock); + + gsi = inst_cur->opts->gsi; + c_port = &inst_cur->opts->gsi->c_port; + if (!c_port) { + log_event_err("%s: gsi ctrl port %pK", __func__, c_port); + return -ENODEV; + } + + if (count > GSI_MAX_CTRL_PKT_SIZE) { + log_event_err("Large buff size %zu, should be %d", + count, GSI_MAX_CTRL_PKT_SIZE); + return -EINVAL; + } + + /* block until a new packet is available */ + spin_lock_irqsave(&c_port->lock, flags); + while (list_empty(&c_port->cpkt_req_q)) { + log_event_dbg("Requests list is empty. Wait."); + spin_unlock_irqrestore(&c_port->lock, flags); + ret = wait_event_interruptible(c_port->read_wq, + !list_empty(&c_port->cpkt_req_q)); + if (ret < 0) { + log_event_err("Waiting failed"); + return -ERESTARTSYS; + } + log_event_dbg("Received request packet"); + spin_lock_irqsave(&c_port->lock, flags); + } + + cpkt = list_first_entry(&c_port->cpkt_req_q, struct gsi_ctrl_pkt, + list); + list_del(&cpkt->list); + spin_unlock_irqrestore(&c_port->lock, flags); + + if (cpkt->len > count) { + log_event_err("cpkt size large:%d > buf size:%zu", + cpkt->len, count); + gsi_ctrl_pkt_free(cpkt); + return -ENOMEM; + } + + log_event_dbg("%s: cpkt size:%d", __func__, cpkt->len); + if (debugfs.qti_packet_debug) + print_hex_dump(KERN_DEBUG, "READ:", DUMP_PREFIX_OFFSET, 16, 1, + cpkt->buf, min_t(int, 30, cpkt->len), false); + + ret = copy_to_user(buf, cpkt->buf, cpkt->len); + if (ret) { + log_event_err("copy_to_user failed: err %d", ret); + ret = -EFAULT; + } else { + log_event_dbg("%s: copied %d bytes to user", __func__, + cpkt->len); + ret = cpkt->len; + c_port->copied_to_modem++; + } + + gsi_ctrl_pkt_free(cpkt); + + log_event_dbg("%s: Exit %zu", __func__, count); + + return ret; +} + +static ssize_t gsi_ctrl_dev_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + int ret = 0; + unsigned long flags; + struct gsi_ctrl_pkt *cpkt; + struct gsi_ctrl_port *c_port; + enum ipa_usb_teth_prot prot_id = + *(enum ipa_usb_teth_prot *)(fp->private_data); + struct gsi_inst_status *inst_cur = &inst_status[prot_id]; + struct f_gsi *gsi; + + if (prot_id == IPA_USB_DIAG) + return -EINVAL; + + pr_debug("Enter %zu\n", count); + + mutex_lock(&inst_cur->gsi_lock); + if (unlikely(!inst_cur->inst_exist)) { + mutex_unlock(&inst_cur->gsi_lock); + pr_err_ratelimited("%s: free_inst is called and being freed\n", + __func__); + return -ENODEV; + } + mutex_unlock(&inst_cur->gsi_lock); + + gsi = inst_cur->opts->gsi; + c_port = &gsi->c_port; + + if (!count || count > GSI_MAX_CTRL_PKT_SIZE) { + log_event_err("error: ctrl pkt length %zu", count); + return -EINVAL; + } + + if (!atomic_read(&gsi->connected)) { + log_event_err("USB cable not connected\n"); + return -ECONNRESET; + } + + if (gsi->func_is_suspended && !gsi->func_wakeup_allowed) { + c_port->cpkt_drop_cnt++; + log_event_err("drop ctrl pkt of len %zu", count); + return -EOPNOTSUPP; + } + + cpkt = gsi_ctrl_pkt_alloc(count, GFP_KERNEL); + if (IS_ERR(cpkt)) { + log_event_err("failed to allocate ctrl pkt"); + return -ENOMEM; + } + + ret = copy_from_user(cpkt->buf, buf, count); + if (ret) { + log_event_err("copy_from_user failed err:%d", ret); + gsi_ctrl_pkt_free(cpkt); + return ret; + } + cpkt->type = GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE; + c_port->copied_from_modem++; + if (debugfs.qti_packet_debug) + print_hex_dump(KERN_DEBUG, "WRITE:", DUMP_PREFIX_OFFSET, 16, 1, + cpkt->buf, min_t(int, 30, count), false); + + spin_lock_irqsave(&c_port->lock, flags); + list_add_tail(&cpkt->list, &c_port->cpkt_resp_q); + spin_unlock_irqrestore(&c_port->lock, flags); + + if (!gsi_ctrl_send_notification(gsi)) + c_port->modem_to_host++; + + log_event_dbg("Exit %zu", count); + + return ret ? ret : count; +} + +static long gsi_ctrl_dev_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + struct gsi_ctrl_port *c_port; + struct f_gsi *gsi; + struct gsi_ctrl_pkt *cpkt; + struct ep_info info; + struct data_buf_info data_info = {0}; + enum ipa_usb_teth_prot prot_id = + *(enum ipa_usb_teth_prot *)(fp->private_data); + struct gsi_inst_status *inst_cur = &inst_status[prot_id]; + int val, ret = 0; + unsigned long flags; + + mutex_lock(&inst_cur->gsi_lock); + if (unlikely(!inst_cur->inst_exist)) { + mutex_unlock(&inst_cur->gsi_lock); + pr_err_ratelimited("%s: free_inst is called and being freed\n", + __func__); + return -ENODEV; + } + mutex_unlock(&inst_cur->gsi_lock); + + gsi = inst_cur->opts->gsi; + c_port = &gsi->c_port; + + if (!atomic_read(&gsi->connected)) { + log_event_err("USB cable not connected\n"); + return -ECONNRESET; + } + + switch (cmd) { + case QTI_CTRL_MODEM_OFFLINE: + if (gsi->prot_id == IPA_USB_DIAG) { + log_event_dbg("%s:Modem Offline not handled", __func__); + goto exit_ioctl; + } + atomic_set(&c_port->ctrl_online, 0); + gsi_ctrl_clear_cpkt_queues(gsi, true); + cpkt = gsi_ctrl_pkt_alloc(0, GFP_KERNEL); + if (IS_ERR(cpkt)) { + log_event_err("%s: err allocating cpkt\n", __func__); + return -ENOMEM; + } + cpkt->type = GSI_CTRL_NOTIFY_OFFLINE; + spin_lock_irqsave(&c_port->lock, flags); + list_add_tail(&cpkt->list, &c_port->cpkt_resp_q); + spin_unlock_irqrestore(&c_port->lock, flags); + gsi_ctrl_send_notification(gsi); + break; + case QTI_CTRL_MODEM_ONLINE: + if (gsi->prot_id == IPA_USB_DIAG) { + log_event_dbg("%s:Modem Online not handled", __func__); + goto exit_ioctl; + } + + atomic_set(&c_port->ctrl_online, 1); + break; + case QTI_CTRL_GET_LINE_STATE: + val = atomic_read(&gsi->connected); + if (gsi->prot_id == IPA_USB_RMNET) + val = gsi->rmnet_dtr_status; + + ret = copy_to_user((void __user *)arg, &val, sizeof(val)); + if (ret) { + log_event_err("copy_to_user fail LINE_STATE"); + ret = -EFAULT; + } + log_event_dbg("%s: Sent line_state: %d for prot id:%d", + __func__, val, gsi->prot_id); + break; + case QTI_CTRL_EP_LOOKUP: + case GSI_MBIM_EP_LOOKUP: + log_event_dbg("%s: EP_LOOKUP for prot id:%d", __func__, + gsi->prot_id); + if (!atomic_read(&gsi->connected)) { + log_event_dbg("EP_LOOKUP failed: not connected"); + ret = -EAGAIN; + break; + } + + if (gsi->prot_id == IPA_USB_DIAG && + (gsi->d_port.in_channel_handle == -EINVAL)) { + ret = -EAGAIN; + break; + } + + if (gsi->d_port.in_channel_handle == -EINVAL && + gsi->d_port.out_channel_handle == -EINVAL) { + ret = -EAGAIN; + break; + } + + info.ph_ep_info.ep_type = GSI_MBIM_DATA_EP_TYPE_HSUSB; + info.ph_ep_info.peripheral_iface_id = gsi->data_id; + info.ipa_ep_pair.cons_pipe_num = + (gsi->prot_id == IPA_USB_DIAG) ? -1 : + gsi->d_port.out_channel_handle; + info.ipa_ep_pair.prod_pipe_num = gsi->d_port.in_channel_handle; + + log_event_dbg("%s: prot id :%d ep_type:%d intf:%d", + __func__, gsi->prot_id, info.ph_ep_info.ep_type, + info.ph_ep_info.peripheral_iface_id); + + log_event_dbg("%s: ipa_cons_idx:%d ipa_prod_idx:%d", + __func__, info.ipa_ep_pair.cons_pipe_num, + info.ipa_ep_pair.prod_pipe_num); + + ret = copy_to_user((void __user *)arg, &info, + sizeof(info)); + if (ret) { + log_event_err("copy_to_user fail MBIM"); + ret = -EFAULT; + } + break; + case GSI_MBIM_GET_NTB_SIZE: + ret = copy_to_user((void __user *)arg, + &gsi->d_port.ntb_info.ntb_input_size, + sizeof(gsi->d_port.ntb_info.ntb_input_size)); + if (ret) { + log_event_err("copy_to_user failNTB_SIZE"); + ret = -EFAULT; + } + log_event_dbg("Sent NTB size %d", + gsi->d_port.ntb_info.ntb_input_size); + break; + case GSI_MBIM_GET_DATAGRAM_COUNT: + ret = copy_to_user((void __user *)arg, + &gsi->d_port.ntb_info.ntb_max_datagrams, + sizeof(gsi->d_port.ntb_info.ntb_max_datagrams)); + if (ret) { + log_event_err("copy_to_user fail DATAGRAM"); + ret = -EFAULT; + } + log_event_dbg("Sent NTB datagrams count %d", + gsi->d_port.ntb_info.ntb_max_datagrams); + break; + case QTI_CTRL_DATA_BUF_INFO: + if (gsi->d_port.out_ep) { + data_info.epout_buf_len = + gsi->d_port.out_request.buf_len; + data_info.epout_total_buf_len = + gsi->d_port.out_request.buf_len * + gsi->d_port.out_request.num_bufs; + log_event_dbg("prot id :%d OUT: buf_len:%u total_len: %u", + gsi->prot_id, data_info.epout_buf_len, + data_info.epout_total_buf_len); + } + + if (gsi->d_port.in_ep) { + data_info.epin_buf_len = + gsi->d_port.in_request.buf_len; + data_info.epin_total_buf_len = + gsi->d_port.in_request.buf_len * + gsi->d_port.in_request.num_bufs; + log_event_dbg("prot id :%d IN: buf_len:%u total_len:%u\n", + gsi->prot_id, data_info.epin_buf_len, + data_info.epin_total_buf_len); + } + + ret = copy_to_user((void __user *)arg, &data_info, + sizeof(data_info)); + if (ret) { + log_event_err("QTI_CTRL_DATA_BUF_INFO: copy_to_user failed"); + ret = -EFAULT; + } + break; + default: + log_event_err("wrong parameter"); + ret = -EINVAL; + } + +exit_ioctl: + return ret; +} + +static __poll_t gsi_ctrl_dev_poll(struct file *fp, poll_table *wait) +{ + struct gsi_ctrl_port *c_port; + enum ipa_usb_teth_prot prot_id = + *(enum ipa_usb_teth_prot *)(fp->private_data); + struct gsi_inst_status *inst_cur = &inst_status[prot_id]; + struct f_gsi *gsi; + unsigned long flags; + __poll_t mask = 0; + + mutex_lock(&inst_cur->gsi_lock); + if (unlikely(!inst_cur->inst_exist)) { + mutex_unlock(&inst_cur->gsi_lock); + pr_err_ratelimited("%s: free_inst is called and being freed\n", + __func__); + mask = EPOLLHUP; + goto out; + } + mutex_unlock(&inst_cur->gsi_lock); + + gsi = inst_cur->opts->gsi; + c_port = &inst_cur->opts->gsi->c_port; + if (!c_port) { + log_event_err("%s: gsi ctrl port %pK", __func__, c_port); + mask = EPOLLHUP; + goto out; + } + + poll_wait(fp, &c_port->read_wq, wait); + + spin_lock_irqsave(&c_port->lock, flags); + if (!list_empty(&c_port->cpkt_req_q)) { + mask = EPOLLIN | EPOLLRDNORM; + log_event_dbg("%s sets POLLIN for %s", __func__, c_port->name); + } + spin_unlock_irqrestore(&c_port->lock, flags); + +out: + return mask; +} + +/* file operations for rmnet/mbim/dpl devices */ +static const struct file_operations gsi_ctrl_dev_fops = { + .owner = THIS_MODULE, + .open = gsi_ctrl_dev_open, + .release = gsi_ctrl_dev_release, + .read = gsi_ctrl_dev_read, + .write = gsi_ctrl_dev_write, + .unlocked_ioctl = gsi_ctrl_dev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = gsi_ctrl_dev_ioctl, +#endif + .poll = gsi_ctrl_dev_poll, +}; + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static unsigned int gsi_xfer_bitrate(struct usb_gadget *g) +{ + if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER) + return 13 * 1024 * 8 * 1000 * 8; + else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +static int gsi_function_ctrl_port_init(struct f_gsi *gsi) +{ + int ret; + int sz = GSI_CTRL_NAME_LEN; + int minor; + struct device *dev; + bool ctrl_dev_create = true; + + if (!gsi) { + log_event_err("%s: gsi prot ctx is NULL", __func__); + return -EINVAL; + } + + INIT_LIST_HEAD(&gsi->c_port.cpkt_req_q); + INIT_LIST_HEAD(&gsi->c_port.cpkt_resp_q); + + spin_lock_init(&gsi->c_port.lock); + + init_waitqueue_head(&gsi->c_port.read_wq); + + if (gsi->prot_id == IPA_USB_RMNET) + strlcat(gsi->c_port.name, GSI_RMNET_CTRL_NAME, sz); + else if (gsi->prot_id == IPA_USB_MBIM) + strlcat(gsi->c_port.name, GSI_MBIM_CTRL_NAME, sz); + else if (gsi->prot_id == IPA_USB_DIAG) + strlcat(gsi->c_port.name, GSI_DPL_CTRL_NAME, sz); + else + ctrl_dev_create = false; + + if (!ctrl_dev_create) + return 0; + + minor = ida_simple_get(&gsi_ida, 0, MAX_CDEV_INSTANCES, GFP_KERNEL); + if (minor < 0) { + pr_err("%s: No more minor numbers left! rc:%d\n", __func__, + minor); + return minor; + } + + cdev_init(&gsi->c_port.cdev, &gsi_ctrl_dev_fops); + ret = cdev_add(&gsi->c_port.cdev, MKDEV(major, minor), 1); + if (ret) { + log_event_err("%s: Failed to add cdev for (%s)\n", __func__, + gsi->c_port.name); + goto err_cdev_add; + } + + dev = device_create(gsi_class, NULL, MKDEV(major, minor), NULL, + gsi->c_port.name); + if (IS_ERR(dev)) { + log_event_err("%s: device_create failed for (%s)\n", __func__, + gsi->c_port.name); + ret = PTR_ERR(dev); + goto err_create_dev; + } + + return 0; + +err_create_dev: + cdev_del(&gsi->c_port.cdev); +err_cdev_add: + ida_simple_remove(&gsi_ida, minor); + return ret; +} + +static struct net_device *gsi_rndis_get_netdev(const char *netname) +{ + struct net_device *net_dev; + + net_dev = dev_get_by_name(&init_net, netname); + if (!net_dev) + return ERR_PTR(-EINVAL); + + /* + * Decrement net_dev refcount as it was incremented in + * dev_get_by_name(). + */ + dev_put(net_dev); + return net_dev; +} + +static void gsi_rndis_open(struct f_gsi *gsi) +{ + struct usb_composite_dev *cdev = gsi->function.config->cdev; + + rndis_set_param_medium(gsi->params, RNDIS_MEDIUM_802_3, + gsi_xfer_bitrate(cdev->gadget) / 100); + rndis_signal_connect(gsi->params); +} + +static void gsi_rndis_ipa_reset_trigger(struct gsi_data_port *d_port) +{ + unsigned long flags; + struct f_gsi *gsi = d_port_to_gsi(d_port); + + log_event_dbg("%s: setting net_ready_trigger\n", __func__); + spin_lock_irqsave(&d_port->lock, flags); + d_port->net_ready_trigger = false; + spin_unlock_irqrestore(&d_port->lock, flags); +} + +static void gsi_rndis_flow_ctrl_enable(bool enable, struct rndis_params *param) +{ + struct f_gsi *gsi = param->v; + struct gsi_data_port *d_port; + + if (!gsi) { + pr_err("%s: gsi prot ctx is %pK\n", __func__, gsi); + return; + } + + d_port = &gsi->d_port; + if (enable) { + log_event_dbg("%s: posting HOST_NRDY\n", __func__); + post_event(d_port, EVT_HOST_NRDY); + } else { + log_event_dbg("%s: posting HOST_READY\n", __func__); + post_event(d_port, EVT_HOST_READY); + /* + * If host supports flow control with RNDIS_MSG_INIT then + * mark the flag to true. This flag will be used further to + * enable the flow control on resume path. + */ + gsi->host_supports_flow_control = true; + } + + queue_delayed_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w, 0); +} + +static int queue_notification_request(struct f_gsi *gsi) +{ + int ret; + unsigned long flags; + + if (!gsi->func_is_suspended) { + ret = usb_ep_queue(gsi->c_port.notify, + gsi->c_port.notify_req, GFP_ATOMIC); + } else { + ret = -EOPNOTSUPP; +#if IS_ENABLED(CONFIG_USB_FUNC_WAKEUP_SUPPORTED) + if (gsi->func_wakeup_allowed) + ret = usb_func_wakeup(&gsi->function); +#endif + } + + if (ret < 0 || gsi->func_is_suspended) { + spin_lock_irqsave(&gsi->c_port.lock, flags); + gsi->c_port.notify_req_queued = false; + spin_unlock_irqrestore(&gsi->c_port.lock, flags); + } + + log_event_dbg("%s: ret:%d req_queued:%d", + __func__, ret, gsi->c_port.notify_req_queued); + + return ret; +} + +static int gsi_ctrl_send_notification(struct f_gsi *gsi) +{ + __le32 *data; + struct usb_cdc_notification *event; + struct usb_request *req = gsi->c_port.notify_req; + struct usb_composite_dev *cdev; + struct gsi_ctrl_pkt *cpkt; + unsigned long flags; + bool del_free_cpkt = false; + + if (!atomic_read(&gsi->connected)) { + log_event_dbg("%s: cable disconnect", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&gsi->c_port.lock, flags); + if (list_empty(&gsi->c_port.cpkt_resp_q)) { + spin_unlock_irqrestore(&gsi->c_port.lock, flags); + log_event_dbg("%s: cpkt_resp_q is empty\n", __func__); + return 0; + } + + log_event_dbg("%s: notify_req_queued:%d\n", + __func__, gsi->c_port.notify_req_queued); + + if (gsi->c_port.notify_req_queued) { + spin_unlock_irqrestore(&gsi->c_port.lock, flags); + log_event_dbg("%s: notify_req is already queued.\n", __func__); + return 0; + } + + cpkt = list_first_entry(&gsi->c_port.cpkt_resp_q, + struct gsi_ctrl_pkt, list); + log_event_dbg("%s: cpkt->type:%d\n", __func__, cpkt->type); + + event = req->buf; + cdev = gsi->function.config->cdev; + + switch (cpkt->type) { + case GSI_CTRL_NOTIFY_CONNECT: + del_free_cpkt = true; + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + event->wValue = cpu_to_le16(1); + event->wLength = cpu_to_le16(0); + break; + case GSI_CTRL_NOTIFY_SPEED: + del_free_cpkt = true; + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(8); + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data = req->buf + sizeof(*event); + data[0] = cpu_to_le32(gsi_xfer_bitrate(cdev->gadget)); + data[1] = data[0]; + + log_event_dbg("notify speed %d", + gsi_xfer_bitrate(cdev->gadget)); + break; + case GSI_CTRL_NOTIFY_OFFLINE: + del_free_cpkt = true; + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(0); + break; + case GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE: + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(0); + + if (gsi->prot_id == IPA_USB_RNDIS) { + data = req->buf; + data[0] = cpu_to_le32(1); + data[1] = cpu_to_le32(0); + /* + * we need to free dummy packet for RNDIS as sending + * notification about response available multiple time, + * RNDIS host driver doesn't like. All SEND/GET + * ENCAPSULATED response is one-to-one for RNDIS case + * and host expects to have below sequence: + * ep0: USB_CDC_SEND_ENCAPSULATED_COMMAND + * int_ep: device->host: RESPONSE_AVAILABLE + * ep0: USB_GET_SEND_ENCAPSULATED_COMMAND + * For RMNET case: host ignores multiple notification. + */ + del_free_cpkt = true; + } + break; + default: + spin_unlock_irqrestore(&gsi->c_port.lock, flags); + log_event_err("%s:unknown notify state", __func__); + WARN_ON(1); + return -EINVAL; + } + + /* + * Delete and free cpkt related to non NOTIFY_RESPONSE_AVAILABLE + * notification whereas NOTIFY_RESPONSE_AVAILABLE related cpkt is + * deleted from USB_CDC_GET_ENCAPSULATED_RESPONSE setup request + */ + if (del_free_cpkt) { + list_del(&cpkt->list); + gsi_ctrl_pkt_free(cpkt); + } + + gsi->c_port.notify_req_queued = true; + spin_unlock_irqrestore(&gsi->c_port.lock, flags); + log_event_dbg("send Notify type %02x", event->bNotificationType); + + return queue_notification_request(gsi); +} + +static void gsi_ctrl_notify_resp_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_gsi *gsi = req->context; + struct usb_cdc_notification *event = req->buf; + int status = req->status; + unsigned long flags; + + spin_lock_irqsave(&gsi->c_port.lock, flags); + gsi->c_port.notify_req_queued = false; + spin_unlock_irqrestore(&gsi->c_port.lock, flags); + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + log_event_dbg("ESHUTDOWN/ECONNRESET, connection gone"); + gsi_ctrl_clear_cpkt_queues(gsi, false); + gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0); + break; + default: + log_event_err("Unknown event %02x --> %d", + event->bNotificationType, req->status); + fallthrough; + case 0: + break; + } +} + +static void gsi_rndis_response_available(void *_rndis) +{ + struct f_gsi *gsi = _rndis; + struct gsi_ctrl_pkt *cpkt; + unsigned long flags; + + cpkt = gsi_ctrl_pkt_alloc(0, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + log_event_err("%s: err allocating cpkt\n", __func__); + return; + } + + cpkt->type = GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE; + spin_lock_irqsave(&gsi->c_port.lock, flags); + list_add_tail(&cpkt->list, &gsi->c_port.cpkt_resp_q); + spin_unlock_irqrestore(&gsi->c_port.lock, flags); + gsi_ctrl_send_notification(gsi); +} + +static void gsi_rndis_command_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_gsi *gsi = req->context; + struct usb_composite_dev *cdev = gsi->function.config->cdev; + int status; + u32 MsgType; + + if (!req->buf || !gsi->params) + return; + + MsgType = get_unaligned_le32((__le32 *)req->buf); + + /* intercept halt message to perform flow control */ + if (MsgType == RNDIS_MSG_HALT) { + log_event_dbg("RNDIS_MSG_HALT, state:%d\n", + gsi->params->state); + if (gsi->params->state == RNDIS_DATA_INITIALIZED) + gsi_rndis_flow_ctrl_enable(true, gsi->params); + gsi->params->state = RNDIS_UNINITIALIZED; + return; + } + + status = rndis_msg_parser(gsi->params, (u8 *) req->buf); + if (status < 0) + log_event_err("RNDIS command error %d, %d/%d", + status, req->actual, req->length); + + if (MsgType == RNDIS_MSG_INIT) { + rndis_init_msg_type *buf = (rndis_init_msg_type *)req->buf; + + log_event_dbg("RNDIS host major:%d minor:%d version\n", + le32_to_cpu(buf->MajorVersion), + le32_to_cpu(buf->MinorVersion)); + + /* honor host dl aggr size */ + gsi->d_port.in_aggr_size = le32_to_cpu(buf->MaxTransferSize); + log_event_dbg("RNDIS host DL MaxTransferSize:%d\n", + le32_to_cpu(buf->MaxTransferSize)); + } else if (MsgType == RNDIS_MSG_SET) { + rndis_set_msg_type *buf = (rndis_set_msg_type *)req->buf; + + if (le32_to_cpu(buf->OID) == + RNDIS_OID_GEN_CURRENT_PACKET_FILTER) + gsi_rndis_flow_ctrl_enable(!(*gsi->params->filter), + gsi->params); + } + cdev->setup_pending = false; +} + +static void +gsi_ctrl_set_ntb_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* now for SET_NTB_INPUT_SIZE only */ + unsigned int in_size = 0; + struct f_gsi *gsi = req->context; + struct gsi_ntb_info *ntb = NULL; + + log_event_dbg("dev:%pK", gsi); + + req->context = NULL; + if (req->status || req->actual != req->length) { + log_event_err("Bad control-OUT transfer"); + goto invalid; + } + + if (req->length == 4) { + in_size = get_unaligned_le32(req->buf); + if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || + in_size > le32_to_cpu(mbim_gsi_ntb_parameters.dwNtbInMaxSize)) + goto invalid; + } else if (req->length == 8) { + ntb = (struct gsi_ntb_info *)req->buf; + in_size = get_unaligned_le32(&(ntb->ntb_input_size)); + if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || + in_size > le32_to_cpu(mbim_gsi_ntb_parameters.dwNtbInMaxSize)) + goto invalid; + + gsi->d_port.ntb_info.ntb_max_datagrams = + get_unaligned_le16(&(ntb->ntb_max_datagrams)); + } else { + goto invalid; + } + + log_event_dbg("Set NTB INPUT SIZE %d", in_size); + + gsi->d_port.ntb_info.ntb_input_size = in_size; + return; + +invalid: + log_event_err("Illegal NTB INPUT SIZE %d from host", in_size); + usb_ep_set_halt(ep); +} + +static void gsi_ctrl_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_gsi *gsi = req->context; + struct usb_composite_dev *cdev = gsi->function.config->cdev; + + gsi_ctrl_send_cpkt_tomodem(gsi, req->buf, req->actual); + cdev->setup_pending = false; +} + +static void gsi_ctrl_reset_cmd_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_gsi *gsi = req->context; + + gsi_ctrl_send_cpkt_tomodem(gsi, req->buf, 0); +} + +static void gsi_ctrl_send_response_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_gsi *gsi = req->context; + + gsi_ctrl_send_notification(gsi); +} + +static int +gsi_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_gsi *gsi = func_to_gsi(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int id, 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); + struct gsi_ctrl_pkt *cpkt; + u8 *buf; + u32 n; + bool line_state; + + if (!atomic_read(&gsi->connected)) { + log_event_dbg("usb cable is not connected"); + return -ENOTCONN; + } + + /* rmnet and dpl does not have ctrl_id */ + if (gsi->ctrl_id == -ENODEV) + id = gsi->data_id; + else + id = gsi->ctrl_id; + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_RESET_FUNCTION: + + log_event_dbg("USB_CDC_RESET_FUNCTION"); + value = 0; + req->complete = gsi_ctrl_reset_cmd_complete; + req->context = gsi; + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + log_event_dbg("USB_CDC_SEND_ENCAPSULATED_COMMAND"); + + if (w_value || w_index != id) + goto invalid; + /* read the request; process it later */ + value = w_length; + req->context = gsi; + if (gsi->prot_id == IPA_USB_RNDIS) + req->complete = gsi_rndis_command_complete; + else + req->complete = gsi_ctrl_cmd_complete; + /* later, rndis_response_available() sends a notification */ + break; + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + log_event_dbg("USB_CDC_GET_ENCAPSULATED_RESPONSE"); + if (w_value || w_index != id) + goto invalid; + + if (gsi->prot_id == IPA_USB_RNDIS) { + rndis_init_cmplt_type *res; + + /* return the result */ + buf = rndis_get_next_response(gsi->params, &n); + if (!buf) + break; + + res = (rndis_init_cmplt_type *)buf; + if (le32_to_cpu(res->MessageType) == RNDIS_MSG_INIT_C) { + log_event_dbg("%s: max_pkt_per_xfer : %d", + __func__, DEFAULT_MAX_PKT_PER_XFER); + res->MaxPacketsPerTransfer = + cpu_to_le32(DEFAULT_MAX_PKT_PER_XFER); + + res->MaxTransferSize = cpu_to_le32( + le32_to_cpu(res->MaxTransferSize) + * DEFAULT_MAX_PKT_PER_XFER); + + /* In case of aggregated packets QC device + * will request aliment to 4 (2^2). + */ + log_event_dbg("%s: pkt_alignment_factor : %d", + __func__, DEFAULT_PKT_ALIGNMENT_FACTOR); + res->PacketAlignmentFactor = + cpu_to_le32( + DEFAULT_PKT_ALIGNMENT_FACTOR); + } + + memcpy(req->buf, buf, n); + rndis_free_response(gsi->params, buf); + value = n; + break; + } + + spin_lock(&gsi->c_port.lock); + if (list_empty(&gsi->c_port.cpkt_resp_q)) { + log_event_dbg("ctrl resp queue empty"); + spin_unlock(&gsi->c_port.lock); + break; + } + + cpkt = list_first_entry(&gsi->c_port.cpkt_resp_q, + struct gsi_ctrl_pkt, list); + list_del(&cpkt->list); + gsi->c_port.get_encap_cnt++; + spin_unlock(&gsi->c_port.lock); + + value = min_t(unsigned int, w_length, cpkt->len); + memcpy(req->buf, cpkt->buf, value); + gsi_ctrl_pkt_free(cpkt); + + req->complete = gsi_ctrl_send_response_complete; + req->context = gsi; + log_event_dbg("copied encap_resp %d bytes", + value); + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + line_state = (w_value & GSI_CTRL_DTR ? true : false); + if (gsi->prot_id == IPA_USB_RMNET) + gsi->rmnet_dtr_status = line_state; + log_event_dbg("%s: USB_CDC_REQ_SET_CONTROL_LINE_STATE DTR:%d\n", + __func__, line_state); + gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0); + value = 0; + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_ETHERNET_PACKET_FILTER: + /* see 6.2.30: no data, wIndex = interface, + * wValue = packet filter bitmap + */ + if (w_length != 0 || w_index != id) + goto invalid; + log_event_dbg("packet filter %02x", w_value); + /* REVISIT locking of cdc_filter. This assumes the UDC + * driver won't have a concurrent packet TX irq running on + * another CPU; or that if it does, this write is atomic... + */ + gsi->d_port.cdc_filter = w_value; + value = 0; + break; + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_PARAMETERS: + log_event_dbg("USB_CDC_GET_NTB_PARAMETERS"); + + if (w_length == 0 || w_value != 0 || w_index != id) + break; + + value = w_length > sizeof(mbim_gsi_ntb_parameters) ? + sizeof(mbim_gsi_ntb_parameters) : w_length; + memcpy(req->buf, &mbim_gsi_ntb_parameters, value); + break; + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_INPUT_SIZE: + + log_event_dbg("USB_CDC_GET_NTB_INPUT_SIZE"); + + if (w_length < 4 || w_value != 0 || w_index != id) + break; + + put_unaligned_le32(gsi->d_port.ntb_info.ntb_input_size, + req->buf); + value = 4; + log_event_dbg("Reply to host INPUT SIZE %d", + gsi->d_port.ntb_info.ntb_input_size); + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_INPUT_SIZE: + log_event_dbg("USB_CDC_SET_NTB_INPUT_SIZE"); + + if (w_length != 4 && w_length != 8) { + log_event_err("wrong NTB length %d", w_length); + break; + } + + if (w_value != 0 || w_index != id) + break; + + req->complete = gsi_ctrl_set_ntb_cmd_complete; + req->length = w_length; + req->context = gsi; + + value = req->length; + break; + default: +invalid: + log_event_err("inval ctrl req%02x.%02x v%04x i%04x l%d", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + log_event_dbg("req%02x.%02x v%04x i%04x l%d", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (value < w_length); + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + log_event_err("response on err %d", value); + else + cdev->setup_pending = true; + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +/* + * Because the data interface supports multiple altsettings, + * function *MUST* implement a get_alt() method. + */ +static int gsi_get_alt(struct usb_function *f, unsigned int intf) +{ + struct f_gsi *gsi = func_to_gsi(f); + + /* RNDIS, RMNET and DPL only support alt 0*/ + if (intf == gsi->ctrl_id || gsi->prot_id == IPA_USB_RNDIS || + gsi->prot_id == IPA_USB_RMNET || + gsi->prot_id == IPA_USB_DIAG) + return 0; + else if (intf == gsi->data_id) + return gsi->data_interface_up; + + return -EINVAL; +} + +static int gsi_set_alt(struct usb_function *f, unsigned int intf, + unsigned int alt) +{ + struct f_gsi *gsi = func_to_gsi(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct net_device *net; + int ret = 0; + + log_event_dbg("intf=%u, alt=%u", intf, alt); + + /* Control interface has only altsetting 0 */ + if (intf == gsi->ctrl_id || gsi->prot_id == IPA_USB_RMNET) { + if (alt != 0) + goto fail; + + if (!gsi->c_port.notify) + goto fail; + + if (gsi->c_port.notify->driver_data) { + log_event_dbg("reset gsi control %d", intf); + usb_ep_disable(gsi->c_port.notify); + } + + ret = config_ep_by_speed(cdev->gadget, f, + gsi->c_port.notify); + if (ret) { + gsi->c_port.notify->desc = NULL; + log_event_err("Config-fail notify ep %s: err %d", + gsi->c_port.notify->name, ret); + goto fail; + } + + ret = usb_ep_enable(gsi->c_port.notify); + if (ret) { + log_event_err("usb ep#%s enable failed, err#%d", + gsi->c_port.notify->name, ret); + goto fail; + } + gsi->c_port.notify->driver_data = gsi; + } + + /* Data interface has two altsettings, 0 and 1 */ + if (intf == gsi->data_id) { + gsi->d_port.net_ready_trigger = false; + /* for rndis and rmnet alt is always 0 update alt accordingly */ + if (gsi->prot_id == IPA_USB_RNDIS || + gsi->prot_id == IPA_USB_RMNET || + gsi->prot_id == IPA_USB_DIAG) { + if (gsi->d_port.in_ep && + !gsi->d_port.in_ep->driver_data) + alt = 1; + else + alt = 0; + } + + if (alt > 1) + goto notify_ep_disable; + + if (gsi->data_interface_up == alt) + return 0; + + if (gsi->d_port.in_ep && gsi->d_port.in_ep->driver_data) + gsi->d_port.ntb_info.ntb_input_size = + MBIM_NTB_DEFAULT_IN_SIZE; + if (alt == 1) { + if (gsi->d_port.in_ep) { + if (gsi->prot_id == IPA_USB_DIAG) + gsi->d_port.in_request.ep_intr_num = 3; + else + gsi->d_port.in_request.ep_intr_num = 2; + } + + if (gsi->d_port.out_ep) + gsi->d_port.out_request.ep_intr_num = 1; + + gsi->d_port.gadget = cdev->gadget; + gsi->d_port.cdev = cdev; + + if (gsi->prot_id == IPA_USB_RNDIS) { + gsi_rndis_open(gsi); + net = gsi_rndis_get_netdev("rndis0"); + if (IS_ERR(net)) + goto notify_ep_disable; + + log_event_dbg("RNDIS RX/TX early activation"); + gsi->d_port.cdc_filter = 0; + rndis_set_param_dev(gsi->params, net, + &gsi->d_port.cdc_filter); + } + + if (gsi->prot_id == IPA_USB_ECM) + gsi->d_port.cdc_filter = DEFAULT_FILTER; + + post_event(&gsi->d_port, EVT_SET_ALT); + /* + * delay until delayed status is returned to + * composite layer. + */ + queue_delayed_work(gsi->d_port.ipa_usb_wq, + &gsi->d_port.usb_ipa_w, + msecs_to_jiffies(1)); + ret = USB_GADGET_DELAYED_STATUS; + } + + if (alt == 0 && ((gsi->d_port.in_ep && + !gsi->d_port.in_ep->driver_data) || + (gsi->d_port.out_ep && + !gsi->d_port.out_ep->driver_data))) { + post_event(&gsi->d_port, EVT_DISCONNECTED); + queue_delayed_work(gsi->d_port.ipa_usb_wq, + &gsi->d_port.usb_ipa_w, 0); + log_event_dbg("%s: Disconnecting\n", __func__); + } + + gsi->data_interface_up = alt; + log_event_dbg("DATA_INTERFACE id = %d, status = %d", + gsi->data_id, gsi->data_interface_up); + } + + atomic_set(&gsi->connected, 1); + + /* send 0 len pkt to qti to notify state change */ + if (gsi->prot_id == IPA_USB_DIAG) + gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0); + + return ret; + +notify_ep_disable: + if (gsi->c_port.notify && gsi->c_port.notify->driver_data) + usb_ep_disable(gsi->c_port.notify); +fail: + return -EINVAL; +} + +static void gsi_disable(struct usb_function *f) +{ + struct f_gsi *gsi = func_to_gsi(f); + + atomic_set(&gsi->connected, 0); + + if (gsi->prot_id == IPA_USB_RNDIS) + rndis_uninit(gsi->params); + + if (gsi->prot_id == IPA_USB_RMNET) + gsi->rmnet_dtr_status = false; + + /* Disable Control Path */ + if (gsi->c_port.notify && + gsi->c_port.notify->driver_data) { + usb_ep_disable(gsi->c_port.notify); + gsi->c_port.notify->driver_data = NULL; + } + + gsi_ctrl_clear_cpkt_queues(gsi, false); + /* send 0 len pkt to qti/qbi to notify state change */ + gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0); + gsi->c_port.notify_req_queued = false; + /* Disable Data Path - only if it was initialized already (alt=1) */ + if (!gsi->data_interface_up) { + log_event_dbg("%s: data intf is closed", __func__); + return; + } + + gsi->data_interface_up = false; + + gsi->host_supports_flow_control = false; + + log_event_dbg("%s deactivated", gsi->function.name); + gsi->d_port.net_ready_trigger = false; + post_event(&gsi->d_port, EVT_DISCONNECTED); + queue_delayed_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w, 0); +} + +static void gsi_suspend(struct usb_function *f) +{ + bool block_db; + struct f_gsi *gsi = func_to_gsi(f); + + /* Check if function is already suspended in gsi_func_suspend() */ + if (gsi->func_is_suspended) { + log_event_dbg("%s: func already suspended, return\n", __func__); + return; + } + + block_db = true; + usb_gsi_ep_op(gsi->d_port.in_ep, (void *)&block_db, + GSI_EP_OP_SET_CLR_BLOCK_DBL); + post_event(&gsi->d_port, EVT_SUSPEND); + queue_delayed_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w, 0); + log_event_dbg("gsi suspended"); +} + +static void gsi_resume(struct usb_function *f) +{ + struct f_gsi *gsi = func_to_gsi(f); + struct usb_composite_dev *cdev = f->config->cdev; + + log_event_dbg("%s for prot_id:%d", __func__, gsi->prot_id); + + /* + * If the function is in USB3 Function Suspend state, resume is + * canceled. In this case resume is done by a Function Resume request. + */ + if ((cdev->gadget->speed >= USB_SPEED_SUPER) && + gsi->func_is_suspended) + return; + + if (gsi->c_port.notify && !gsi->c_port.notify->desc) + config_ep_by_speed(cdev->gadget, f, gsi->c_port.notify); + + /* Check any pending cpkt, and queue immediately on resume */ + gsi_ctrl_send_notification(gsi); + + /* + * Linux host does not send RNDIS_MSG_INIT or non-zero + * RNDIS_MESSAGE_PACKET_FILTER after performing bus resume. + * Check whether host supports flow_control are not. If yes + * Trigger state machine explicitly on resume. + */ + if (gsi->prot_id == IPA_USB_RNDIS && + !usb_gsi_remote_wakeup_allowed(f) && + gsi->host_supports_flow_control && gsi->params) { + if (gsi->params->state != RNDIS_DATA_INITIALIZED) + gsi_rndis_flow_ctrl_enable(false, gsi->params); + gsi->params->state = RNDIS_DATA_INITIALIZED; + } + + post_event(&gsi->d_port, EVT_RESUMED); + queue_delayed_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w, 0); + + log_event_dbg("%s: for prot_id:%d completed", __func__, gsi->prot_id); +} + +static int gsi_get_status(struct usb_function *f) +{ +#ifdef CONFIG_USB_FUNC_WAKEUP_SUPPORTED + struct f_gsi *gsi = func_to_gsi(f); + + return (gsi->func_wakeup_allowed ? USB_INTRF_STAT_FUNC_RW : 0) | + USB_INTRF_STAT_FUNC_RW_CAP; +#else + return 0; +#endif +} + +static int gsi_func_suspend(struct usb_function *f, u8 options) +{ + bool func_wakeup_allowed; + struct f_gsi *gsi = func_to_gsi(f); + + log_event_dbg("func susp %u cmd for %s", + options, f->name ? f->name : ""); + + func_wakeup_allowed = !!(options & (USB_INTRF_FUNC_SUSPEND_RW >> 8)); + + if (options & (USB_INTRF_FUNC_SUSPEND_LP >> 8)) { + gsi->func_wakeup_allowed = func_wakeup_allowed; + if (!gsi->func_is_suspended) { + gsi_suspend(f); + gsi->func_is_suspended = true; + } + } else { + if (gsi->func_is_suspended) { + gsi->func_is_suspended = false; + gsi_resume(f); + } + gsi->func_wakeup_allowed = func_wakeup_allowed; + } + + return 0; +} + +static int gsi_update_function_bind_params(struct f_gsi *gsi, + struct usb_composite_dev *cdev, + struct gsi_function_bind_info *info) +{ + struct usb_ep *ep; + struct usb_cdc_notification *event; + struct usb_function *f = &gsi->function; + int status; + + if (info->ctrl_str_idx >= 0 && info->ctrl_desc) { + /* ctrl interface label */ + status = usb_string_id(cdev); + if (status < 0) + return status; + info->string_defs[info->ctrl_str_idx].id = status; + info->ctrl_desc->iInterface = status; + } + + if (info->data_str_idx >= 0 && info->data_desc) { + /* data interface label */ + status = usb_string_id(cdev); + if (status < 0) + return status; + info->string_defs[info->data_str_idx].id = status; + info->data_desc->iInterface = status; + } + + if (info->iad_str_idx >= 0 && info->iad_desc) { + /* IAD iFunction label */ + status = usb_string_id(cdev); + if (status < 0) + return status; + info->string_defs[info->iad_str_idx].id = status; + info->iad_desc->iFunction = status; + } + + if (info->mac_str_idx >= 0 && info->cdc_eth_desc) { + /* IAD iFunction label */ + status = usb_string_id(cdev); + if (status < 0) + return status; + info->string_defs[info->mac_str_idx].id = status; + info->cdc_eth_desc->iMACAddress = status; + } + + if (info->ctrl_desc) + info->ctrl_desc->bInterfaceNumber = gsi->ctrl_id; + + if (info->iad_desc) + info->iad_desc->bFirstInterface = gsi->ctrl_id; + + if (info->union_desc) { + info->union_desc->bMasterInterface0 = gsi->ctrl_id; + info->union_desc->bSlaveInterface0 = gsi->data_id; + } + + if (info->data_desc) + info->data_desc->bInterfaceNumber = gsi->data_id; + + if (info->data_nop_desc) + info->data_nop_desc->bInterfaceNumber = gsi->data_id; + + /* allocate instance-specific endpoints */ + if (info->fs_in_desc) { + ep = usb_ep_autoconfig(cdev->gadget, info->fs_in_desc); + if (!ep) + goto fail; + gsi->d_port.in_ep = ep; + msm_ep_set_mode(gsi->d_port.in_ep, USB_EP_GSI); + ep->driver_data = cdev; /* claim */ + } + + if (info->fs_out_desc) { + ep = usb_ep_autoconfig(cdev->gadget, info->fs_out_desc); + if (!ep) + goto fail; + gsi->d_port.out_ep = ep; + msm_ep_set_mode(gsi->d_port.out_ep, USB_EP_GSI); + ep->driver_data = cdev; /* claim */ + } + + if (info->fs_notify_desc) { + ep = usb_ep_autoconfig(cdev->gadget, info->fs_notify_desc); + if (!ep) + goto fail; + gsi->c_port.notify = ep; + ep->driver_data = cdev; /* claim */ + + /* allocate notification request and buffer */ + gsi->c_port.notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!gsi->c_port.notify_req) + goto fail; + + gsi->c_port.notify_req->buf = + kmalloc(info->notify_buf_len, GFP_KERNEL); + if (!gsi->c_port.notify_req->buf) + goto fail; + + gsi->c_port.notify_req->length = info->notify_buf_len; + gsi->c_port.notify_req->context = gsi; + gsi->c_port.notify_req->complete = + gsi_ctrl_notify_resp_complete; + event = gsi->c_port.notify_req->buf; + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + + if (gsi->ctrl_id == -ENODEV) + event->wIndex = cpu_to_le16(gsi->data_id); + else + event->wIndex = cpu_to_le16(gsi->ctrl_id); + + event->wLength = cpu_to_le16(0); + } + + gsi->d_port.in_request.buf_len = info->in_req_buf_len; + gsi->d_port.in_request.num_bufs = info->in_req_num_buf; + if (gsi->d_port.out_ep) { + gsi->d_port.out_request.buf_len = info->out_req_buf_len; + gsi->d_port.out_request.num_bufs = info->out_req_num_buf; + } + + /* Initialize event queue */ + spin_lock_init(&gsi->d_port.evt_q.q_lock); + gsi->d_port.evt_q.head = gsi->d_port.evt_q.tail = MAXQUEUELEN - 1; + + if (info->fs_in_desc) { + info->hs_in_desc->bEndpointAddress = + info->fs_in_desc->bEndpointAddress; + info->ss_in_desc->bEndpointAddress = + info->fs_in_desc->bEndpointAddress; + } + + if (info->fs_out_desc) { + info->hs_out_desc->bEndpointAddress = + info->fs_out_desc->bEndpointAddress; + info->ss_out_desc->bEndpointAddress = + info->fs_out_desc->bEndpointAddress; + } + + if (info->fs_notify_desc) { + info->hs_notify_desc->bEndpointAddress = + info->fs_notify_desc->bEndpointAddress; + info->ss_notify_desc->bEndpointAddress = + info->fs_notify_desc->bEndpointAddress; + } + + status = usb_assign_descriptors(f, info->fs_desc_hdr, info->hs_desc_hdr, + info->ss_desc_hdr, info->ss_desc_hdr); + if (status) + goto fail; + + return 0; + +fail: + if (gsi->c_port.notify_req) { + kfree(gsi->c_port.notify_req->buf); + usb_ep_free_request(gsi->c_port.notify, gsi->c_port.notify_req); + } + /* we might as well release our claims on endpoints */ + if (gsi->c_port.notify) + gsi->c_port.notify->driver_data = NULL; + if (gsi->d_port.out_ep && gsi->d_port.out_ep->desc) { + msm_ep_set_mode(gsi->d_port.out_ep, USB_EP_NONE); + gsi->d_port.out_ep->driver_data = NULL; + } + if (gsi->d_port.in_ep && gsi->d_port.in_ep->desc) { + msm_ep_set_mode(gsi->d_port.in_ep, USB_EP_NONE); + gsi->d_port.in_ep->driver_data = NULL; + } + log_event_err("%s: bind failed for %s", __func__, f->name); + return -ENOMEM; +} + +static void ipa_ready_callback(void *user_data) +{ + struct f_gsi *gsi = user_data; + + log_event_info("%s: ipa is ready\n", __func__); + + gsi->d_port.ipa_ready = true; + wake_up_interruptible(&gsi->d_port.wait_for_ipa_ready); +} + +static int gsi_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct gsi_function_bind_info info = {0}; + struct f_gsi *gsi = func_to_gsi(f); + struct rndis_params *params; + int status; + __u8 class; + __u8 subclass; + __u8 proto; + + + if (gsi->prot_id == IPA_USB_RMNET || + gsi->prot_id == IPA_USB_DIAG) + gsi->ctrl_id = -ENODEV; + else { + status = gsi->ctrl_id = usb_interface_id(c, f); + if (status < 0) + goto fail; + } + + status = gsi->data_id = usb_interface_id(c, f); + if (status < 0) + goto fail; + + switch (gsi->prot_id) { + case IPA_USB_RNDIS: + info.string_defs = rndis_gsi_string_defs; + info.ctrl_desc = &rndis_gsi_control_intf; + info.ctrl_str_idx = 0; + info.data_desc = &rndis_gsi_data_intf; + info.data_str_idx = 1; + info.iad_desc = &rndis_gsi_iad_descriptor; + info.iad_str_idx = 2; + info.union_desc = &rndis_gsi_union_desc; + info.fs_in_desc = &rndis_gsi_fs_in_desc; + info.fs_out_desc = &rndis_gsi_fs_out_desc; + info.fs_notify_desc = &rndis_gsi_fs_notify_desc; + info.hs_in_desc = &rndis_gsi_hs_in_desc; + info.hs_out_desc = &rndis_gsi_hs_out_desc; + info.hs_notify_desc = &rndis_gsi_hs_notify_desc; + info.ss_in_desc = &rndis_gsi_ss_in_desc; + info.ss_out_desc = &rndis_gsi_ss_out_desc; + info.ss_notify_desc = &rndis_gsi_ss_notify_desc; + info.fs_desc_hdr = gsi_eth_fs_function; + info.hs_desc_hdr = gsi_eth_hs_function; + info.ss_desc_hdr = gsi_eth_ss_function; + info.in_epname = "gsi-epin"; + info.out_epname = "gsi-epout"; + info.in_req_buf_len = GSI_IN_RNDIS_BUFF_SIZE; + gsi->d_port.in_aggr_size = GSI_IN_RNDIS_AGGR_SIZE; + info.in_req_num_buf = GSI_NUM_IN_RNDIS_BUFFERS; + gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE; + info.out_req_buf_len = GSI_OUT_AGGR_SIZE; + info.out_req_num_buf = GSI_NUM_OUT_BUFFERS; + info.notify_buf_len = sizeof(struct usb_cdc_notification); + + params = rndis_register(gsi_rndis_response_available, gsi); + if (IS_ERR(params)) + goto fail; + + gsi->params = params; + + rndis_set_param_medium(gsi->params, RNDIS_MEDIUM_802_3, 0); + + /* export host's Ethernet address in CDC format */ + random_ether_addr(gsi->d_port.ipa_init_params.device_ethaddr); + random_ether_addr(gsi->d_port.ipa_init_params.host_ethaddr); + log_event_dbg("setting host_ethaddr=%pM, device_ethaddr = %pM", + gsi->d_port.ipa_init_params.host_ethaddr, + gsi->d_port.ipa_init_params.device_ethaddr); + memcpy(gsi->ethaddr, &gsi->d_port.ipa_init_params.host_ethaddr, + ETH_ALEN); + rndis_set_host_mac(gsi->params, gsi->ethaddr); + + if (gsi->manufacturer && gsi->vendorID && + rndis_set_param_vendor(gsi->params, gsi->vendorID, + gsi->manufacturer)) + goto dereg_rndis; + + /* Windows7/Windows10 automatically loads RNDIS drivers for + * class drivers which represents MISC_ACTIVE_SYNC, + * MISC_RNDIS_OVER_ETHERNET & WIRELESS_CONTROLLER_REMOTE_NDIS. + * All the codes listed below are from + * http://www.usb.org/developers/defined_class and its unknown + * why windows loads rndis class driver for some of them. + * Note that, Windows loads NDIS6 stack automatically for + * MISC_RNDIS_OVER_ETHERNET. Windows loads NDIS5 stack for + * MISC_ACTIVE_SYNC and WIRELESS_CONTROLLER_REMOTE_NDIS. + * For other class codes, NDIS stack can be selected using + * customized INF file but that defeats the purpose as its + * expected to load drivers automatically for known class + * drivers published by usbif. + * Linux rndis host driver supports MISC_ACTIVE_SYNC and + * WIRELESS_CONTROLLER_REMOTE_NDIS as of now. + * Default to rndis over ethernet which loads NDIS6 drivers + * for windows7/windows10 to avoid data stall issues + */ + if (gsi->rndis_id == RNDIS_ID_UNKNOWN) + gsi->rndis_id = MISC_RNDIS_OVER_ETHERNET; + + switch (gsi->rndis_id) { + default: + /* fall throug */ + case WIRELESS_CONTROLLER_REMOTE_NDIS: + class = USB_CLASS_WIRELESS_CONTROLLER; + subclass = 0x01; + proto = 0x03; + break; + case MISC_ACTIVE_SYNC: + class = USB_CLASS_MISC; + subclass = 0x01; + proto = 0x01; + break; + case MISC_RNDIS_OVER_ETHERNET: + class = USB_CLASS_MISC; + subclass = 0x04; + proto = 0x01; + break; + case MISC_RNDIS_OVER_WIFI: + class = USB_CLASS_MISC; + subclass = 0x04; + proto = 0x02; + break; + case MISC_RNDIS_OVER_WIMAX: + class = USB_CLASS_MISC; + subclass = 0x04; + proto = 0x03; + break; + case MISC_RNDIS_OVER_WWAN: + class = USB_CLASS_MISC; + subclass = 0x04; + proto = 0x04; + break; + case MISC_RNDIS_FOR_IPV4: + class = USB_CLASS_MISC; + subclass = 0x04; + proto = 0x05; + break; + case MISC_RNDIS_FOR_IPV6: + class = USB_CLASS_MISC; + subclass = 0x04; + proto = 0x06; + break; + case MISC_RNDIS_FOR_GPRS: + class = USB_CLASS_MISC; + subclass = 0x04; + proto = 0x07; + break; + } + + info.iad_desc->bFunctionClass = class; + info.iad_desc->bFunctionSubClass = subclass; + info.iad_desc->bFunctionProtocol = proto; + info.ctrl_desc->bInterfaceClass = class; + info.ctrl_desc->bInterfaceSubClass = subclass; + info.ctrl_desc->bInterfaceProtocol = proto; + + break; + case IPA_USB_MBIM: + info.string_defs = mbim_gsi_string_defs; + info.ctrl_desc = &mbim_gsi_control_intf; + info.ctrl_str_idx = 0; + info.data_desc = &mbim_gsi_data_intf; + info.data_str_idx = 1; + info.data_nop_desc = &mbim_gsi_data_nop_intf; + info.iad_desc = &mbim_gsi_iad_desc; + info.iad_str_idx = -1; + info.union_desc = &mbim_gsi_union_desc; + info.fs_in_desc = &mbim_gsi_fs_in_desc; + info.fs_out_desc = &mbim_gsi_fs_out_desc; + info.fs_notify_desc = &mbim_gsi_fs_notify_desc; + info.hs_in_desc = &mbim_gsi_hs_in_desc; + info.hs_out_desc = &mbim_gsi_hs_out_desc; + info.hs_notify_desc = &mbim_gsi_hs_notify_desc; + info.ss_in_desc = &mbim_gsi_ss_in_desc; + info.ss_out_desc = &mbim_gsi_ss_out_desc; + info.ss_notify_desc = &mbim_gsi_ss_notify_desc; + info.fs_desc_hdr = mbim_gsi_fs_function; + info.hs_desc_hdr = mbim_gsi_hs_function; + info.ss_desc_hdr = mbim_gsi_ss_function; + info.in_epname = "gsi-epin"; + info.out_epname = "gsi-epout"; + gsi->d_port.in_aggr_size = GSI_IN_MBIM_AGGR_SIZE; + info.in_req_buf_len = GSI_IN_MBIM_AGGR_SIZE; + info.in_req_num_buf = GSI_NUM_IN_BUFFERS; + gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE; + info.out_req_buf_len = GSI_OUT_MBIM_BUF_LEN; + info.out_req_num_buf = GSI_NUM_OUT_BUFFERS; + info.notify_buf_len = sizeof(struct usb_cdc_notification); + mbim_gsi_desc.wMaxSegmentSize = cpu_to_le16(0x800); + + /* + * If MBIM is bound in a config other than the first, tell + * Windows about it by returning the num as a string in the + * OS descriptor's subCompatibleID field. Windows only supports + * up to config #4. + */ + if (c->bConfigurationValue >= 2 && + c->bConfigurationValue <= 4) { + log_event_dbg("MBIM in configuration %d", + c->bConfigurationValue); + mbim_gsi_ext_config_desc.function.subCompatibleID[0] = + c->bConfigurationValue + '0'; + } + break; + case IPA_USB_RMNET: + info.string_defs = rmnet_gsi_string_defs; + info.data_desc = &rmnet_gsi_interface_desc; + info.data_str_idx = 0; + info.fs_in_desc = &rmnet_gsi_fs_in_desc; + info.fs_out_desc = &rmnet_gsi_fs_out_desc; + info.fs_notify_desc = &rmnet_gsi_fs_notify_desc; + info.hs_in_desc = &rmnet_gsi_hs_in_desc; + info.hs_out_desc = &rmnet_gsi_hs_out_desc; + info.hs_notify_desc = &rmnet_gsi_hs_notify_desc; + info.ss_in_desc = &rmnet_gsi_ss_in_desc; + info.ss_out_desc = &rmnet_gsi_ss_out_desc; + info.ss_notify_desc = &rmnet_gsi_ss_notify_desc; + info.fs_desc_hdr = rmnet_gsi_fs_function; + info.hs_desc_hdr = rmnet_gsi_hs_function; + info.ss_desc_hdr = rmnet_gsi_ss_function; + info.in_epname = "gsi-epin"; + info.out_epname = "gsi-epout"; + gsi->d_port.in_aggr_size = GSI_IN_RMNET_AGGR_SIZE; + info.in_req_buf_len = GSI_IN_RMNET_BUFF_SIZE; + info.in_req_num_buf = GSI_NUM_IN_RMNET_BUFFERS; + gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE; + info.out_req_buf_len = GSI_OUT_RMNET_BUF_LEN; + info.out_req_num_buf = GSI_NUM_OUT_BUFFERS; + info.notify_buf_len = sizeof(struct usb_cdc_notification); + break; + case IPA_USB_ECM: + info.string_defs = ecm_gsi_string_defs; + info.ctrl_desc = &ecm_gsi_control_intf; + info.ctrl_str_idx = 0; + info.data_desc = &ecm_gsi_data_intf; + info.data_str_idx = 2; + info.data_nop_desc = &ecm_gsi_data_nop_intf; + info.cdc_eth_desc = &ecm_gsi_desc; + info.mac_str_idx = 1; + info.union_desc = &ecm_gsi_union_desc; + info.fs_in_desc = &ecm_gsi_fs_in_desc; + info.fs_out_desc = &ecm_gsi_fs_out_desc; + info.fs_notify_desc = &ecm_gsi_fs_notify_desc; + info.hs_in_desc = &ecm_gsi_hs_in_desc; + info.hs_out_desc = &ecm_gsi_hs_out_desc; + info.hs_notify_desc = &ecm_gsi_hs_notify_desc; + info.ss_in_desc = &ecm_gsi_ss_in_desc; + info.ss_out_desc = &ecm_gsi_ss_out_desc; + info.ss_notify_desc = &ecm_gsi_ss_notify_desc; + info.fs_desc_hdr = ecm_gsi_fs_function; + info.hs_desc_hdr = ecm_gsi_hs_function; + info.ss_desc_hdr = ecm_gsi_ss_function; + info.in_epname = "gsi-epin"; + info.out_epname = "gsi-epout"; + gsi->d_port.in_aggr_size = GSI_ECM_AGGR_SIZE; + info.in_req_buf_len = GSI_IN_BUFF_SIZE; + info.in_req_num_buf = GSI_NUM_IN_BUFFERS; + gsi->d_port.out_aggr_size = GSI_ECM_AGGR_SIZE; + info.out_req_buf_len = GSI_OUT_ECM_BUF_LEN; + info.out_req_num_buf = GSI_NUM_OUT_BUFFERS; + info.notify_buf_len = GSI_CTRL_NOTIFY_BUFF_LEN; + + /* export host's Ethernet address in CDC format */ + random_ether_addr(gsi->d_port.ipa_init_params.device_ethaddr); + random_ether_addr(gsi->d_port.ipa_init_params.host_ethaddr); + log_event_dbg("setting host_ethaddr=%pM, device_ethaddr = %pM", + gsi->d_port.ipa_init_params.host_ethaddr, + gsi->d_port.ipa_init_params.device_ethaddr); + + scnprintf(gsi->ethaddr, sizeof(gsi->ethaddr), + "%02X%02X%02X%02X%02X%02X", + gsi->d_port.ipa_init_params.host_ethaddr[0], + gsi->d_port.ipa_init_params.host_ethaddr[1], + gsi->d_port.ipa_init_params.host_ethaddr[2], + gsi->d_port.ipa_init_params.host_ethaddr[3], + gsi->d_port.ipa_init_params.host_ethaddr[4], + gsi->d_port.ipa_init_params.host_ethaddr[5]); + info.string_defs[1].s = gsi->ethaddr; + break; + case IPA_USB_DIAG: + info.string_defs = qdss_gsi_string_defs; + info.data_desc = &qdss_gsi_data_intf_desc; + info.data_str_idx = 0; + info.fs_in_desc = &qdss_gsi_fs_data_desc; + info.hs_in_desc = &qdss_gsi_hs_data_desc; + info.ss_in_desc = &qdss_gsi_ss_data_desc; + info.fs_desc_hdr = qdss_gsi_fs_data_only_desc; + info.hs_desc_hdr = qdss_gsi_hs_data_only_desc; + info.ss_desc_hdr = qdss_gsi_ss_data_only_desc; + info.in_epname = "gsi-epin"; + info.out_epname = ""; + info.in_req_buf_len = 16384; + info.in_req_num_buf = GSI_NUM_IN_BUFFERS; + info.notify_buf_len = sizeof(struct usb_cdc_notification); + break; + default: + log_event_err("%s: Invalid prot id %d", __func__, + gsi->prot_id); + return -EINVAL; + } + + status = gsi_update_function_bind_params(gsi, cdev, &info); + if (status) + goto dereg_rndis; + + status = ipa_register_ipa_ready_cb(ipa_ready_callback, gsi); + if (!status) { + log_event_info("%s: ipa is not ready", __func__); + status = wait_event_interruptible_timeout( + gsi->d_port.wait_for_ipa_ready, gsi->d_port.ipa_ready, + msecs_to_jiffies(GSI_IPA_READY_TIMEOUT)); + if (!status) { + log_event_err("%s: ipa ready timeout", __func__); + status = -ETIMEDOUT; + goto dereg_rndis; + } + } + + gsi->d_port.ipa_usb_notify_cb = ipa_usb_notify_cb; + status = ipa_usb_init_teth_prot(gsi->prot_id, + &gsi->d_port.ipa_init_params, gsi->d_port.ipa_usb_notify_cb, + gsi); + if (status) { + log_event_err("%s: failed to init teth prot(%d) with err:%d", + __func__, gsi->prot_id, status); + goto dereg_rndis; + } + + gsi->d_port.sm_state = STATE_INITIALIZED; + + DBG(cdev, "%s: %s speed IN/%s OUT/%s NOTIFY/%s\n", + f->name, + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + (gsi->d_port.in_ep == NULL ? "NULL" : + gsi->d_port.in_ep->name), + (gsi->d_port.out_ep == NULL ? "NULL" : + gsi->d_port.out_ep->name), + (gsi->c_port.notify == NULL ? "NULL" : + gsi->c_port.notify->name)); + return 0; + +dereg_rndis: + rndis_deregister(gsi->params); +fail: + return status; +} + +static void gsi_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_gsi *gsi = func_to_gsi(f); + + log_event_dbg("%s:id:%d: dwq start", __func__, gsi->prot_id); + + if (atomic_read(&gsi->connected)) + gsi_disable(f); + + /* + * Use drain_workqueue to accomplish below conditions: + * 1. Make sure that any running work completed + * 2. Make sure to wait until all pending work completed i.e. workqueue + * is not having any pending work. + * Above conditions are making sure that ipa_usb_deinit_teth_prot() + * with ipa driver shall not fail due to unexpected state. + */ + drain_workqueue(gsi->d_port.ipa_usb_wq); + log_event_dbg("%s:id:%d: dwq end", __func__, gsi->prot_id); + + ipa_usb_deinit_teth_prot(gsi->prot_id); + + /* Reset string ids */ + rndis_gsi_string_defs[0].id = 0; + ecm_gsi_string_defs[0].id = 0; + rmnet_gsi_string_defs[0].id = 0; + mbim_gsi_string_defs[0].id = 0; + qdss_gsi_string_defs[0].id = 0; + + if (gsi->prot_id == IPA_USB_RNDIS) { + gsi->d_port.sm_state = STATE_UNINITIALIZED; + rndis_deregister(gsi->params); + } + + if (gsi->prot_id == IPA_USB_MBIM) + mbim_gsi_ext_config_desc.function.subCompatibleID[0] = 0; + + usb_free_all_descriptors(f); + + if (gsi->c_port.notify) { + kfree(gsi->c_port.notify_req->buf); + usb_ep_free_request(gsi->c_port.notify, gsi->c_port.notify_req); + } +} + + +static void gsi_free_func(struct usb_function *f) +{ } + +static int gsi_bind_config(struct f_gsi *gsi) +{ + int status = 0; + enum ipa_usb_teth_prot prot_id = gsi->prot_id; + + log_event_dbg("%s: prot id %d", __func__, prot_id); + + switch (prot_id) { + case IPA_USB_RNDIS: + gsi->function.name = "rndis"; + gsi->function.strings = rndis_gsi_strings; + break; + case IPA_USB_ECM: + gsi->function.name = "cdc_ethernet"; + gsi->function.strings = ecm_gsi_strings; + break; + case IPA_USB_RMNET: + gsi->function.name = "rmnet"; + gsi->function.strings = rmnet_gsi_strings; + break; + case IPA_USB_MBIM: + gsi->function.name = "mbim"; + gsi->function.strings = mbim_gsi_strings; + break; + case IPA_USB_DIAG: + gsi->function.name = "dpl"; + gsi->function.strings = qdss_gsi_strings; + break; + default: + log_event_err("%s: invalid prot id %d", __func__, prot_id); + return -EINVAL; + } + + /* descriptors are per-instance copies */ + gsi->function.bind = gsi_bind; + gsi->function.unbind = gsi_unbind; + gsi->function.set_alt = gsi_set_alt; + gsi->function.get_alt = gsi_get_alt; + gsi->function.setup = gsi_setup; + gsi->function.disable = gsi_disable; + gsi->function.free_func = gsi_free_func; + gsi->function.suspend = gsi_suspend; + gsi->function.get_status = gsi_get_status; + gsi->function.func_suspend = gsi_func_suspend; + gsi->function.resume = gsi_resume; + + return status; +} + +static struct f_gsi *gsi_function_init(enum ipa_usb_teth_prot prot_id) +{ + struct f_gsi *gsi; + int ret = 0; + + if (prot_id >= IPA_USB_MAX_TETH_PROT_SIZE) { + pr_err("%s: invalid prot id %d\n", __func__, prot_id); + ret = -EINVAL; + goto error; + } + + gsi = kzalloc(sizeof(*gsi), GFP_KERNEL); + if (!gsi) { + ret = -ENOMEM; + goto error; + } + + spin_lock_init(&gsi->d_port.lock); + + init_waitqueue_head(&gsi->d_port.wait_for_ipa_ready); + + INIT_DELAYED_WORK(&gsi->d_port.usb_ipa_w, ipa_work_handler); + + gsi->d_port.in_channel_handle = -EINVAL; + gsi->d_port.out_channel_handle = -EINVAL; + + gsi->prot_id = prot_id; + + gsi->d_port.ipa_usb_wq = ipa_usb_wq; + + ret = gsi_function_ctrl_port_init(gsi); + if (ret) { + kfree(gsi); + goto error; + } + gsi->gsi_rw_timer_interval = DEFAULT_RW_TIMER_INTERVAL; + timer_setup(&gsi->gsi_rw_timer, gsi_rw_timer_func, 0); + + return gsi; +error: + return ERR_PTR(ret); +} + +static void gsi_opts_release(struct config_item *item) +{ + struct gsi_opts *opts = to_gsi_opts(item); + struct f_gsi *gsi; + + gsi = opts->gsi; + log_event_dbg("%s: releasing %s instance\n", + __func__, gsi->function.name); + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations gsi_item_ops = { + .release = gsi_opts_release, +}; + +static ssize_t gsi_info_show(struct config_item *item, char *page) +{ + struct ipa_usb_xdci_chan_params *ipa_chnl_params; + struct ipa_usb_xdci_connect_params *con_pms; + struct f_gsi *gsi = to_gsi_opts(item)->gsi; + int ret, j = 0; + unsigned int len = 0; + char *buf; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (gsi && atomic_read(&gsi->connected)) { + len += scnprintf(buf + len, PAGE_SIZE - len, + "Info: Prot_id:%d\n", gsi->prot_id); + ipa_chnl_params = &gsi->d_port.ipa_in_channel_params; + con_pms = &gsi->d_port.ipa_conn_pms; + len += scnprintf(buf + len, PAGE_SIZE - len, "%55s\n", + "=================================================="); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10s\n", "Ctrl Name: ", gsi->c_port.name); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Ctrl Online: ", + gsi->c_port.ctrl_online.counter); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Ctrl Open: ", + gsi->c_port.is_open); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Ctrl Host to Modem: ", + gsi->c_port.host_to_modem); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Ctrl Modem to Host: ", + gsi->c_port.modem_to_host); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Ctrl Cpd to Modem: ", + gsi->c_port.copied_to_modem); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Ctrl Cpd From Modem: ", + gsi->c_port.copied_from_modem); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Ctrl Pkt Drops: ", + gsi->c_port.cpkt_drop_cnt); + len += scnprintf(buf + len, PAGE_SIZE - len, "%25s\n", + "=============="); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Protocol ID: ", gsi->prot_id); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "SM State: ", gsi->d_port.sm_state); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "IN XferRscIndex: ", + gsi->d_port.in_xfer_rsc_index); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10d\n", "IN Chnl Hdl: ", + gsi->d_port.in_channel_handle); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "IN Chnl Dbl Addr: ", + gsi->d_port.in_request.db_reg_phs_addr_lsb); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "IN TRB Ring Len: ", + ipa_chnl_params->xfer_ring_len); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "IN TRB Base Addr: ", (unsigned int) + ipa_chnl_params->xfer_ring_base_addr_iova); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "GEVENTCNTLO IN Addr: ", + ipa_chnl_params->gevntcount_low_addr); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "DEPCMDLO IN Addr: ", + ipa_chnl_params->xfer_scratch.depcmd_low_addr); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "IN LastTRB Addr Off: ", + ipa_chnl_params->xfer_scratch.last_trb_addr_iova); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "IN Buffer Size: ", + ipa_chnl_params->xfer_scratch.const_buffer_size); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "IN/DL Aggr Size: ", + con_pms->teth_prot_params.max_xfer_size_bytes_to_host); + + ipa_chnl_params = &gsi->d_port.ipa_out_channel_params; + len += scnprintf(buf + len, PAGE_SIZE - len, "%25s\n", + "=============="); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "OUT XferRscIndex: ", + gsi->d_port.out_xfer_rsc_index); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10d\n", "OUT Channel Hdl: ", + gsi->d_port.out_channel_handle); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "OUT Channel Dbl Addr: ", + gsi->d_port.out_request.db_reg_phs_addr_lsb); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "OUT TRB Ring Len: ", + ipa_chnl_params->xfer_ring_len); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "OUT TRB Base Addr: ", (unsigned int) + ipa_chnl_params->xfer_ring_base_addr_iova); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "GEVENTCNTLO OUT Addr: ", + ipa_chnl_params->gevntcount_low_addr); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "DEPCMDLO OUT Addr: ", + ipa_chnl_params->xfer_scratch.depcmd_low_addr); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10x\n", "OUT LastTRB Addr Off: ", + ipa_chnl_params->xfer_scratch.last_trb_addr_iova); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "OUT Buffer Size: ", + ipa_chnl_params->xfer_scratch.const_buffer_size); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "OUT/UL Aggr Size: ", + con_pms->teth_prot_params.max_xfer_size_bytes_to_dev); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "OUT/UL Packets to dev: ", + con_pms->teth_prot_params.max_packet_number_to_dev); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Net_ready_trigger:", + gsi->d_port.net_ready_trigger); + len += scnprintf(buf + len, PAGE_SIZE - len, "%25s\n", + "USB Bus Events"); + for (j = 0; j < MAXQUEUELEN; j++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d\t", gsi->d_port.evt_q.event[j]); + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Eventq head: ", + gsi->d_port.evt_q.head); + len += scnprintf(buf + len, PAGE_SIZE - len, + "%25s %10u\n", "Eventq tail: ", + gsi->d_port.evt_q.tail); + } + + if (len > PAGE_SIZE) + len = PAGE_SIZE; + + ret = scnprintf(page, len, buf); + + kfree(buf); + + return ret; +} + +CONFIGFS_ATTR_RO(gsi_, info); + +static struct configfs_attribute *gsi_attrs[] = { + &gsi_attr_info, + NULL, +}; + +static struct config_item_type gsi_func_type = { + .ct_item_ops = &gsi_item_ops, + .ct_attrs = gsi_attrs, + .ct_owner = THIS_MODULE, +}; + +static ssize_t gsi_rndis_class_id_show(struct config_item *item, char *page) +{ + struct f_gsi *gsi = to_gsi_opts(item)->gsi; + + return scnprintf(page, PAGE_SIZE, "%d\n", gsi->rndis_id); +} + +static ssize_t gsi_rndis_class_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_gsi *gsi = to_gsi_opts(item)->gsi; + u8 id; + + if (kstrtou8(page, 0, &id)) + return -EINVAL; + + if (id > RNDIS_ID_UNKNOWN && id < RNDIS_ID_MAX) + gsi->rndis_id = id; + else + return -EINVAL; + + return len; +} +CONFIGFS_ATTR(gsi_, rndis_class_id); + +static struct configfs_attribute *gsi_rndis_attrs[] = { + &gsi_attr_info, + &gsi_attr_rndis_class_id, + NULL, +}; + +static struct config_item_type gsi_func_rndis_type = { + .ct_item_ops = &gsi_item_ops, + .ct_attrs = gsi_rndis_attrs, + .ct_owner = THIS_MODULE, +}; + +static void gsi_inst_clean(struct gsi_opts *opts) +{ + if (opts->gsi->c_port.cdev.dev) { + struct cdev *cdev = &opts->gsi->c_port.cdev; + int minor = MINOR(cdev->dev); + + device_destroy(gsi_class, cdev->dev); + cdev_del(cdev); + cdev->dev = 0; + ida_simple_remove(&gsi_ida, minor); + } + + kfree(opts->gsi); + kfree(opts); +} + +static int gsi_set_inst_name(struct usb_function_instance *fi, + const char *name) +{ + int prot_id, name_len; + struct f_gsi *gsi; + char gsi_inst_name[MAX_INST_NAME_LEN + sizeof("gsi.") + 1]; + void *ipc_log_ctxt; + struct gsi_opts *opts, *opts_prev; + + opts = container_of(fi, struct gsi_opts, func_inst); + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + prot_id = name_to_prot_id(name); + if (prot_id < 0) { + pr_err("%s: failed to find prot id for %s instance\n", + __func__, name); + return -EINVAL; + } + + mutex_lock(&inst_status[prot_id].gsi_lock); + opts_prev = inst_status[prot_id].opts; + if (opts_prev) { + mutex_unlock(&inst_status[prot_id].gsi_lock); + pr_err("%s: prot_id = %d, prev inst do not freed yet\n", + __func__, prot_id); + return -EBUSY; + } + mutex_unlock(&inst_status[prot_id].gsi_lock); + + if (prot_id == IPA_USB_RNDIS) + config_group_init_type_name(&opts->func_inst.group, + fi->group.cg_item.ci_name, + &gsi_func_rndis_type); + + gsi = gsi_function_init(prot_id); + if (IS_ERR(gsi)) + return PTR_ERR(gsi); + + opts->gsi = gsi; + /* + * create instance name with prefixing "gsi." to differentiate + * ipc log debugfs entry + */ + scnprintf(gsi_inst_name, sizeof(gsi_inst_name), "gsi.%s", name); + ipc_log_ctxt = ipc_log_context_create(NUM_LOG_PAGES, gsi_inst_name, 0); + if (!ipc_log_ctxt) + pr_err("%s: Err allocating ipc_log_ctxt for prot:%s\n", + __func__, gsi_inst_name); + opts->gsi->ipc_log_ctxt = ipc_log_ctxt; + + /* Set instance status */ + mutex_lock(&inst_status[prot_id].gsi_lock); + inst_status[prot_id].inst_exist = true; + inst_status[prot_id].opts = opts; + mutex_unlock(&inst_status[prot_id].gsi_lock); + + return 0; +} + +static void gsi_free_inst(struct usb_function_instance *f) +{ + struct gsi_opts *opts = container_of(f, struct gsi_opts, func_inst); + enum ipa_usb_teth_prot prot_id; + struct f_gsi *gsi; + + if (!opts->gsi) + return; + + prot_id = opts->gsi->prot_id; + gsi = opts->gsi; + mutex_lock(&inst_status[prot_id].gsi_lock); + if (opts->gsi->c_port.is_open) { + /* Mark instance exist as false */ + inst_status[prot_id].inst_exist = false; + mutex_unlock(&inst_status[prot_id].gsi_lock); + log_event_err( + "%s: [prot_id = %d] Dev is open, free mem when dev close\n", + __func__, prot_id); + return; + } + + ipc_log_context_destroy(opts->gsi->ipc_log_ctxt); + /* Clear instance status */ + gsi_inst_clean(opts); + inst_status[prot_id].inst_exist = false; + inst_status[prot_id].opts = NULL; + mutex_unlock(&inst_status[prot_id].gsi_lock); +} + +static struct usb_function_instance *gsi_alloc_inst(void) +{ + struct gsi_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.set_inst_name = gsi_set_inst_name; + opts->func_inst.free_func_inst = gsi_free_inst; + config_group_init_type_name(&opts->func_inst.group, "", + &gsi_func_type); + + return &opts->func_inst; +} + +static struct usb_function *gsi_alloc(struct usb_function_instance *fi) +{ + struct gsi_opts *opts; + int ret; + + opts = container_of(fi, struct gsi_opts, func_inst); + + ret = gsi_bind_config(opts->gsi); + if (ret) + return ERR_PTR(ret); + + return &opts->gsi->function; +} + +DECLARE_USB_FUNCTION(gsi, gsi_alloc_inst, gsi_alloc); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GSI function driver"); + +static int fgsi_init(void) +{ + int i; + int ret; + dev_t dev; + + ipa_usb_wq = alloc_workqueue("k_ipa_usb", + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_FREEZABLE, 1); + if (!ipa_usb_wq) { + pr_err("%s(): Failed to create workqueue\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < IPA_USB_MAX_TETH_PROT_SIZE; i++) + mutex_init(&inst_status[i].gsi_lock); + + gsi_class = class_create(THIS_MODULE, "gsi_usb"); + if (IS_ERR(gsi_class)) { + ret = PTR_ERR(gsi_class); + gsi_class = NULL; + pr_err("%s: class_create() failed:%d\n", __func__, ret); + return ret; + } + + ret = alloc_chrdev_region(&dev, 0, MAX_CDEV_INSTANCES, "gsi_usb"); + if (ret) { + pr_err("%s: alloc_chrdev_region() failed:%d\n", __func__, ret); + class_destroy(gsi_class); + gsi_class = NULL; + return ret; + } + + major = MAJOR(dev); + + usb_gsi_debugfs_init(); + return usb_function_register(&gsiusb_func); +} +module_init(fgsi_init); + +static void __exit fgsi_exit(void) +{ + if (ipa_usb_wq) + destroy_workqueue(ipa_usb_wq); + usb_function_unregister(&gsiusb_func); + + if (major) { + unregister_chrdev_region(MKDEV(major, 0), MAX_CDEV_INSTANCES); + major = 0; + } + + class_destroy(gsi_class); + usb_gsi_debugfs_exit(); +} +module_exit(fgsi_exit); diff --git a/drivers/usb/gadget/function/f_gsi.h b/drivers/usb/gadget/function/f_gsi.h new file mode 100644 index 000000000000..c14a9f8c46be --- /dev/null +++ b/drivers/usb/gadget/function/f_gsi.h @@ -0,0 +1,1433 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _F_GSI_H +#define _F_GSI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_CDC_RESET_FUNCTION 0x05 + +#define GSI_RMNET_CTRL_NAME "rmnet_ctrl" +#define GSI_MBIM_CTRL_NAME "android_mbim" +#define GSI_DPL_CTRL_NAME "dpl_ctrl" +#define GSI_CTRL_NAME_LEN (sizeof(GSI_MBIM_CTRL_NAME)+2) +#define GSI_MAX_CTRL_PKT_SIZE 8192 +#define GSI_CTRL_DTR (1 << 0) + +#define GSI_NUM_IN_RNDIS_BUFFERS 50 +#define GSI_NUM_IN_RMNET_BUFFERS 50 +#define GSI_NUM_IN_BUFFERS 15 +#define GSI_IN_BUFF_SIZE 2048 +#define GSI_IN_RMNET_BUFF_SIZE 31744 +#define GSI_IN_RNDIS_BUFF_SIZE 16384 +#define GSI_NUM_OUT_BUFFERS 14 +#define GSI_OUT_AGGR_SIZE 24576 + +#define GSI_IN_RNDIS_AGGR_SIZE 16384 +#define GSI_IN_MBIM_AGGR_SIZE 16384 +#define GSI_IN_RMNET_AGGR_SIZE 16384 +#define GSI_ECM_AGGR_SIZE 2048 + +#define GSI_OUT_MBIM_BUF_LEN 16384 +#define GSI_OUT_RMNET_BUF_LEN 31744 +#define GSI_OUT_ECM_BUF_LEN 2048 + +#define GSI_IPA_READY_TIMEOUT 5000 + +#define ETH_ADDR_STR_LEN 14 + +/* mbin and ecm */ +#define GSI_CTRL_NOTIFY_BUFF_LEN 16 + +/* default max packets per tarnsfer value */ +#define DEFAULT_MAX_PKT_PER_XFER 15 + +/* default pkt alignment factor */ +#define DEFAULT_PKT_ALIGNMENT_FACTOR 4 + +#define GSI_MBIM_DATA_EP_TYPE_HSUSB 0x2 +/* ID for Microsoft OS String */ +#define GSI_MBIM_OS_STRING_ID 0xEE + +#define EVT_NONE 0 +#define EVT_UNINITIALIZED 1 +#define EVT_INITIALIZED 2 +#define EVT_SET_ALT 3 +#define EVT_IPA_READY 4 +#define EVT_HOST_NRDY 5 +#define EVT_HOST_READY 6 +#define EVT_DISCONNECTED 7 +#define EVT_SUSPEND 8 +#define EVT_IPA_SUSPEND 9 +#define EVT_RESUMED 10 + +#define NUM_LOG_PAGES 10 +#define log_event_err(x, ...) do { \ + if (gsi) { \ + ipc_log_string(gsi->ipc_log_ctxt, x, ##__VA_ARGS__); \ + pr_err(x, ##__VA_ARGS__); \ + } \ +} while (0) + +#define log_event_dbg(x, ...) do { \ + if (gsi) { \ + ipc_log_string(gsi->ipc_log_ctxt, x, ##__VA_ARGS__); \ + pr_debug(x, ##__VA_ARGS__); \ + } \ +} while (0) + +#define log_event_info(x, ...) do { \ + if (gsi) { \ + ipc_log_string(gsi->ipc_log_ctxt, x, ##__VA_ARGS__); \ + pr_info(x, ##__VA_ARGS__); \ + } \ +} while (0) + +enum connection_state { + STATE_UNINITIALIZED, + STATE_INITIALIZED, + STATE_WAIT_FOR_IPA_RDY, + STATE_CONNECTED, + STATE_HOST_NRDY, + STATE_DISCONNECTED, + STATE_SUSPEND_IN_PROGRESS, + STATE_SUSPENDED +}; + +enum gsi_ctrl_notify_state { + GSI_CTRL_NOTIFY_NONE, + GSI_CTRL_NOTIFY_CONNECT, + GSI_CTRL_NOTIFY_SPEED, + GSI_CTRL_NOTIFY_OFFLINE, + GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE, +}; + +enum rndis_class_id { + RNDIS_ID_UNKNOWN, + WIRELESS_CONTROLLER_REMOTE_NDIS, + MISC_ACTIVE_SYNC, + MISC_RNDIS_OVER_ETHERNET, + MISC_RNDIS_OVER_WIFI, + MISC_RNDIS_OVER_WIMAX, + MISC_RNDIS_OVER_WWAN, + MISC_RNDIS_FOR_IPV4, + MISC_RNDIS_FOR_IPV6, + MISC_RNDIS_FOR_GPRS, + RNDIS_ID_MAX, +}; + +#define MAXQUEUELEN 128 +struct event_queue { + u8 event[MAXQUEUELEN]; + u8 head, tail; + spinlock_t q_lock; +}; + +struct gsi_ntb_info { + __u32 ntb_input_size; + __u16 ntb_max_datagrams; + __u16 reserved; +}; + +struct gsi_ctrl_pkt { + void *buf; + int len; + enum gsi_ctrl_notify_state type; + struct list_head list; +}; + +struct gsi_function_bind_info { + struct usb_string *string_defs; + int ctrl_str_idx; + int data_str_idx; + int iad_str_idx; + int mac_str_idx; + struct usb_interface_descriptor *ctrl_desc; + struct usb_interface_descriptor *data_desc; + struct usb_interface_assoc_descriptor *iad_desc; + struct usb_cdc_ether_desc *cdc_eth_desc; + struct usb_cdc_union_desc *union_desc; + struct usb_interface_descriptor *data_nop_desc; + struct usb_endpoint_descriptor *fs_in_desc; + struct usb_endpoint_descriptor *fs_out_desc; + struct usb_endpoint_descriptor *fs_notify_desc; + struct usb_endpoint_descriptor *hs_in_desc; + struct usb_endpoint_descriptor *hs_out_desc; + struct usb_endpoint_descriptor *hs_notify_desc; + struct usb_endpoint_descriptor *ss_in_desc; + struct usb_endpoint_descriptor *ss_out_desc; + struct usb_endpoint_descriptor *ss_notify_desc; + + struct usb_descriptor_header **fs_desc_hdr; + struct usb_descriptor_header **hs_desc_hdr; + struct usb_descriptor_header **ss_desc_hdr; + const char *in_epname; + const char *out_epname; + + u32 in_req_buf_len; + u32 in_req_num_buf; + u32 out_req_buf_len; + u32 out_req_num_buf; + u32 notify_buf_len; +}; + +struct gsi_ctrl_port { + char name[GSI_CTRL_NAME_LEN]; + struct cdev cdev; + + struct usb_ep *notify; + struct usb_request *notify_req; + bool notify_req_queued; + + atomic_t ctrl_online; + + bool is_open; + + wait_queue_head_t read_wq; + + struct list_head cpkt_req_q; + struct list_head cpkt_resp_q; + unsigned long cpkts_len; + + spinlock_t lock; + + int ipa_cons_clnt_hdl; + int ipa_prod_clnt_hdl; + + unsigned int host_to_modem; + unsigned int copied_to_modem; + unsigned int copied_from_modem; + unsigned int modem_to_host; + unsigned int cpkt_drop_cnt; + unsigned int get_encap_cnt; +}; + +struct gsi_data_port { + struct usb_ep *in_ep; + struct usb_ep *out_ep; + struct usb_gsi_request in_request; + struct usb_gsi_request out_request; + struct usb_gadget *gadget; + struct usb_composite_dev *cdev; + int (*ipa_usb_notify_cb)(enum ipa_usb_notify_event, void *driver_data); + struct ipa_usb_teth_params ipa_init_params; + int in_channel_handle; + int out_channel_handle; + u32 in_xfer_rsc_index; + u32 out_xfer_rsc_index; + u16 in_last_trb_addr; + u16 cdc_filter; + u32 in_aggr_size; + u32 out_aggr_size; + + bool ipa_ready; + bool net_ready_trigger; + struct gsi_ntb_info ntb_info; + + spinlock_t lock; + + struct delayed_work usb_ipa_w; + struct workqueue_struct *ipa_usb_wq; + enum connection_state sm_state; + struct event_queue evt_q; + wait_queue_head_t wait_for_ipa_ready; + + /* Track these for debugfs */ + struct ipa_usb_xdci_chan_params ipa_in_channel_params; + struct ipa_usb_xdci_chan_params ipa_out_channel_params; + struct ipa_usb_xdci_connect_params ipa_conn_pms; +}; + +struct f_gsi { + struct usb_function function; + enum ipa_usb_teth_prot prot_id; + int ctrl_id; + int data_id; + u32 vendorID; + u8 ethaddr[ETH_ADDR_STR_LEN]; + const char *manufacturer; + struct rndis_params *params; + atomic_t connected; + bool data_interface_up; + enum rndis_class_id rndis_id; + + /* function suspend status */ + bool func_is_suspended; + bool func_wakeup_allowed; + + const struct usb_endpoint_descriptor *in_ep_desc_backup; + const struct usb_endpoint_descriptor *out_ep_desc_backup; + + struct gsi_data_port d_port; + struct gsi_ctrl_port c_port; + void *ipc_log_ctxt; + bool rmnet_dtr_status; + + /* To test remote wakeup using debugfs */ + struct timer_list gsi_rw_timer; + u8 debugfs_rw_timer_enable; + u16 gsi_rw_timer_interval; + bool host_supports_flow_control; +}; + +static inline struct f_gsi *func_to_gsi(struct usb_function *f) +{ + return container_of(f, struct f_gsi, function); +} + +static inline struct f_gsi *d_port_to_gsi(struct gsi_data_port *d) +{ + return container_of(d, struct f_gsi, d_port); +} + +static inline struct f_gsi *c_port_to_gsi(struct gsi_ctrl_port *d) +{ + return container_of(d, struct f_gsi, c_port); +} + +/* for configfs support */ +#define MAX_INST_NAME_LEN 40 + +struct gsi_opts { + struct usb_function_instance func_inst; + struct f_gsi *gsi; +}; + +static inline struct gsi_opts *to_gsi_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct gsi_opts, + func_inst.group); +} + +static enum ipa_usb_teth_prot name_to_prot_id(const char *name) +{ + if (!name) + goto error; + + if (!strncasecmp(name, "rndis", strlen("rndis"))) + return IPA_USB_RNDIS; + if (!strncasecmp(name, "ecm", strlen("ecm"))) + return IPA_USB_ECM; + if (!strncasecmp(name, "rmnet", strlen("rmnet"))) + return IPA_USB_RMNET; + if (!strncasecmp(name, "mbim", strlen("mbim"))) + return IPA_USB_MBIM; + if (!strncasecmp(name, "dpl", strlen("dpl"))) + return IPA_USB_DIAG; + +error: + return -EINVAL; +} + +/* device descriptors */ + +#define LOG2_STATUS_INTERVAL_MSEC 5 +#define MAX_NOTIFY_SIZE sizeof(struct usb_cdc_notification) + +/* rmnet device descriptors */ + +static struct usb_interface_descriptor rmnet_gsi_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0x50, + /* .iInterface = DYNAMIC */ +}; + +/* Full speed support */ +static struct usb_endpoint_descriptor rmnet_gsi_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(MAX_NOTIFY_SIZE), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor rmnet_gsi_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor rmnet_gsi_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_descriptor_header *rmnet_gsi_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_gsi_interface_desc, + (struct usb_descriptor_header *) &rmnet_gsi_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_gsi_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_gsi_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_gsi_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(MAX_NOTIFY_SIZE), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_endpoint_descriptor rmnet_gsi_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rmnet_gsi_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *rmnet_gsi_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_gsi_interface_desc, + (struct usb_descriptor_header *) &rmnet_gsi_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_gsi_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_gsi_hs_out_desc, + NULL, +}; + +/* Super speed support */ +static struct usb_endpoint_descriptor rmnet_gsi_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(MAX_NOTIFY_SIZE), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_ss_ep_comp_descriptor rmnet_gsi_ss_notify_comp_desc = { + .bLength = sizeof(rmnet_gsi_ss_notify_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(MAX_NOTIFY_SIZE), +}; + +static struct usb_endpoint_descriptor rmnet_gsi_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor rmnet_gsi_ss_in_comp_desc = { + .bLength = sizeof(rmnet_gsi_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 6, + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor rmnet_gsi_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor rmnet_gsi_ss_out_comp_desc = { + .bLength = sizeof(rmnet_gsi_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 2, + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *rmnet_gsi_ss_function[] = { + (struct usb_descriptor_header *) &rmnet_gsi_interface_desc, + (struct usb_descriptor_header *) &rmnet_gsi_ss_notify_desc, + (struct usb_descriptor_header *) &rmnet_gsi_ss_notify_comp_desc, + (struct usb_descriptor_header *) &rmnet_gsi_ss_in_desc, + (struct usb_descriptor_header *) &rmnet_gsi_ss_in_comp_desc, + (struct usb_descriptor_header *) &rmnet_gsi_ss_out_desc, + (struct usb_descriptor_header *) &rmnet_gsi_ss_out_comp_desc, + NULL, +}; + +/* String descriptors */ +static struct usb_string rmnet_gsi_string_defs[] = { + [0].s = "RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_gsi_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_gsi_string_defs, +}; + +static struct usb_gadget_strings *rmnet_gsi_strings[] = { + &rmnet_gsi_string_table, + NULL, +}; + +/* rndis device descriptors */ + +/* interface descriptor: Supports "Wireless" RNDIS; auto-detected by Windows*/ +static struct usb_interface_descriptor rndis_gsi_control_intf = { + .bLength = sizeof(rndis_gsi_control_intf), + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + /* status endpoint is optional; this could be patched later */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_WIRELESS_CONTROLLER, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x03, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc rndis_gsi_header_desc = { + .bLength = sizeof(rndis_gsi_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor rndis_gsi_call_mgmt_descriptor = { + .bLength = sizeof(rndis_gsi_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + + .bmCapabilities = 0x00, + .bDataInterface = 0x01, +}; + +static struct usb_cdc_acm_descriptor rndis_gsi_acm_descriptor = { + .bLength = sizeof(rndis_gsi_acm_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + + .bmCapabilities = 0x00, +}; + +static struct usb_cdc_union_desc rndis_gsi_union_desc = { + .bLength = sizeof(rndis_gsi_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* the data interface has two bulk endpoints */ + +static struct usb_interface_descriptor rndis_gsi_data_intf = { + .bLength = sizeof(rndis_gsi_data_intf), + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +/* Supports "Wireless" RNDIS; auto-detected by Windows */ +static struct usb_interface_assoc_descriptor +rndis_gsi_iad_descriptor = { + .bLength = sizeof(rndis_gsi_iad_descriptor), + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + .bFirstInterface = 0, /* XXX, hardcoded */ + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = USB_CLASS_WIRELESS_CONTROLLER, + .bFunctionSubClass = 0x01, + .bFunctionProtocol = 0x03, + /* .iFunction = DYNAMIC */ +}; + +/* full speed support: */ +static struct usb_endpoint_descriptor rndis_gsi_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(MAX_NOTIFY_SIZE), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor rndis_gsi_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .wMaxPacketSize = cpu_to_le16(64), + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor rndis_gsi_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .wMaxPacketSize = cpu_to_le16(64), + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *gsi_eth_fs_function[] = { + (struct usb_descriptor_header *) &rndis_gsi_iad_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_gsi_control_intf, + (struct usb_descriptor_header *) &rndis_gsi_header_desc, + (struct usb_descriptor_header *) &rndis_gsi_call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_gsi_acm_descriptor, + (struct usb_descriptor_header *) &rndis_gsi_union_desc, + (struct usb_descriptor_header *) &rndis_gsi_fs_notify_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_gsi_data_intf, + (struct usb_descriptor_header *) &rndis_gsi_fs_in_desc, + (struct usb_descriptor_header *) &rndis_gsi_fs_out_desc, + NULL, +}; + +/* high speed support: */ +static struct usb_endpoint_descriptor rndis_gsi_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(MAX_NOTIFY_SIZE), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor rndis_gsi_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rndis_gsi_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *gsi_eth_hs_function[] = { + (struct usb_descriptor_header *) &rndis_gsi_iad_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_gsi_control_intf, + (struct usb_descriptor_header *) &rndis_gsi_header_desc, + (struct usb_descriptor_header *) &rndis_gsi_call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_gsi_acm_descriptor, + (struct usb_descriptor_header *) &rndis_gsi_union_desc, + (struct usb_descriptor_header *) &rndis_gsi_hs_notify_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_gsi_data_intf, + (struct usb_descriptor_header *) &rndis_gsi_hs_in_desc, + (struct usb_descriptor_header *) &rndis_gsi_hs_out_desc, + NULL, +}; + +/* super speed support: */ +static struct usb_endpoint_descriptor rndis_gsi_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(MAX_NOTIFY_SIZE), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_ss_ep_comp_descriptor rndis_gsi_ss_intr_comp_desc = { + .bLength = sizeof(rndis_gsi_ss_intr_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(MAX_NOTIFY_SIZE), +}; + +static struct usb_endpoint_descriptor rndis_gsi_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor rndis_gsi_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor rndis_gsi_ss_bulk_comp_desc = { + .bLength = sizeof(rndis_gsi_ss_bulk_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 6, + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *gsi_eth_ss_function[] = { + (struct usb_descriptor_header *) &rndis_gsi_iad_descriptor, + + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_gsi_control_intf, + (struct usb_descriptor_header *) &rndis_gsi_header_desc, + (struct usb_descriptor_header *) &rndis_gsi_call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_gsi_acm_descriptor, + (struct usb_descriptor_header *) &rndis_gsi_union_desc, + (struct usb_descriptor_header *) &rndis_gsi_ss_notify_desc, + (struct usb_descriptor_header *) &rndis_gsi_ss_intr_comp_desc, + + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_gsi_data_intf, + (struct usb_descriptor_header *) &rndis_gsi_ss_in_desc, + (struct usb_descriptor_header *) &rndis_gsi_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &rndis_gsi_ss_out_desc, + (struct usb_descriptor_header *) &rndis_gsi_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ +static struct usb_string rndis_gsi_string_defs[] = { + [0].s = "RNDIS Communications Control", + [1].s = "RNDIS Ethernet Data", + [2].s = "RNDIS", + { } /* end of list */ +}; + +static struct usb_gadget_strings rndis_gsi_string_table = { + .language = 0x0409, /* en-us */ + .strings = rndis_gsi_string_defs, +}; + +static struct usb_gadget_strings *rndis_gsi_strings[] = { + &rndis_gsi_string_table, + NULL, +}; + +/* mbim device descriptors */ +#define MBIM_NTB_DEFAULT_IN_SIZE (0x4000) + +static struct usb_cdc_ncm_ntb_parameters mbim_gsi_ntb_parameters = { + .wLength = cpu_to_le16(sizeof(mbim_gsi_ntb_parameters)), + .bmNtbFormatsSupported = cpu_to_le16(USB_CDC_NCM_NTB16_SUPPORTED), + .dwNtbInMaxSize = cpu_to_le32(MBIM_NTB_DEFAULT_IN_SIZE), + .wNdpInDivisor = cpu_to_le16(4), + .wNdpInPayloadRemainder = cpu_to_le16(0), + .wNdpInAlignment = cpu_to_le16(4), + + .dwNtbOutMaxSize = cpu_to_le32(0x4000), + .wNdpOutDivisor = cpu_to_le16(4), + .wNdpOutPayloadRemainder = cpu_to_le16(0), + .wNdpOutAlignment = cpu_to_le16(4), + .wNtbOutMaxDatagrams = cpu_to_le16(16), +}; + +/* + * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; + */ +#define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +static struct usb_interface_assoc_descriptor mbim_gsi_iad_desc = { + .bLength = sizeof(mbim_gsi_iad_desc), + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = 2, + .bFunctionSubClass = 0x0e, + .bFunctionProtocol = 0, + /* .iFunction = DYNAMIC */ +}; + +/* interface descriptor: */ +static struct usb_interface_descriptor mbim_gsi_control_intf = { + .bLength = sizeof(mbim_gsi_control_intf), + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = 0x02, + .bInterfaceSubClass = 0x0e, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc mbim_gsi_header_desc = { + .bLength = sizeof(mbim_gsi_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc mbim_gsi_union_desc = { + .bLength = sizeof(mbim_gsi_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_mbim_desc mbim_gsi_desc = { + .bLength = sizeof(mbim_gsi_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_MBIM_TYPE, + + .bcdMBIMVersion = cpu_to_le16(0x0100), + + .wMaxControlMessage = cpu_to_le16(0x1000), + .bNumberFilters = 0x20, + .bMaxFilterSize = 0x80, + .wMaxSegmentSize = cpu_to_le16(0xfe0), + .bmNetworkCapabilities = 0x20, +}; + +static struct usb_cdc_mbim_extended_desc mbim_gsi_ext_mbb_desc = { + .bLength = sizeof(mbim_gsi_ext_mbb_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_MBIM_EXTENDED_TYPE, + + .bcdMBIMExtendedVersion = cpu_to_le16(0x0100), + .bMaxOutstandingCommandMessages = 64, + .wMTU = cpu_to_le16(1500), +}; + +/* the default data interface has no endpoints ... */ +static struct usb_interface_descriptor mbim_gsi_data_nop_intf = { + .bLength = sizeof(mbim_gsi_data_nop_intf), + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = 0x0a, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0x02, + /* .iInterface = DYNAMIC */ +}; + +/* ... but the "real" data interface has two bulk endpoints */ +static struct usb_interface_descriptor mbim_gsi_data_intf = { + .bLength = sizeof(mbim_gsi_data_intf), + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = 0x0a, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0x02, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor mbim_gsi_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(4 * NCM_STATUS_BYTECOUNT), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor mbim_gsi_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(4 * NCM_STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor mbim_gsi_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(4 * NCM_STATUS_BYTECOUNT), +}; + +static struct usb_descriptor_header *mbim_gsi_fs_function[] = { + (struct usb_descriptor_header *) &mbim_gsi_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_gsi_control_intf, + (struct usb_descriptor_header *) &mbim_gsi_header_desc, + (struct usb_descriptor_header *) &mbim_gsi_union_desc, + (struct usb_descriptor_header *) &mbim_gsi_desc, + (struct usb_descriptor_header *) &mbim_gsi_ext_mbb_desc, + (struct usb_descriptor_header *) &mbim_gsi_fs_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_gsi_data_nop_intf, + (struct usb_descriptor_header *) &mbim_gsi_data_intf, + (struct usb_descriptor_header *) &mbim_gsi_fs_in_desc, + (struct usb_descriptor_header *) &mbim_gsi_fs_out_desc, + NULL, +}; + +/* high speed support: */ +static struct usb_endpoint_descriptor mbim_gsi_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(4 * NCM_STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor mbim_gsi_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mbim_gsi_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *mbim_gsi_hs_function[] = { + (struct usb_descriptor_header *) &mbim_gsi_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_gsi_control_intf, + (struct usb_descriptor_header *) &mbim_gsi_header_desc, + (struct usb_descriptor_header *) &mbim_gsi_union_desc, + (struct usb_descriptor_header *) &mbim_gsi_desc, + (struct usb_descriptor_header *) &mbim_gsi_ext_mbb_desc, + (struct usb_descriptor_header *) &mbim_gsi_hs_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_gsi_data_nop_intf, + (struct usb_descriptor_header *) &mbim_gsi_data_intf, + (struct usb_descriptor_header *) &mbim_gsi_hs_in_desc, + (struct usb_descriptor_header *) &mbim_gsi_hs_out_desc, + NULL, +}; + +/* Super Speed Support */ +static struct usb_endpoint_descriptor mbim_gsi_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(4 * NCM_STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_ss_ep_comp_descriptor mbim_gsi_ss_notify_comp_desc = { + .bLength = sizeof(mbim_gsi_ss_notify_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(4 * NCM_STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor mbim_gsi_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor mbim_gsi_ss_in_comp_desc = { + .bLength = sizeof(mbim_gsi_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 6, + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor mbim_gsi_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor mbim_gsi_ss_out_comp_desc = { + .bLength = sizeof(mbim_gsi_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 2, + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *mbim_gsi_ss_function[] = { + (struct usb_descriptor_header *) &mbim_gsi_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_gsi_control_intf, + (struct usb_descriptor_header *) &mbim_gsi_header_desc, + (struct usb_descriptor_header *) &mbim_gsi_union_desc, + (struct usb_descriptor_header *) &mbim_gsi_desc, + (struct usb_descriptor_header *) &mbim_gsi_ext_mbb_desc, + (struct usb_descriptor_header *) &mbim_gsi_ss_notify_desc, + (struct usb_descriptor_header *) &mbim_gsi_ss_notify_comp_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_gsi_data_nop_intf, + (struct usb_descriptor_header *) &mbim_gsi_data_intf, + (struct usb_descriptor_header *) &mbim_gsi_ss_in_desc, + (struct usb_descriptor_header *) &mbim_gsi_ss_in_comp_desc, + (struct usb_descriptor_header *) &mbim_gsi_ss_out_desc, + (struct usb_descriptor_header *) &mbim_gsi_ss_out_comp_desc, + NULL, +}; + +/* string descriptors: */ +static struct usb_string mbim_gsi_string_defs[] = { + [0].s = "MBIM Control", + [1].s = "MBIM Data", + { } /* end of list */ +}; + +static struct usb_gadget_strings mbim_gsi_string_table = { + .language = 0x0409, /* en-us */ + .strings = mbim_gsi_string_defs, +}; + +static struct usb_gadget_strings *mbim_gsi_strings[] = { + &mbim_gsi_string_table, + NULL, +}; + +/* Microsoft OS Descriptors */ + +/* + * We specify our own bMS_VendorCode byte which Windows will use + * as the bRequest value in subsequent device get requests. + */ +#define MBIM_VENDOR_CODE 0xA5 + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mbim_gsi_ext_config_desc_header { + __le32 dwLength; + __le16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mbim_gsi_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* Microsoft Extended Configuration Descriptor */ +static struct { + struct mbim_gsi_ext_config_desc_header header; + struct mbim_gsi_ext_config_desc_function function; +} mbim_gsi_ext_config_desc = { + .header = { + .dwLength = cpu_to_le32(sizeof(mbim_gsi_ext_config_desc)), + .bcdVersion = cpu_to_le16(0x0100), + .wIndex = cpu_to_le16(4), + .bCount = 1, + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'A', 'L', 'T', 'R', 'C', 'F', 'G' }, + /* .subCompatibleID = DYNAMIC */ + }, +}; +/* ecm device descriptors */ +#define ECM_QC_LOG2_STATUS_INTERVAL_MSEC 5 +#define ECM_QC_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +/* interface descriptor: */ +static struct usb_interface_descriptor ecm_gsi_control_intf = { + .bLength = sizeof(ecm_gsi_control_intf), + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + /* status endpoint is optional; this could be patched later */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc ecm_gsi_header_desc = { + .bLength = sizeof(ecm_gsi_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc ecm_gsi_union_desc = { + .bLength = sizeof(ecm_gsi_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_ether_desc ecm_gsi_desc = { + .bLength = sizeof(ecm_gsi_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, + + /* this descriptor actually adds value, surprise! */ + /* .iMACAddress = DYNAMIC */ + .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */ + .wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN), + .wNumberMCFilters = cpu_to_le16(0), + .bNumberPowerFilters = 0, +}; + +/* the default data interface has no endpoints ... */ + +static struct usb_interface_descriptor ecm_gsi_data_nop_intf = { + .bLength = sizeof(ecm_gsi_data_nop_intf), + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +/* ... but the "real" data interface has two bulk endpoints */ + +static struct usb_interface_descriptor ecm_gsi_data_intf = { + .bLength = sizeof(ecm_gsi_data_intf), + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ +static struct usb_endpoint_descriptor ecm_gsi_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(ECM_QC_STATUS_BYTECOUNT), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor ecm_gsi_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(ECM_QC_STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor ecm_gsi_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(ECM_QC_STATUS_BYTECOUNT), +}; + +static struct usb_descriptor_header *ecm_gsi_fs_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_gsi_control_intf, + (struct usb_descriptor_header *) &ecm_gsi_header_desc, + (struct usb_descriptor_header *) &ecm_gsi_union_desc, + (struct usb_descriptor_header *) &ecm_gsi_desc, + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ecm_gsi_fs_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_gsi_data_nop_intf, + (struct usb_descriptor_header *) &ecm_gsi_data_intf, + (struct usb_descriptor_header *) &ecm_gsi_fs_in_desc, + (struct usb_descriptor_header *) &ecm_gsi_fs_out_desc, + NULL, +}; + +/* high speed support: */ +static struct usb_endpoint_descriptor ecm_gsi_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(ECM_QC_STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor ecm_gsi_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor ecm_gsi_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *ecm_gsi_hs_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_gsi_control_intf, + (struct usb_descriptor_header *) &ecm_gsi_header_desc, + (struct usb_descriptor_header *) &ecm_gsi_union_desc, + (struct usb_descriptor_header *) &ecm_gsi_desc, + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ecm_gsi_hs_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_gsi_data_nop_intf, + (struct usb_descriptor_header *) &ecm_gsi_data_intf, + (struct usb_descriptor_header *) &ecm_gsi_hs_in_desc, + (struct usb_descriptor_header *) &ecm_gsi_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor ecm_gsi_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(ECM_QC_STATUS_BYTECOUNT), + .bInterval = ECM_QC_LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_ss_ep_comp_descriptor ecm_gsi_ss_notify_comp_desc = { + .bLength = sizeof(ecm_gsi_ss_notify_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(ECM_QC_STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor ecm_gsi_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ecm_gsi_ss_in_comp_desc = { + .bLength = sizeof(ecm_gsi_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 6, + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor ecm_gsi_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ecm_gsi_ss_out_comp_desc = { + .bLength = sizeof(ecm_gsi_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 2, + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ecm_gsi_ss_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_gsi_control_intf, + (struct usb_descriptor_header *) &ecm_gsi_header_desc, + (struct usb_descriptor_header *) &ecm_gsi_union_desc, + (struct usb_descriptor_header *) &ecm_gsi_desc, + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ecm_gsi_ss_notify_desc, + (struct usb_descriptor_header *) &ecm_gsi_ss_notify_comp_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_gsi_data_nop_intf, + (struct usb_descriptor_header *) &ecm_gsi_data_intf, + (struct usb_descriptor_header *) &ecm_gsi_ss_in_desc, + (struct usb_descriptor_header *) &ecm_gsi_ss_in_comp_desc, + (struct usb_descriptor_header *) &ecm_gsi_ss_out_desc, + (struct usb_descriptor_header *) &ecm_gsi_ss_out_comp_desc, + NULL, +}; + +/* string descriptors: */ +static struct usb_string ecm_gsi_string_defs[] = { + [0].s = "CDC Ethernet Control Model (ECM)", + [1].s = NULL /* DYNAMIC */, + [2].s = "CDC Ethernet Data", + { } /* end of list */ +}; + +static struct usb_gadget_strings ecm_gsi_string_table = { + .language = 0x0409, /* en-us */ + .strings = ecm_gsi_string_defs, +}; + +static struct usb_gadget_strings *ecm_gsi_strings[] = { + &ecm_gsi_string_table, + NULL, +}; + +/* qdss device descriptor */ + +static struct usb_interface_descriptor qdss_gsi_data_intf_desc = { + .bLength = sizeof(qdss_gsi_data_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0x80, +}; + +static struct usb_endpoint_descriptor qdss_gsi_fs_data_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor qdss_gsi_hs_data_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor qdss_gsi_ss_data_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor qdss_gsi_data_ep_comp_desc = { + .bLength = sizeof(qdss_gsi_data_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 1, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +static struct usb_descriptor_header *qdss_gsi_fs_data_only_desc[] = { + (struct usb_descriptor_header *) &qdss_gsi_data_intf_desc, + (struct usb_descriptor_header *) &qdss_gsi_fs_data_desc, + NULL, +}; + +static struct usb_descriptor_header *qdss_gsi_hs_data_only_desc[] = { + (struct usb_descriptor_header *) &qdss_gsi_data_intf_desc, + (struct usb_descriptor_header *) &qdss_gsi_hs_data_desc, + NULL, +}; + +static struct usb_descriptor_header *qdss_gsi_ss_data_only_desc[] = { + (struct usb_descriptor_header *) &qdss_gsi_data_intf_desc, + (struct usb_descriptor_header *) &qdss_gsi_ss_data_desc, + (struct usb_descriptor_header *) &qdss_gsi_data_ep_comp_desc, + NULL, +}; + +/* string descriptors: */ +static struct usb_string qdss_gsi_string_defs[] = { + [0].s = "DPL Data", + {}, /* end of list */ +}; + +static struct usb_gadget_strings qdss_gsi_string_table = { + .language = 0x0409, + .strings = qdss_gsi_string_defs, +}; + +static struct usb_gadget_strings *qdss_gsi_strings[] = { + &qdss_gsi_string_table, + NULL, +}; +#endif diff --git a/drivers/usb/gadget/function/f_qdss.c b/drivers/usb/gadget/function/f_qdss.c new file mode 100644 index 000000000000..1ce6c1dfbe1f --- /dev/null +++ b/drivers/usb/gadget/function/f_qdss.c @@ -0,0 +1,1236 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * f_qdss.c -- QDSS function Driver + * + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "f_qdss.h" + +static void *_qdss_ipc_log; + +#define NUM_PAGES 10 /* # of pages for ipc logging */ + +#ifdef CONFIG_DYNAMIC_DEBUG +#define qdss_log(fmt, ...) do { \ + ipc_log_string(_qdss_ipc_log, "%s: " fmt, __func__, ##__VA_ARGS__); \ + dynamic_pr_debug("%s: " fmt, __func__, ##__VA_ARGS__); \ +} while (0) +#else +#define qdss_log(fmt, ...) \ + ipc_log_string(_qdss_ipc_log, "%s: " fmt, __func__, ##__VA_ARGS__) +#endif + +static DEFINE_SPINLOCK(channel_lock); +static LIST_HEAD(usb_qdss_ch_list); + +static struct usb_interface_descriptor qdss_data_intf_desc = { + .bLength = sizeof(qdss_data_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0x70, +}; + +static struct usb_endpoint_descriptor qdss_hs_data_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor qdss_ss_data_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor qdss_data_ep_comp_desc = { + .bLength = sizeof(qdss_data_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 1, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +static struct usb_interface_descriptor qdss_ctrl_intf_desc = { + .bLength = sizeof(qdss_ctrl_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0x70, +}; + +static struct usb_endpoint_descriptor qdss_hs_ctrl_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor qdss_ss_ctrl_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor qdss_hs_ctrl_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor qdss_ss_ctrl_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(0x400), +}; + +static struct usb_ss_ep_comp_descriptor qdss_ctrl_in_ep_comp_desc = { + .bLength = sizeof(qdss_ctrl_in_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +static struct usb_ss_ep_comp_descriptor qdss_ctrl_out_ep_comp_desc = { + .bLength = sizeof(qdss_ctrl_out_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, +}; + +/* Full speed support */ +static struct usb_endpoint_descriptor qdss_fs_data_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor qdss_fs_ctrl_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor qdss_fs_ctrl_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(64), +}; + +static struct usb_descriptor_header *qdss_fs_desc[] = { + (struct usb_descriptor_header *) &qdss_data_intf_desc, + (struct usb_descriptor_header *) &qdss_fs_data_desc, + (struct usb_descriptor_header *) &qdss_ctrl_intf_desc, + (struct usb_descriptor_header *) &qdss_fs_ctrl_in_desc, + (struct usb_descriptor_header *) &qdss_fs_ctrl_out_desc, + NULL, +}; + +static struct usb_descriptor_header *qdss_hs_desc[] = { + (struct usb_descriptor_header *) &qdss_data_intf_desc, + (struct usb_descriptor_header *) &qdss_hs_data_desc, + (struct usb_descriptor_header *) &qdss_ctrl_intf_desc, + (struct usb_descriptor_header *) &qdss_hs_ctrl_in_desc, + (struct usb_descriptor_header *) &qdss_hs_ctrl_out_desc, + NULL, +}; + +static struct usb_descriptor_header *qdss_ss_desc[] = { + (struct usb_descriptor_header *) &qdss_data_intf_desc, + (struct usb_descriptor_header *) &qdss_ss_data_desc, + (struct usb_descriptor_header *) &qdss_data_ep_comp_desc, + (struct usb_descriptor_header *) &qdss_ctrl_intf_desc, + (struct usb_descriptor_header *) &qdss_ss_ctrl_in_desc, + (struct usb_descriptor_header *) &qdss_ctrl_in_ep_comp_desc, + (struct usb_descriptor_header *) &qdss_ss_ctrl_out_desc, + (struct usb_descriptor_header *) &qdss_ctrl_out_ep_comp_desc, + NULL, +}; + +static struct usb_descriptor_header *qdss_fs_data_only_desc[] = { + (struct usb_descriptor_header *) &qdss_data_intf_desc, + (struct usb_descriptor_header *) &qdss_fs_data_desc, + NULL, +}; + +static struct usb_descriptor_header *qdss_hs_data_only_desc[] = { + (struct usb_descriptor_header *) &qdss_data_intf_desc, + (struct usb_descriptor_header *) &qdss_hs_data_desc, + NULL, +}; + +static struct usb_descriptor_header *qdss_ss_data_only_desc[] = { + (struct usb_descriptor_header *) &qdss_data_intf_desc, + (struct usb_descriptor_header *) &qdss_ss_data_desc, + (struct usb_descriptor_header *) &qdss_data_ep_comp_desc, + NULL, +}; + +/* string descriptors: */ +#define QDSS_DATA_IDX 0 +#define QDSS_CTRL_IDX 1 + +static struct usb_string qdss_string_defs[] = { + [QDSS_DATA_IDX].s = "QDSS DATA", + [QDSS_CTRL_IDX].s = "QDSS CTRL", + {}, /* end of list */ +}; + +static struct usb_gadget_strings qdss_string_table = { + .language = 0x0409, + .strings = qdss_string_defs, +}; + +static struct usb_gadget_strings *qdss_strings[] = { + &qdss_string_table, + NULL, +}; + +static void qdss_disable(struct usb_function *f); + +static inline struct f_qdss *func_to_qdss(struct usb_function *f) +{ + return container_of(f, struct f_qdss, port.function); +} + +static +struct usb_qdss_opts *to_fi_usb_qdss_opts(struct usb_function_instance *fi) +{ + return container_of(fi, struct usb_qdss_opts, func_inst); +} + +static inline bool qdss_uses_sw_path(struct f_qdss *qdss) +{ + return (!strcmp(qdss->ch.name, USB_QDSS_CH_MDM) || + !strcmp(qdss->ch.name, USB_QDSS_CH_SW)); +} + +/*----------------------------------------------------------------------*/ + +static void qdss_write_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_qdss *qdss = ep->driver_data; + struct qdss_req *qreq = req->context; + struct qdss_request *d_req = qreq->qdss_req; + struct usb_ep *in; + enum qdss_state state; + unsigned long flags; + + in = qdss->port.data; + state = USB_QDSS_DATA_WRITE_DONE; + + qdss_log("channel:%s ep:%s req:%pK req->status:%d req->length:%d\n", + qdss->ch.name, ep->name, req, req->status, req->length); + spin_lock_irqsave(&qdss->lock, flags); + list_move_tail(&qreq->list, &qdss->data_write_pool); + + /* + * When channel is closed, we move all queued requests to + * dequeued_data_pool list and wait for it to be drained. + * Signal the completion here if the channel is closed + * and both queued & dequeued lists are empty. + */ + if (!qdss->opened && list_empty(&qdss->dequeued_data_pool) && + list_empty(&qdss->queued_data_pool)) + complete(&qdss->dequeue_done); + + if (req->length != 0) { + d_req->actual = req->actual; + d_req->status = req->status; + } + spin_unlock_irqrestore(&qdss->lock, flags); + + if (qdss->ch.notify) + qdss->ch.notify(qdss->ch.priv, state, d_req, NULL); +} + +static void qdss_free_reqs(struct f_qdss *qdss) +{ + struct list_head *act, *tmp; + struct qdss_req *qreq; + int data_write_req = 0; + unsigned long flags; + + lockdep_assert_held(&qdss->mutex); + + spin_lock_irqsave(&qdss->lock, flags); + + list_for_each_safe(act, tmp, &qdss->data_write_pool) { + qreq = list_entry(act, struct qdss_req, list); + list_del(&qreq->list); + usb_ep_free_request(qdss->port.data, qreq->usb_req); + kfree(qreq); + data_write_req++; + } + + qdss_log("channel:%s data_write_req:%d freed\n", qdss->ch.name, + data_write_req); + spin_unlock_irqrestore(&qdss->lock, flags); +} + +void usb_qdss_free_req(struct usb_qdss_ch *ch) +{ + struct f_qdss *qdss = container_of(ch, struct f_qdss, ch); + + if (!ch) { + pr_err("%s: ch is NULL\n", __func__); + return; + } + + mutex_lock(&qdss->mutex); + if (!qdss->opened) + pr_err("%s: channel %s closed\n", __func__, ch->name); + else + qdss_free_reqs(qdss); + mutex_unlock(&qdss->mutex); +} +EXPORT_SYMBOL(usb_qdss_free_req); + +int usb_qdss_alloc_req(struct usb_qdss_ch *ch, int no_write_buf) +{ + struct f_qdss *qdss = container_of(ch, struct f_qdss, ch); + struct usb_request *req; + struct usb_ep *in; + struct list_head *list_pool; + int i; + struct qdss_req *qreq; + unsigned long flags; + + if (!ch) { + pr_err("%s: ch is NULL\n", __func__); + return -EINVAL; + } + + qdss_log("channel:%s num_write_buf:%d\n", ch->name, no_write_buf); + + if (!qdss) { + pr_err("%s: %s closed\n", __func__, ch->name); + return -ENODEV; + } + + mutex_lock(&qdss->mutex); + + in = qdss->port.data; + list_pool = &qdss->data_write_pool; + + for (i = 0; i < no_write_buf; i++) { + qreq = kzalloc(sizeof(struct qdss_req), GFP_KERNEL); + if (!qreq) + goto fail; + + req = usb_ep_alloc_request(in, GFP_ATOMIC); + if (!req) { + pr_err("%s: ctrl_in allocation err\n", __func__); + kfree(qreq); + goto fail; + } + spin_lock_irqsave(&qdss->lock, flags); + qreq->usb_req = req; + req->context = qreq; + req->complete = qdss_write_complete; + list_add_tail(&qreq->list, list_pool); + spin_unlock_irqrestore(&qdss->lock, flags); + } + + mutex_unlock(&qdss->mutex); + return 0; + +fail: + qdss_free_reqs(qdss); + mutex_unlock(&qdss->mutex); + return -ENOMEM; +} +EXPORT_SYMBOL(usb_qdss_alloc_req); + +static void clear_eps(struct usb_function *f) +{ + struct f_qdss *qdss = func_to_qdss(f); + + qdss_log("channel:%s\n", qdss->ch.name); + + if (qdss->port.ctrl_in) + qdss->port.ctrl_in->driver_data = NULL; + if (qdss->port.ctrl_out) + qdss->port.ctrl_out->driver_data = NULL; + if (qdss->port.data) { + msm_ep_clear_ops(qdss->port.data); + msm_ep_set_mode(qdss->port.data, USB_EP_NONE); + qdss->port.data->driver_data = NULL; + } +} + +static void clear_desc(struct usb_gadget *gadget, struct usb_function *f) +{ + struct f_qdss *qdss = func_to_qdss(f); + + qdss_log("channel:%s\n", qdss->ch.name); + + usb_free_all_descriptors(f); +} + +static int qdss_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_gadget *gadget = c->cdev->gadget; + struct f_qdss *qdss = func_to_qdss(f); + struct usb_ep *ep; + int iface, id, ret; + + qdss_log("channel:%s\n", qdss->ch.name); + + /* Allocate data I/F */ + iface = usb_interface_id(c, f); + if (iface < 0) { + pr_err("interface allocation error\n"); + return iface; + } + qdss_data_intf_desc.bInterfaceNumber = iface; + qdss->data_iface_id = iface; + + if (!qdss_string_defs[QDSS_DATA_IDX].id) { + id = usb_string_id(c->cdev); + if (id < 0) + return id; + qdss_string_defs[QDSS_DATA_IDX].id = id; + qdss_data_intf_desc.iInterface = id; + } + + if (qdss->debug_inface_enabled) { + /* Allocate ctrl I/F */ + iface = usb_interface_id(c, f); + if (iface < 0) { + pr_err("interface allocation error\n"); + return iface; + } + qdss_ctrl_intf_desc.bInterfaceNumber = iface; + qdss->ctrl_iface_id = iface; + + if (!qdss_string_defs[QDSS_CTRL_IDX].id) { + id = usb_string_id(c->cdev); + if (id < 0) + return id; + qdss_string_defs[QDSS_CTRL_IDX].id = id; + qdss_ctrl_intf_desc.iInterface = id; + } + } + + /* for non-accelerated path keep tx fifo size 1k */ + if (qdss_uses_sw_path(qdss)) + qdss_data_ep_comp_desc.bMaxBurst = 0; + + ep = usb_ep_autoconfig(gadget, &qdss_fs_data_desc); + if (!ep) { + pr_err("%s: ep_autoconfig error\n", __func__); + goto clear_ep; + } + qdss->port.data = ep; + ep->driver_data = qdss; + + if (!qdss_uses_sw_path(qdss)) { + ret = msm_ep_set_mode(qdss->port.data, qdss->ch.ch_type); + if (ret < 0) + goto clear_ep; + + msm_ep_update_ops(qdss->port.data); + } + + if (qdss->debug_inface_enabled) { + ep = usb_ep_autoconfig(gadget, &qdss_fs_ctrl_in_desc); + if (!ep) { + pr_err("%s: ep_autoconfig error\n", __func__); + goto clear_ep; + } + + qdss->port.ctrl_in = ep; + ep->driver_data = qdss; + + ep = usb_ep_autoconfig(gadget, &qdss_fs_ctrl_out_desc); + if (!ep) { + pr_err("%s: ep_autoconfig error\n", __func__); + goto clear_ep; + } + qdss->port.ctrl_out = ep; + ep->driver_data = qdss; + } + + if (!qdss_uses_sw_path(qdss)) { + ret = alloc_hw_req(qdss->port.data); + if (ret) { + pr_err("%s: alloc_sps_req error (%d)\n", + __func__, ret); + goto clear_ep; + } + } + + /* update hs/ss descriptors */ + qdss_hs_data_desc.bEndpointAddress = + qdss_ss_data_desc.bEndpointAddress = + qdss_fs_data_desc.bEndpointAddress; + if (qdss->debug_inface_enabled) { + qdss_hs_ctrl_in_desc.bEndpointAddress = + qdss_ss_ctrl_in_desc.bEndpointAddress = + qdss_fs_ctrl_in_desc.bEndpointAddress; + qdss_hs_ctrl_out_desc.bEndpointAddress = + qdss_ss_ctrl_out_desc.bEndpointAddress = + qdss_fs_ctrl_out_desc.bEndpointAddress; + } + + if (qdss->debug_inface_enabled) + ret = usb_assign_descriptors(f, qdss_fs_desc, qdss_hs_desc, + qdss_ss_desc, qdss_ss_desc); + else + ret = usb_assign_descriptors(f, qdss_fs_data_only_desc, + qdss_hs_data_only_desc, qdss_ss_data_only_desc, + qdss_ss_data_only_desc); + + if (ret) + goto fail; + + return 0; + +fail: + /* check if usb_request allocated */ + if (qdss->endless_req) { + usb_ep_free_request(qdss->port.data, + qdss->endless_req); + qdss->endless_req = NULL; + } + +clear_ep: + clear_eps(f); + + return -EOPNOTSUPP; +} + + +static void qdss_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_qdss *qdss = func_to_qdss(f); + struct usb_gadget *gadget = c->cdev->gadget; + + qdss_log("channel:%s\n", qdss->ch.name); + + qdss_disable(f); + flush_workqueue(qdss->wq); + + if (qdss->endless_req) { + usb_ep_free_request(qdss->port.data, + qdss->endless_req); + qdss->endless_req = NULL; + } + + /* Reset string ids */ + qdss_string_defs[QDSS_DATA_IDX].id = 0; + qdss_string_defs[QDSS_CTRL_IDX].id = 0; + + clear_eps(f); + clear_desc(gadget, f); +} + +static void qdss_eps_disable(struct usb_function *f) +{ + struct f_qdss *qdss = func_to_qdss(f); + + qdss_log("channel:%s\n", qdss->ch.name); + + if (qdss->ctrl_in_enabled) { + usb_ep_disable(qdss->port.ctrl_in); + qdss->ctrl_in_enabled = 0; + } + + if (qdss->ctrl_out_enabled) { + usb_ep_disable(qdss->port.ctrl_out); + qdss->ctrl_out_enabled = 0; + } + + if (qdss->data_enabled) { + usb_ep_disable(qdss->port.data); + qdss->data_enabled = 0; + } +} + +static void usb_qdss_disconnect_work(struct work_struct *work) +{ + struct f_qdss *qdss; + int status; + + qdss = container_of(work, struct f_qdss, disconnect_w); + qdss_log("channel:%s\n", qdss->ch.name); + + /* Notify qdss to cancel all active transfers */ + if (qdss->ch.notify) + qdss->ch.notify(qdss->ch.priv, + USB_QDSS_DISCONNECT, + NULL, + NULL); + + mutex_lock(&qdss->mutex); + + /* Uninitialized init data i.e. ep specific operation */ + if (qdss->opened && !qdss_uses_sw_path(qdss)) { + status = set_qdss_data_connection(qdss, 0); + if (status) + pr_err("qdss_disconnect error\n"); + } + + /* + * Decrement usage count which was incremented + * before calling connect work + */ + usb_gadget_autopm_put_async(qdss->gadget); + + mutex_unlock(&qdss->mutex); +} + +static void qdss_disable(struct usb_function *f) +{ + struct f_qdss *qdss = func_to_qdss(f); + unsigned long flags; + + qdss_log("channel:%s\n", qdss->ch.name); + spin_lock_irqsave(&qdss->lock, flags); + if (!qdss->usb_connected) { + spin_unlock_irqrestore(&qdss->lock, flags); + return; + } + + qdss->usb_connected = 0; + spin_unlock_irqrestore(&qdss->lock, flags); + /*cancell all active xfers*/ + qdss_eps_disable(f); + queue_work(qdss->wq, &qdss->disconnect_w); +} + +static void usb_qdss_connect_work(struct work_struct *work) +{ + struct f_qdss *qdss; + int status; + struct usb_request *req = NULL; + unsigned long flags; + + qdss = container_of(work, struct f_qdss, connect_w); + + /* If cable is already removed, discard connect_work */ + if (qdss->usb_connected == 0) { + cancel_work_sync(&qdss->disconnect_w); + return; + } + + mutex_lock(&qdss->mutex); + + qdss_log("channel:%s opened:%d\n", qdss->ch.name, qdss->opened); + if (!qdss->opened) + goto unlock_out; + + if (qdss_uses_sw_path(qdss)) + goto notify; + + status = set_qdss_data_connection(qdss, 1); + if (status) { + pr_err("set_qdss_data_connection error(%d)\n", status); + goto unlock_out; + } + + spin_lock_irqsave(&qdss->lock, flags); + req = qdss->endless_req; + spin_unlock_irqrestore(&qdss->lock, flags); + if (!req) + goto unlock_out; + + status = usb_ep_queue(qdss->port.data, req, GFP_ATOMIC); + if (status) { + pr_err("%s: usb_ep_queue error (%d)\n", __func__, status); + goto unlock_out; + } + +notify: + mutex_unlock(&qdss->mutex); + if (qdss->ch.notify) + qdss->ch.notify(qdss->ch.priv, USB_QDSS_CONNECT, + NULL, &qdss->ch); + return; + +unlock_out: + mutex_unlock(&qdss->mutex); +} + +static int qdss_set_alt(struct usb_function *f, unsigned int intf, + unsigned int alt) +{ + struct f_qdss *qdss = func_to_qdss(f); + struct usb_gadget *gadget = f->config->cdev->gadget; + int ret = 0; + + qdss_log("qdss pointer = %pK\n", qdss); + qdss->gadget = gadget; + + if (alt != 0) + goto fail1; + + if (gadget->speed < USB_SPEED_HIGH) { + pr_err("%s: qdss doesn't support USB full or low speed\n", + __func__); + ret = -EINVAL; + goto fail1; + } + + if (intf == qdss->data_iface_id && !qdss->data_enabled) { + /* Increment usage count on connect */ + usb_gadget_autopm_get_async(qdss->gadget); + + ret = config_ep_by_speed(gadget, f, qdss->port.data); + if (ret) { + pr_err("%s: failed config_ep_by_speed ret:%d\n", + __func__, ret); + goto fail; + } + + ret = usb_ep_enable(qdss->port.data); + if (ret) { + pr_err("%s: failed to enable ep ret:%d\n", + __func__, ret); + goto fail; + } + + qdss->port.data->driver_data = qdss; + qdss->data_enabled = 1; + + + } else if ((intf == qdss->ctrl_iface_id) && + (qdss->debug_inface_enabled)) { + + if (config_ep_by_speed(gadget, f, qdss->port.ctrl_in)) { + ret = -EINVAL; + goto fail1; + } + + ret = usb_ep_enable(qdss->port.ctrl_in); + if (ret) + goto fail1; + + qdss->port.ctrl_in->driver_data = qdss; + qdss->ctrl_in_enabled = 1; + + if (config_ep_by_speed(gadget, f, qdss->port.ctrl_out)) { + ret = -EINVAL; + goto fail1; + } + + + ret = usb_ep_enable(qdss->port.ctrl_out); + if (ret) + goto fail1; + + qdss->port.ctrl_out->driver_data = qdss; + qdss->ctrl_out_enabled = 1; + } + + if (qdss->debug_inface_enabled) { + if (qdss->ctrl_out_enabled && qdss->ctrl_in_enabled && + qdss->data_enabled) { + qdss->usb_connected = 1; + qdss_log("usb_connected INTF enabled\n"); + } + } else { + if (qdss->data_enabled) { + qdss->usb_connected = 1; + qdss_log("usb_connected INTF disabled\n"); + } + } + + if (qdss->usb_connected) + queue_work(qdss->wq, &qdss->connect_w); + + return 0; +fail: + /* Decrement usage count in case of failure */ + usb_gadget_autopm_put_async(qdss->gadget); +fail1: + pr_err("%s failed ret:%d\n", __func__, ret); + qdss_eps_disable(f); + return ret; +} + +static struct f_qdss *alloc_usb_qdss(char *channel_name) +{ + struct f_qdss *qdss; + int found = 0; + struct usb_qdss_ch *ch; + unsigned long flags; + + spin_lock_irqsave(&channel_lock, flags); + list_for_each_entry(ch, &usb_qdss_ch_list, list) { + if (!strcmp(channel_name, ch->name)) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&channel_lock, flags); + + if (found) { + pr_err("%s: (%s) is already available.\n", + __func__, channel_name); + return ERR_PTR(-EEXIST); + } + + qdss = kzalloc(sizeof(struct f_qdss), GFP_KERNEL); + if (!qdss) + return ERR_PTR(-ENOMEM); + + qdss->wq = create_singlethread_workqueue(channel_name); + if (!qdss->wq) { + kfree(qdss); + return ERR_PTR(-ENOMEM); + } + + spin_lock_irqsave(&channel_lock, flags); + ch = &qdss->ch; + ch->name = channel_name; + + if (!strcmp(ch->name, USB_QDSS_CH_EBC)) + ch->ch_type = USB_EP_EBC; + else + ch->ch_type = USB_EP_NONE; + + list_add_tail(&ch->list, &usb_qdss_ch_list); + spin_unlock_irqrestore(&channel_lock, flags); + + spin_lock_init(&qdss->lock); + INIT_LIST_HEAD(&qdss->data_write_pool); + INIT_LIST_HEAD(&qdss->queued_data_pool); + INIT_LIST_HEAD(&qdss->dequeued_data_pool); + INIT_WORK(&qdss->connect_w, usb_qdss_connect_work); + INIT_WORK(&qdss->disconnect_w, usb_qdss_disconnect_work); + mutex_init(&qdss->mutex); + init_completion(&qdss->dequeue_done); + + return qdss; +} + +int usb_qdss_write(struct usb_qdss_ch *ch, struct qdss_request *d_req) +{ + struct f_qdss *qdss = container_of(ch, struct f_qdss, ch); + unsigned long flags; + struct usb_request *req = NULL; + struct qdss_req *qreq; + + if (!ch) { + pr_err("%s: ch is NULL\n", __func__); + return -EINVAL; + } + + mutex_lock(&qdss->mutex); + + qdss_log("channel:%s d_req:%pK\n", ch->name, d_req); + spin_lock_irqsave(&qdss->lock, flags); + + if (!qdss->opened || !qdss->usb_connected) { + spin_unlock_irqrestore(&qdss->lock, flags); + qdss_log("return -EIO\n"); + mutex_unlock(&qdss->mutex); + return -EIO; + } + + if (list_empty(&qdss->data_write_pool)) { + pr_err("error: usb_qdss_data_write list is empty\n"); + spin_unlock_irqrestore(&qdss->lock, flags); + mutex_unlock(&qdss->mutex); + return -EAGAIN; + } + + qreq = list_first_entry(&qdss->data_write_pool, struct qdss_req, + list); + list_move_tail(&qreq->list, &qdss->queued_data_pool); + spin_unlock_irqrestore(&qdss->lock, flags); + + qreq->qdss_req = d_req; + req = qreq->usb_req; + req->buf = d_req->buf; + req->length = d_req->length; + req->sg = d_req->sg; + req->num_sgs = d_req->num_sgs; + if (req->sg) + qdss_log("%s: req:%pK req->num_sgs:0x%x\n", + ch->name, req, req->num_sgs); + else + qdss_log("%s: req:%pK rq->length:0x%x\n", + ch->name, req, req->length); + if (usb_ep_queue(qdss->port.data, req, GFP_ATOMIC)) { + spin_lock_irqsave(&qdss->lock, flags); + /* Remove from queued pool and add back to data pool */ + list_move_tail(&qreq->list, &qdss->data_write_pool); + spin_unlock_irqrestore(&qdss->lock, flags); + pr_err("qdss usb_ep_queue failed\n"); + mutex_unlock(&qdss->mutex); + return -EIO; + } + + mutex_unlock(&qdss->mutex); + return 0; +} +EXPORT_SYMBOL(usb_qdss_write); + +struct usb_qdss_ch *usb_qdss_open(const char *name, void *priv, + void (*notify)(void *priv, unsigned int event, + struct qdss_request *d_req, struct usb_qdss_ch *)) +{ + struct usb_qdss_ch *ch; + struct f_qdss *qdss = NULL; + unsigned long flags; + + qdss_log("called for channel:%s\n", name); + if (!notify) { + pr_err("%s: notification func is missing\n", __func__); + return NULL; + } + + spin_lock_irqsave(&channel_lock, flags); +retry: + /* Check if we already have a channel with this name */ + list_for_each_entry(ch, &usb_qdss_ch_list, list) { + if (!strcmp(name, ch->name)) { + qdss = container_of(ch, struct f_qdss, ch); + break; + } + } + + if (!strcmp(name, USB_QDSS_CH_SW) && + (!qdss || !qdss->port.function.name)) { + qdss_log("qdss_sw not added to config, fall back to qdss_mdm\n"); + name = USB_QDSS_CH_MDM; + qdss = NULL; + goto retry; + } + + spin_unlock_irqrestore(&channel_lock, flags); + if (!qdss) { + qdss_log("failed to find channel:%s\n", name); + return NULL; + } + + mutex_lock(&qdss->mutex); + qdss_log("qdss ctx found for channel:%s\n", name); + ch->priv = priv; + ch->notify = notify; + qdss->opened = true; + reinit_completion(&qdss->dequeue_done); + + /* the case USB cabel was connected before qdss called qdss_open */ + if (qdss->usb_connected) + queue_work(qdss->wq, &qdss->connect_w); + + mutex_unlock(&qdss->mutex); + return ch; +} +EXPORT_SYMBOL(usb_qdss_open); + +void usb_qdss_close(struct usb_qdss_ch *ch) +{ + struct f_qdss *qdss = container_of(ch, struct f_qdss, ch); + struct usb_gadget *gadget; + unsigned long flags; + int status; + struct qdss_req *qreq; + bool do_wait; + + if (!ch) { + pr_err("%s: ch is NULL\n", __func__); + return; + } + + qdss_log("channel:%s\n", ch->name); + + mutex_lock(&qdss->mutex); + if (!qdss->opened) { + pr_err("%s: channel %s closed\n", __func__, ch->name); + goto unlock_out; + } + + spin_lock_irqsave(&qdss->lock, flags); + qdss->opened = false; + /* + * Some UDCs like DWC3 stop the endpoint transfer upon dequeue + * of a request and retire all the previously *started* requests. + * This introduces a race between the below dequeue loop and + * retiring of all started requests. As soon as we drop the lock + * here before dequeue, the request gets retired and UDC thinks + * we are dequeuing a request that was not queued before. To + * avoid this problem, lets dequeue the requests in the reverse + * order. + */ + while (!list_empty(&qdss->queued_data_pool)) { + qreq = list_last_entry(&qdss->queued_data_pool, + struct qdss_req, list); + list_move_tail(&qreq->list, &qdss->dequeued_data_pool); + spin_unlock_irqrestore(&qdss->lock, flags); + status = usb_ep_dequeue(qdss->port.data, qreq->usb_req); + qdss_log("dequeue req:%pK status=%d\n", qreq->usb_req, status); + spin_lock_irqsave(&qdss->lock, flags); + } + + /* + * It's possible that requests may be completed synchronously during + * usb_ep_dequeue() and would have already been moved back to + * data_write_pool. So make sure to check that our dequeued_data_pool + * is empty. If not, wait for it to happen. The request completion + * handler would signal us when this list is empty and channel close + * is in progress. + */ + do_wait = !list_empty(&qdss->dequeued_data_pool); + spin_unlock_irqrestore(&qdss->lock, flags); + + if (do_wait) { + qdss_log("waiting for completion on dequeued requests\n"); + wait_for_completion(&qdss->dequeue_done); + } + + WARN_ON(!list_empty(&qdss->dequeued_data_pool)); + + qdss_free_reqs(qdss); + ch->notify = NULL; + if (!qdss->usb_connected || qdss_uses_sw_path(qdss)) + goto unlock_out; + + if (qdss->endless_req) + usb_ep_dequeue(qdss->port.data, qdss->endless_req); + + gadget = qdss->gadget; + + status = set_qdss_data_connection(qdss, 0); + if (status) + pr_err("%s:qdss_disconnect error\n", __func__); + +unlock_out: + mutex_unlock(&qdss->mutex); +} +EXPORT_SYMBOL(usb_qdss_close); + +static void qdss_cleanup(void) +{ + struct f_qdss *qdss; + struct list_head *act, *tmp; + struct usb_qdss_ch *_ch; + unsigned long flags; + + qdss_log("cleaning up channel resources.\n"); + + list_for_each_safe(act, tmp, &usb_qdss_ch_list) { + _ch = list_entry(act, struct usb_qdss_ch, list); + qdss = container_of(_ch, struct f_qdss, ch); + destroy_workqueue(qdss->wq); + spin_lock_irqsave(&channel_lock, flags); + if (!_ch->priv) { + list_del(&_ch->list); + kfree(qdss); + } + spin_unlock_irqrestore(&channel_lock, flags); + } +} + +static void qdss_free_func(struct usb_function *f) +{ + struct f_qdss *qdss = func_to_qdss(f); + + qdss->debug_inface_enabled = false; +} + +static inline struct usb_qdss_opts *to_f_qdss_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct usb_qdss_opts, + func_inst.group); +} + +static void qdss_attr_release(struct config_item *item) +{ + struct usb_qdss_opts *opts = to_f_qdss_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations qdss_item_ops = { + .release = qdss_attr_release, +}; + +static ssize_t qdss_enable_debug_inface_show(struct config_item *item, + char *page) +{ + return scnprintf(page, PAGE_SIZE, "%s\n", + (to_f_qdss_opts(item)->usb_qdss->debug_inface_enabled) ? + "Enabled" : "Disabled"); +} + +static ssize_t qdss_enable_debug_inface_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_qdss *qdss = to_f_qdss_opts(item)->usb_qdss; + unsigned long flags; + u8 stats; + + if (page == NULL) { + pr_err("Invalid buffer\n"); + return len; + } + + if (kstrtou8(page, 0, &stats) != 0 && !(stats == 0 || stats == 1)) { + pr_err("(%u)Wrong value. enter 0 to disable or 1 to enable.\n", + stats); + return len; + } + + spin_lock_irqsave(&qdss->lock, flags); + qdss->debug_inface_enabled = stats; + spin_unlock_irqrestore(&qdss->lock, flags); + return len; +} + +CONFIGFS_ATTR(qdss_, enable_debug_inface); +static struct configfs_attribute *qdss_attrs[] = { + &qdss_attr_enable_debug_inface, + NULL, +}; + +static struct config_item_type qdss_func_type = { + .ct_item_ops = &qdss_item_ops, + .ct_attrs = qdss_attrs, + .ct_owner = THIS_MODULE, +}; + +static void usb_qdss_free_inst(struct usb_function_instance *fi) +{ + struct usb_qdss_opts *opts; + + opts = container_of(fi, struct usb_qdss_opts, func_inst); + kfree(opts->usb_qdss); + kfree(opts); +} + +static int usb_qdss_set_inst_name(struct usb_function_instance *f, + const char *name) +{ + struct usb_qdss_opts *opts = + container_of(f, struct usb_qdss_opts, func_inst); + char *ptr; + size_t name_len; + struct f_qdss *usb_qdss; + + /* get channel_name as expected input qdss. */ + name_len = strlen(name) + 1; + if (name_len > 15) + return -ENAMETOOLONG; + + /* get channel name */ + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) { + pr_err("error:%ld\n", PTR_ERR(ptr)); + return -ENOMEM; + } + + opts->channel_name = ptr; + qdss_log("qdss: channel_name:%s\n", opts->channel_name); + + usb_qdss = alloc_usb_qdss(opts->channel_name); + if (IS_ERR(usb_qdss)) { + pr_err("Failed to create usb_qdss port(%s)\n", + opts->channel_name); + return -ENOMEM; + } + + opts->usb_qdss = usb_qdss; + return 0; +} + +static struct usb_function_instance *qdss_alloc_inst(void) +{ + struct usb_qdss_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = usb_qdss_free_inst; + opts->func_inst.set_inst_name = usb_qdss_set_inst_name; + + config_group_init_type_name(&opts->func_inst.group, "", + &qdss_func_type); + return &opts->func_inst; +} + +static struct usb_function *qdss_alloc(struct usb_function_instance *fi) +{ + struct usb_qdss_opts *opts = to_fi_usb_qdss_opts(fi); + struct f_qdss *usb_qdss = opts->usb_qdss; + + usb_qdss->port.function.name = "usb_qdss"; + usb_qdss->port.function.strings = qdss_strings; + usb_qdss->port.function.bind = qdss_bind; + usb_qdss->port.function.unbind = qdss_unbind; + usb_qdss->port.function.set_alt = qdss_set_alt; + usb_qdss->port.function.disable = qdss_disable; + usb_qdss->port.function.setup = NULL; + usb_qdss->port.function.free_func = qdss_free_func; + + return &usb_qdss->port.function; +} + +DECLARE_USB_FUNCTION(qdss, qdss_alloc_inst, qdss_alloc); +static int __init usb_qdss_init(void) +{ + int ret; + + _qdss_ipc_log = ipc_log_context_create(NUM_PAGES, "usb_qdss", 0); + if (IS_ERR_OR_NULL(_qdss_ipc_log)) + _qdss_ipc_log = NULL; + + INIT_LIST_HEAD(&usb_qdss_ch_list); + ret = usb_function_register(&qdssusb_func); + if (ret) { + pr_err("%s: failed to register diag %d\n", __func__, ret); + return ret; + } + return ret; +} + +static void __exit usb_qdss_exit(void) +{ + ipc_log_context_destroy(_qdss_ipc_log); + usb_function_unregister(&qdssusb_func); + qdss_cleanup(); +} + +module_init(usb_qdss_init); +module_exit(usb_qdss_exit); +MODULE_DESCRIPTION("USB QDSS Function Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_qdss.h b/drivers/usb/gadget/function/f_qdss.h new file mode 100644 index 000000000000..65e541603e32 --- /dev/null +++ b/drivers/usb/gadget/function/f_qdss.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _F_QDSS_H +#define _F_QDSS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum qti_port_type { + QTI_PORT_RMNET, + QTI_PORT_DPL, + QTI_NUM_PORTS +}; + +struct usb_qdss_ch { + const char *name; + struct list_head list; + void (*notify)(void *priv, unsigned int event, + struct qdss_request *d_req, struct usb_qdss_ch *ch); + void *priv; + int ch_type; +}; + +struct gqdss { + struct usb_function function; + struct usb_ep *ctrl_out; + struct usb_ep *ctrl_in; + struct usb_ep *data; + int (*send_encap_cmd)(enum qti_port_type qport, void *buf, size_t len); + void (*notify_modem)(void *g, enum qti_port_type qport, int cbits); +}; + +/* struct f_qdss - USB qdss function driver private structure */ +struct f_qdss { + struct gqdss port; + struct usb_gadget *gadget; + short int port_num; + u8 ctrl_iface_id; + u8 data_iface_id; + int usb_connected; + bool debug_inface_enabled; + struct usb_request *endless_req; + struct usb_qdss_ch ch; + + /* for mdm channel SW path */ + struct list_head data_write_pool; + struct list_head queued_data_pool; + struct list_head dequeued_data_pool; + + struct work_struct connect_w; + struct work_struct disconnect_w; + spinlock_t lock; + unsigned int data_enabled:1; + unsigned int ctrl_in_enabled:1; + unsigned int ctrl_out_enabled:1; + struct workqueue_struct *wq; + + struct mutex mutex; + bool opened; /* protected by 'mutex' */ + struct completion dequeue_done; +}; + +struct usb_qdss_opts { + struct usb_function_instance func_inst; + struct f_qdss *usb_qdss; + char *channel_name; +}; + +struct qdss_req { + struct usb_request *usb_req; + struct qdss_request *qdss_req; + struct list_head list; +}; + +int set_qdss_data_connection(struct f_qdss *qdss, int enable); +int alloc_hw_req(struct usb_ep *data_ep); +#endif diff --git a/drivers/usb/gadget/function/u_qdss.c b/drivers/usb/gadget/function/u_qdss.c new file mode 100644 index 000000000000..dda420ff2c08 --- /dev/null +++ b/drivers/usb/gadget/function/u_qdss.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include + +#include "f_qdss.h" + +#define NUM_EBC_IN_BUF 2 + +int alloc_hw_req(struct usb_ep *data_ep) +{ + struct usb_request *req = NULL; + struct f_qdss *qdss = data_ep->driver_data; + + pr_debug("allocating EBC request\n"); + + req = usb_ep_alloc_request(data_ep, GFP_ATOMIC); + if (!req) { + pr_err("usb_ep_alloc_request failed\n"); + return -ENOMEM; + } + + req->length = NUM_EBC_IN_BUF * EBC_TRB_SIZE; + qdss->endless_req = req; + + return 0; +} + +static int enable_qdss_ebc_data_connection(struct f_qdss *qdss) +{ + int ret; + + ret = msm_ep_config(qdss->port.data, qdss->endless_req, 1); + if (ret) + pr_err("msm_ep_config failed\n"); + + return ret; +} + +int set_qdss_data_connection(struct f_qdss *qdss, int enable) +{ + struct usb_gadget *gadget; + struct device *dev; + int ret = 0; + + if (!qdss) { + pr_err("%s: qdss ptr is NULL\n", __func__); + return -EINVAL; + } + + gadget = qdss->gadget; + dev = gadget->dev.parent; + + pr_debug("%s ch_type:%d\n", __func__, qdss->ch.ch_type); + if (enable) { + ret = enable_qdss_ebc_data_connection(qdss); + } else { + ret = msm_ep_unconfig(qdss->port.data); + if (ret) + pr_err("msm_ep_unconfig failed\n"); + } + + return ret; +} diff --git a/include/linux/usb/ccid_desc.h b/include/linux/usb/ccid_desc.h new file mode 100644 index 000000000000..8f381d06ca36 --- /dev/null +++ b/include/linux/usb/ccid_desc.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011, 2017 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_USB_CCID_DESC_H +#define __LINUX_USB_CCID_DESC_H + +#include + +/*CCID specification version 1.10*/ +#define CCID1_10 0x0110 + +#define SMART_CARD_DEVICE_CLASS 0x0B +/* Smart Card Device Class Descriptor Type */ +#define CCID_DECRIPTOR_TYPE 0x21 + +/* Table 5.3-1 Summary of CCID Class Specific Request */ +#define CCIDGENERICREQ_ABORT 0x01 +#define CCIDGENERICREQ_GET_CLOCK_FREQUENCIES 0x02 +#define CCIDGENERICREQ_GET_DATA_RATES 0x03 + +/* 6.1 Command Pipe, Bulk-OUT Messages */ +#define PC_TO_RDR_ICCPOWERON 0x62 +#define PC_TO_RDR_ICCPOWEROFF 0x63 +#define PC_TO_RDR_GETSLOTSTATUS 0x65 +#define PC_TO_RDR_XFRBLOCK 0x6F +#define PC_TO_RDR_GETPARAMETERS 0x6C +#define PC_TO_RDR_RESETPARAMETERS 0x6D +#define PC_TO_RDR_SETPARAMETERS 0x61 +#define PC_TO_RDR_ESCAPE 0x6B +#define PC_TO_RDR_ICCCLOCK 0x6E +#define PC_TO_RDR_T0APDU 0x6A +#define PC_TO_RDR_SECURE 0x69 +#define PC_TO_RDR_MECHANICAL 0x71 +#define PC_TO_RDR_ABORT 0x72 +#define PC_TO_RDR_SETDATARATEANDCLOCKFREQUENCY 0x73 + +/* 6.2 Response Pipe, Bulk-IN Messages */ +#define RDR_TO_PC_DATABLOCK 0x80 +#define RDR_TO_PC_SLOTSTATUS 0x81 +#define RDR_TO_PC_PARAMETERS 0x82 +#define RDR_TO_PC_ESCAPE 0x83 +#define RDR_TO_PC_DATARATEANDCLOCKFREQUENCY 0x84 + +/* 6.3 Interrupt-IN Messages */ +#define RDR_TO_PC_NOTIFYSLOTCHANGE 0x50 +#define RDR_TO_PC_HARDWAREERROR 0x51 + +/* Table 6.2-2 Slot error register when bmCommandStatus = 1 */ +#define CMD_ABORTED 0xFF +#define ICC_MUTE 0xFE +#define XFR_PARITY_ERROR 0xFD +#define XFR_OVERRUN 0xFC +#define HW_ERROR 0xFB +#define BAD_ATR_TS 0xF8 +#define BAD_ATR_TCK 0xF7 +#define ICC_PROTOCOL_NOT_SUPPORTED 0xF6 +#define ICC_CLASS_NOT_SUPPORTED 0xF5 +#define PROCEDURE_BYTE_CONFLICT 0xF4 +#define DEACTIVATED_PROTOCOL 0xF3 +#define BUSY_WITH_AUTO_SEQUENCE 0xF2 +#define PIN_TIMEOUT 0xF0 +#define PIN_CANCELLED 0xEF +#define CMD_SLOT_BUSY 0xE0 + +/* CCID rev 1.1, p.27 */ +#define VOLTS_AUTO 0x00 +#define VOLTS_5_0 0x01 +#define VOLTS_3_0 0x02 +#define VOLTS_1_8 0x03 + +/* 6.3.1 RDR_to_PC_NotifySlotChange */ +#define ICC_NOT_PRESENT 0x00 +#define ICC_PRESENT 0x01 +#define ICC_CHANGE 0x02 +#define ICC_INSERTED_EVENT (ICC_PRESENT+ICC_CHANGE) + +/* Identifies the length of type of subordinate descriptors of a CCID device + * Table 5.1-1 Smart Card Device Class descriptors + */ +struct usb_ccid_class_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u16 bcdCCID; + __u8 bMaxSlotIndex; + __u8 bVoltageSupport; + __u32 dwProtocols; + __u32 dwDefaultClock; + __u32 dwMaximumClock; + __u8 bNumClockSupported; + __u32 dwDataRate; + __u32 dwMaxDataRate; + __u8 bNumDataRatesSupported; + __u32 dwMaxIFSD; + __u32 dwSynchProtocols; + __u32 dwMechanical; + __u32 dwFeatures; + __u32 dwMaxCCIDMessageLength; + __u8 bClassGetResponse; + __u8 bClassEnvelope; + __u16 wLcdLayout; + __u8 bPINSupport; + __u8 bMaxCCIDBusySlots; +} __packed; +#endif diff --git a/include/linux/usb/usb_qdss.h b/include/linux/usb/usb_qdss.h new file mode 100644 index 000000000000..3b659e3db360 --- /dev/null +++ b/include/linux/usb/usb_qdss.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2013, 2017-2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_USB_QDSS_H +#define __LINUX_USB_QDSS_H + +#include +#include +#include + +#define USB_QDSS_CH_EBC "qdss_ebc" +#define USB_QDSS_CH_MDM "qdss_mdm" +#define USB_QDSS_CH_SW "qdss_sw" + +struct qdss_request { + char *buf; + int length; + int actual; + int status; + void *context; + struct scatterlist *sg; + unsigned int num_sgs; +}; + +struct usb_qdss_ch; + +enum qdss_state { + USB_QDSS_CONNECT, + USB_QDSS_DISCONNECT, + USB_QDSS_CTRL_READ_DONE, + USB_QDSS_DATA_WRITE_DONE, +}; + +#if IS_ENABLED(CONFIG_USB_F_QDSS) +struct usb_qdss_ch *usb_qdss_open(const char *name, void *priv, + void (*notify)(void *priv, unsigned int event, + struct qdss_request *d_req, struct usb_qdss_ch *ch)); +void usb_qdss_close(struct usb_qdss_ch *ch); +int usb_qdss_alloc_req(struct usb_qdss_ch *ch, int n_write); +void usb_qdss_free_req(struct usb_qdss_ch *ch); +int usb_qdss_write(struct usb_qdss_ch *ch, struct qdss_request *d_req); +#else +static inline struct usb_qdss_ch *usb_qdss_open(const char *name, void *priv, + void (*n)(void *, unsigned int event, + struct qdss_request *d, struct usb_qdss_ch *c)) +{ + return ERR_PTR(-ENODEV); +} + +static inline int usb_qdss_write(struct usb_qdss_ch *c, struct qdss_request *d) +{ + return -ENODEV; +} + +static inline int usb_qdss_alloc_req(struct usb_qdss_ch *c, int n_wr, int n_rd) +{ + return -ENODEV; +} + + +static inline void usb_qdss_close(struct usb_qdss_ch *ch) { } + +static inline void usb_qdss_free_req(struct usb_qdss_ch *ch) { } +#endif /* CONFIG_USB_F_QDSS */ + +#endif