summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorHemant Kumar <hemantk@codeaurora.org>2016-01-28 19:55:06 -0800
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:03:08 -0700
commit7bc7efae3f8c2b9872b7d517a14f476a8b9a0c1d (patch)
tree50afc096c2e88d01a24dffc873a5fcfaa531737f /drivers/usb
parentf8197f0801ee2d074c19a7a250f90562e52ae37f (diff)
usb: gadget: Add snapshot of GSI function driver
Adds a generic function driver that will handle all functions that support h/w acceleration to IPA over GSI. This snapshot is taken as of msm-3.18 commit 30b0c37abcd9cf0 (Merge "msm: vidc: add support to set low latency property to venus") Change-Id: I4f78a4a36727fec24b1c5481acd6d3122eeb9150 Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/gadget/function/f_gsi.c2997
-rw-r--r--drivers/usb/gadget/function/f_gsi.h1330
2 files changed, 4327 insertions, 0 deletions
diff --git a/drivers/usb/gadget/function/f_gsi.c b/drivers/usb/gadget/function/f_gsi.c
new file mode 100644
index 000000000000..b66e7cff7511
--- /dev/null
+++ b/drivers/usb/gadget/function/f_gsi.c
@@ -0,0 +1,2997 @@
+/* Copyright (c) 2015-2016, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/usb/usb_ctrl_qti.h>
+#include <linux/etherdevice.h>
+#include <linux/debugfs.h>
+#include "f_gsi.h"
+#include "rndis.h"
+#include "debug.h"
+
+static unsigned int gsi_in_aggr_size;
+module_param(gsi_in_aggr_size, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(gsi_in_aggr_size,
+ "Aggr size of bus transfer to host");
+
+static unsigned int gsi_out_aggr_size;
+module_param(gsi_out_aggr_size, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(gsi_out_aggr_size,
+ "Aggr size of bus transfer to device");
+
+static unsigned int num_in_bufs = GSI_NUM_IN_BUFFERS;
+module_param(num_in_bufs, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(num_in_bufs,
+ "Number of IN buffers");
+
+static unsigned int num_out_bufs = GSI_NUM_OUT_BUFFERS;
+module_param(num_out_bufs, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(num_out_bufs,
+ "Number of OUT buffers");
+
+static struct workqueue_struct *ipa_usb_wq;
+static bool gadget_restarted;
+
+struct usb_gsi_debugfs {
+ struct dentry *debugfs_root;
+};
+
+static struct usb_gsi_debugfs debugfs;
+
+static void ipa_disconnect_handler(struct gsi_data_port *d_port);
+static int gsi_ctrl_send_notification(struct f_gsi *gsi,
+ enum gsi_ctrl_notify_state);
+
+void post_event(struct gsi_data_port *port, u8 event)
+{
+ unsigned long flags;
+
+ 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);
+}
+
+void post_event_to_evt_queue(struct gsi_data_port *port, u8 event)
+{
+ post_event(port, event);
+ queue_work(port->ipa_usb_wq, &port->usb_ipa_w);
+}
+
+u8 read_event(struct gsi_data_port *port)
+{
+ u8 event;
+ unsigned long flags;
+
+ 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;
+}
+
+u8 peek_event(struct gsi_data_port *port)
+{
+ u8 event;
+ unsigned long flags;
+ u8 peek_index = 0;
+
+ 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;
+}
+
+void 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);
+}
+
+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) && (func->func_is_suspended)) {
+ log_event_dbg("%s: Calling usb_func_wakeup", __func__);
+ ret = usb_func_wakeup(func);
+ } 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 ssize_t usb_gsi_debugfs_read(struct file *file,
+ char __user *user_buf, size_t count, loff_t *ppos)
+{
+ char *buf;
+ unsigned int len = 0, buf_len = 4096;
+ struct f_gsi *gsi;
+ struct ipa_usb_xdci_chan_params *ipa_chnl_params;
+ struct ipa_usb_xdci_connect_params *con_pms;
+ int i = 0;
+ int j = 0;
+ ssize_t ret_cnt;
+
+ buf = kzalloc(buf_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ len += scnprintf(buf + len, buf_len - len, "%25s\n",
+ "USB GSI Info");
+ for (i = 0; i < IPA_USB_MAX_TETH_PROT_SIZE; i++) {
+ gsi = gsi_prot_ctx[i];
+ if (gsi && atomic_read(&gsi->connected)) {
+ ipa_chnl_params = &gsi->d_port.ipa_in_channel_params;
+ con_pms = &gsi->d_port.ipa_conn_pms;
+ len += scnprintf(buf + len, buf_len - len, "%55s\n",
+ "==================================================");
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10s\n", "Ctrl Name: ", gsi->c_port.name);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Notify State: ",
+ gsi->c_port.notify_state);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Notify Count: ",
+ gsi->c_port.notify_count.counter);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Ctrl Online: ",
+ gsi->c_port.ctrl_online.counter);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Ctrl Open: ",
+ gsi->c_port.is_open);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Ctrl Host to Modem: ",
+ gsi->c_port.host_to_modem);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Ctrl Modem to Host: ",
+ gsi->c_port.modem_to_host);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Ctrl Cpd to Modem: ",
+ gsi->c_port.copied_to_modem);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Ctrl Cpd From Modem: ",
+ gsi->c_port.copied_from_modem);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Ctrl Pkt Drops: ",
+ gsi->c_port.cpkt_drop_cnt);
+ len += scnprintf(buf + len, buf_len - len, "%25s\n",
+ "==============");
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Protocol ID: ", gsi->prot_id);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "SM State: ", gsi->d_port.sm_state);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "IN XferRscIndex: ",
+ gsi->d_port.in_xfer_rsc_index);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10d\n", "IN Chnl Hdl: ",
+ gsi->d_port.in_channel_handle);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "IN Chnl Dbl Addr: ",
+ gsi->d_port.in_db_reg_phs_addr_lsb);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "IN TRB Ring Len: ",
+ ipa_chnl_params->xfer_ring_len);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "IN TRB Base Addr: ", (unsigned int)
+ ipa_chnl_params->xfer_ring_base_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "GEVENTCNTLO IN Addr: ",
+ ipa_chnl_params->gevntcount_low_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "DEPCMDLO IN Addr: ",
+ ipa_chnl_params->xfer_scratch.depcmd_low_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "IN LastTRB Addr Off: ",
+ ipa_chnl_params->xfer_scratch.last_trb_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "IN Buffer Size: ",
+ ipa_chnl_params->xfer_scratch.const_buffer_size);
+ len += scnprintf(buf + len, buf_len - 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, buf_len - len, "%25s\n",
+ "==============");
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "OUT XferRscIndex: ",
+ gsi->d_port.out_xfer_rsc_index);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10d\n", "OUT Channel Hdl: ",
+ gsi->d_port.out_channel_handle);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "OUT Channel Dbl Addr: ",
+ gsi->d_port.out_db_reg_phs_addr_lsb);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "OUT TRB Ring Len: ",
+ ipa_chnl_params->xfer_ring_len);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "OUT TRB Base Addr: ", (unsigned int)
+ ipa_chnl_params->xfer_ring_base_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "GEVENTCNTLO OUT Addr: ",
+ ipa_chnl_params->gevntcount_low_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "DEPCMDLO OUT Addr: ",
+ ipa_chnl_params->xfer_scratch.depcmd_low_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10x\n", "OUT LastTRB Addr Off: ",
+ ipa_chnl_params->xfer_scratch.last_trb_addr);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "OUT Buffer Size: ",
+ ipa_chnl_params->xfer_scratch.const_buffer_size);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "OUT/UL Aggr Size: ",
+ con_pms->teth_prot_params.max_xfer_size_bytes_to_dev);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "OUT/UL Packets to dev: ",
+ con_pms->teth_prot_params.max_packet_number_to_dev);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Net_ready_trigger:",
+ gsi->d_port.net_ready_trigger);
+ len += scnprintf(buf + len, buf_len - len, "%25s\n",
+ "USB Bus Events");
+ for (j = 0; j < MAXQUEUELEN; j++)
+ len += scnprintf(buf + len, buf_len - len,
+ "%d\t", gsi->d_port.evt_q.event[j]);
+ len += scnprintf(buf + len, buf_len - len, "\n");
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Eventq head: ",
+ gsi->d_port.evt_q.head);
+ len += scnprintf(buf + len, buf_len - len,
+ "%25s %10u\n", "Eventq tail: ",
+ gsi->d_port.evt_q.tail);
+ }
+ }
+
+ if (len > buf_len)
+ len = buf_len;
+
+ ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+ kfree(buf);
+
+ return ret_cnt;
+}
+
+static const struct file_operations fops_usb_gsi = {
+ .read = usb_gsi_debugfs_read,
+ .open = simple_open,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+
+static int usb_gsi_debugfs_init(void)
+{
+ debugfs.debugfs_root = debugfs_create_dir("usb_gsi", 0);
+ if (!debugfs.debugfs_root)
+ return -ENOMEM;
+
+ debugfs_create_file("info", S_IRUSR, debugfs.debugfs_root,
+ gsi_prot_ctx, &fops_usb_gsi);
+ return 0;
+}
+
+void usb_gsi_debugfs_exit(void)
+{
+ debugfs_remove_recursive(debugfs.debugfs_root);
+}
+
+/*
+ * Callback for when when network interface is up
+ * and userspace is ready to answer DHCP requests, or remote wakeup
+ */
+int ipa_usb_notify_cb(enum ipa_usb_notify_event event,
+ void *driver_data)
+{
+ struct f_gsi *gsi = driver_data;
+ unsigned long flags;
+
+ 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) {
+ log_event_err("%s: Already triggered", __func__);
+ spin_unlock_irqrestore(&gsi->d_port.lock, flags);
+ 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)
+ gsi_ctrl_send_notification(gsi,
+ GSI_CTRL_NOTIFY_CONNECT);
+
+ /* 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_CONNECTED);
+ queue_work(gsi->d_port.ipa_usb_wq,
+ &gsi->d_port.usb_ipa_w);
+ }
+ 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_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+ 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;
+
+ usb_gsi_ep_op(d_port->in_ep, &d_port->in_request,
+ GSI_EP_OP_PREPARE_TRBS);
+ usb_gsi_ep_op(d_port->in_ep, &d_port->in_request,
+ GSI_EP_OP_STARTXFER);
+ 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);
+ 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_ring_base_addr = gsi_channel_info.xfer_ring_base_addr;
+ in_params->xfer_scratch.last_trb_addr =
+ d_port->in_last_trb_addr = gsi_channel_info.last_trb_addr;
+ 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) {
+ usb_gsi_ep_op(d_port->out_ep, &d_port->out_request,
+ GSI_EP_OP_PREPARE_TRBS);
+ usb_gsi_ep_op(d_port->out_ep, &d_port->out_request,
+ GSI_EP_OP_STARTXFER);
+ 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);
+
+ 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 =
+ gsi_channel_info.xfer_ring_base_addr;
+ out_params->xfer_scratch.last_trb_addr =
+ 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;
+ if (gsi_out_aggr_size)
+ conn_params->teth_prot_params.max_xfer_size_bytes_to_dev
+ = gsi_out_aggr_size;
+ else
+ conn_params->teth_prot_params.max_xfer_size_bytes_to_dev
+ = d_port->out_aggr_size;
+ if (gsi_in_aggr_size)
+ conn_params->teth_prot_params.max_xfer_size_bytes_to_host
+ = gsi_in_aggr_size;
+ else
+ 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);
+ return ret;
+ }
+ 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_db_reg_phs_addr_lsb =
+ ipa_in_channel_out_params.db_reg_phs_addr_lsb;
+ d_port->in_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_db_reg_phs_addr_lsb =
+ ipa_out_channel_out_params.db_reg_phs_addr_lsb;
+ d_port->out_db_reg_phs_addr_msb =
+ ipa_out_channel_out_params.db_reg_phs_addr_msb;
+ }
+ return ret;
+}
+
+static void ipa_data_path_enable(struct gsi_data_port *d_port)
+{
+ struct f_gsi *gsi = d_port_to_gsi(d_port);
+ struct usb_gsi_request req;
+ u64 dbl_register_addr;
+ bool block_db = false;
+
+
+ log_event_dbg("in_db_reg_phs_addr_lsb = %x",
+ gsi->d_port.in_db_reg_phs_addr_lsb);
+ usb_gsi_ep_op(gsi->d_port.in_ep,
+ (void *)&gsi->d_port.in_db_reg_phs_addr_lsb,
+ 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_db_reg_phs_addr_lsb);
+ usb_gsi_ep_op(gsi->d_port.out_ep,
+ (void *)&gsi->d_port.out_db_reg_phs_addr_lsb,
+ 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);
+
+ dbl_register_addr = gsi->d_port.in_db_reg_phs_addr_msb;
+ dbl_register_addr = dbl_register_addr << 32;
+ dbl_register_addr =
+ dbl_register_addr | gsi->d_port.in_db_reg_phs_addr_lsb;
+
+ /* use temp gsi request to pass 64 bit dbl reg addr and num_bufs */
+ req.buf_base_addr = &dbl_register_addr;
+
+ req.num_bufs = gsi->d_port.in_request.num_bufs;
+ usb_gsi_ep_op(gsi->d_port.in_ep, &req, GSI_EP_OP_RING_IN_DB);
+
+ if (gsi->d_port.out_ep) {
+ usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request,
+ GSI_EP_OP_UPDATEXFER);
+ }
+}
+
+static void ipa_disconnect_handler(struct gsi_data_port *d_port)
+{
+ struct f_gsi *gsi = d_port_to_gsi(d_port);
+ bool block_db = true;
+
+ log_event_dbg("%s: EP Disable for data", __func__);
+
+ /* Block doorbell to GSI to avoid USB wrapper from
+ * ringing doorbell in case IPA clocks are OFF
+ */
+ usb_gsi_ep_op(d_port->in_ep, (void *)&block_db,
+ GSI_EP_OP_SET_CLR_BLOCK_DBL);
+
+ usb_ep_disable(gsi->d_port.in_ep);
+
+ if (gsi->d_port.out_ep)
+ usb_ep_disable(gsi->d_port.out_ep);
+ gsi->d_port.net_ready_trigger = false;
+}
+
+static void ipa_disconnect_work_handler(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;
+
+ usb_gsi_ep_op(gsi->d_port.in_ep, NULL, GSI_EP_OP_FREE_TRBS);
+
+ if (gsi->d_port.out_ep)
+ usb_gsi_ep_op(gsi->d_port.out_ep, NULL, GSI_EP_OP_FREE_TRBS);
+
+ /*
+ * Unconfig the gsi eps after freeing the trbs. If done in
+ * gsi_disable() then since gsi_disable() is called in interrupt context
+ * and the usb_gsi_ep_op() for GSI_EP_OP_FREE_TRBS which is called from
+ * ipa_disconnect_work_handler() a worker thread, can get delayed. So
+ * when gsi_disable() unconfigures the eps, usb_gsi_ep_op() will not be
+ * executed which leads to a memory leak.
+ * Also if this is done in gsi_unbind() then again this is executed in
+ * interrupt context and ipa_disconnect_work_handler() is a worker
+ * thread which can get delayed.
+ */
+ if (gadget_is_dwc3(d_port->gadget)) {
+ if (gsi->d_port.in_ep)
+ msm_ep_unconfig(gsi->d_port.in_ep);
+ if (gsi->d_port.out_ep)
+ msm_ep_unconfig(gsi->d_port.out_ep);
+ }
+
+}
+
+static int ipa_suspend_work_handler(struct gsi_data_port *d_port)
+{
+ int ret = 0;
+ bool block_db;
+ struct f_gsi *gsi = d_port_to_gsi(d_port);
+
+ if (!usb_gsi_ep_op(gsi->d_port.in_ep, NULL,
+ GSI_EP_OP_CHECK_FOR_SUSPEND)) {
+ ret = -EFAULT;
+ 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);
+
+ 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);
+ }
+
+ log_event_dbg("%s: xdci_suspend ret %d", __func__, ret);
+
+done:
+ 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 void ipa_work_handler(struct work_struct *w)
+{
+ struct gsi_data_port *d_port = container_of(w, struct gsi_data_port,
+ usb_ipa_w);
+ u8 event;
+ int ret = 0;
+ struct device *gad_dev = &d_port->gadget->dev;
+
+ event = read_event(d_port);
+
+ log_event_dbg("%s: event = %x sm_state %x", __func__,
+ event, d_port->sm_state);
+
+ switch (d_port->sm_state) {
+ case STATE_UNINITIALIZED:
+ if (event == EVT_INITIALIZED) {
+ d_port->sm_state = STATE_INITIALIZED;
+ log_event_dbg("%s: ST_INIT_EVT_INIT", __func__);
+ }
+ break;
+ case STATE_INITIALIZED:
+ if (event == EVT_CONNECT_IN_PROGRESS) {
+ ipa_connect_channels(d_port);
+ d_port->sm_state = STATE_CONNECT_IN_PROGRESS;
+ log_event_dbg("%s: ST_INIT_EVT_CONN_IN_PROG",
+ __func__);
+ }
+ break;
+ case STATE_CONNECT_IN_PROGRESS:
+ if (event == EVT_HOST_READY) {
+ ipa_data_path_enable(d_port);
+ d_port->sm_state = STATE_CONNECTED;
+ log_event_dbg("%s: ST_CON_IN_PROG_EVT_HOST_READY",
+ __func__);
+ } else if (event == EVT_CONNECTED) {
+ ipa_data_path_enable(d_port);
+ d_port->sm_state = STATE_CONNECTED;
+ log_event_dbg("%s: ST_CON_IN_PROG_EVT_CON %d",
+ __func__, __LINE__);
+ } else if (event == EVT_SUSPEND) {
+ if (peek_event(d_port) == EVT_DISCONNECTED) {
+ read_event(d_port);
+ ipa_disconnect_work_handler(d_port);
+ d_port->sm_state = STATE_INITIALIZED;
+ usb_gadget_autopm_put_async(d_port->gadget);
+ log_event_dbg("%s: ST_CON_IN_PROG_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_CON_IN_PROG_EVT_SUS",
+ __func__);
+ log_event_dbg("%s: put_async2 = %d", __func__,
+ atomic_read(
+ &gad_dev->power.usage_count));
+ }
+ } else if (event == EVT_DISCONNECTED) {
+ ipa_disconnect_work_handler(d_port);
+ d_port->sm_state = STATE_INITIALIZED;
+ usb_gadget_autopm_put_async(d_port->gadget);
+ log_event_dbg("%s: ST_CON_IN_PROG_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) {
+ ipa_disconnect_work_handler(d_port);
+ d_port->sm_state = STATE_INITIALIZED;
+ usb_gadget_autopm_put_async(d_port->gadget);
+ log_event_dbg("%s: ST_CON_EVT_DIS", __func__);
+ 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) {
+ ipa_disconnect_work_handler(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));
+ }
+ } else if (event == EVT_CONNECTED) {
+ d_port->sm_state = STATE_CONNECTED;
+ log_event_dbg("%s: ST_CON_EVT_CON", __func__);
+ }
+ break;
+ case STATE_DISCONNECTED:
+ if (event == EVT_CONNECT_IN_PROGRESS) {
+ ipa_connect_channels(d_port);
+ d_port->sm_state = STATE_CONNECT_IN_PROGRESS;
+ log_event_dbg("%s: ST_DIS_EVT_CON_IN_PROG", __func__);
+ } else if (event == EVT_UNINITIALIZED) {
+ d_port->sm_state = STATE_UNINITIALIZED;
+ log_event_dbg("%s: ST_DIS_EVT_UNINIT", __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_disconnect_work_handler(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) {
+ 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 handshake 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_EVT_RES", __func__);
+ log_event_dbg("%s: get_nores2 = %d", __func__,
+ atomic_read(
+ &gad_dev->power.usage_count));
+ } else if (event == EVT_DISCONNECTED) {
+ ipa_disconnect_work_handler(d_port);
+ d_port->sm_state = STATE_INITIALIZED;
+ log_event_dbg("%s: ST_SUS_EVT_DIS", __func__);
+ }
+ 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_work(d_port->ipa_usb_wq, &d_port->usb_ipa_w);
+ }
+}
+
+static struct gsi_ctrl_pkt *gsi_ctrl_pkt_alloc(unsigned 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;
+
+ spin_lock(&gsi->c_port.lock);
+ 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(&gsi->c_port.lock);
+}
+
+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(fp->private_data,
+ struct gsi_ctrl_port,
+ ctrl_device);
+
+ if (!c_port) {
+ log_event_err("%s: gsi ctrl port %p", __func__, c_port);
+ return -ENODEV;
+ }
+
+ log_event_dbg("%s: open ctrl dev %s", __func__, c_port->name);
+
+ if (c_port->is_open) {
+ log_event_err("%s: Already opened", __func__);
+ return -EBUSY;
+ }
+
+ c_port->is_open = true;
+
+ return 0;
+}
+
+static int gsi_ctrl_dev_release(struct inode *ip, struct file *fp)
+{
+ struct gsi_ctrl_port *c_port = container_of(fp->private_data,
+ struct gsi_ctrl_port,
+ ctrl_device);
+
+ if (!c_port) {
+ log_event_err("%s: gsi ctrl port %p", __func__, c_port);
+ return -ENODEV;
+ }
+
+ log_event_dbg("close ctrl dev %s", c_port->name);
+
+ c_port->is_open = false;
+
+ 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 = container_of(fp->private_data,
+ struct gsi_ctrl_port,
+ ctrl_device);
+
+ struct gsi_ctrl_pkt *cpkt = NULL;
+ unsigned long flags;
+ int ret = 0;
+
+ log_event_dbg("%s: Enter %zu", __func__, count);
+
+ if (!c_port) {
+ log_event_err("%s: gsi ctrl port %p", __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);
+
+ 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 = container_of(fp->private_data,
+ struct gsi_ctrl_port,
+ ctrl_device);
+ struct f_gsi *gsi = c_port_to_gsi(c_port);
+ struct usb_request *req = c_port->notify_req;
+
+ log_event_dbg("Enter %zu", count);
+
+ if (!c_port || !req || !req->buf) {
+ log_event_err("%s: c_port %p req %p req->buf %p",
+ __func__, c_port, req, req ? req->buf : req);
+ return -ENODEV;
+ }
+
+ 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->function.func_is_suspended &&
+ !gsi->function.func_wakeup_allowed) {
+ c_port->cpkt_drop_cnt++;
+ log_event_err("drop ctrl pkt of len %zu", count);
+ return -ENOTSUPP;
+ }
+
+ 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;
+ }
+ c_port->copied_from_modem++;
+
+ spin_lock_irqsave(&c_port->lock, flags);
+ list_add_tail(&cpkt->list, &c_port->cpkt_resp_q);
+ spin_unlock_irqrestore(&c_port->lock, flags);
+
+ ret = gsi_ctrl_send_notification(gsi,
+ GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE);
+
+ 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 cmd,
+ unsigned long arg)
+{
+ struct gsi_ctrl_port *c_port = container_of(fp->private_data,
+ struct gsi_ctrl_port,
+ ctrl_device);
+ struct f_gsi *gsi = c_port_to_gsi(c_port);
+ struct ep_info info;
+ int val, ret = 0;
+
+ if (!c_port) {
+ log_event_err("%s: gsi ctrl port %p", __func__, c_port);
+ return -ENODEV;
+ }
+
+ 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_send_notification(gsi, GSI_CTRL_NOTIFY_OFFLINE);
+ gsi_ctrl_clear_cpkt_queues(gsi, true);
+ 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);
+ 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__,
+ atomic_read(&gsi->connected), 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;
+ default:
+ log_event_err("wrong parameter");
+ ret = -EINVAL;
+ }
+
+exit_ioctl:
+ return ret;
+}
+
+static unsigned int gsi_ctrl_dev_poll(struct file *fp, poll_table *wait)
+{
+ struct gsi_ctrl_port *c_port = container_of(fp->private_data,
+ struct gsi_ctrl_port,
+ ctrl_device);
+ unsigned long flags;
+ unsigned int mask = 0;
+
+ if (!c_port) {
+ log_event_err("%s: gsi ctrl port %p", __func__, c_port);
+ return -ENODEV;
+ }
+
+ poll_wait(fp, &c_port->read_wq, wait);
+
+ spin_lock_irqsave(&c_port->lock, flags);
+ if (!list_empty(&c_port->cpkt_req_q)) {
+ mask |= POLLIN | POLLRDNORM;
+ log_event_dbg("%s sets POLLIN for %s", __func__, c_port->name);
+ }
+ spin_unlock_irqrestore(&c_port->lock, flags);
+
+ 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;
+}
+
+int gsi_function_ctrl_port_init(enum ipa_usb_teth_prot prot_id)
+{
+ int ret;
+ int sz = GSI_CTRL_NAME_LEN;
+ bool ctrl_dev_create = true;
+ struct f_gsi *gsi = gsi_prot_ctx[prot_id];
+
+ 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 (prot_id == IPA_USB_RMNET)
+ strlcat(gsi->c_port.name, GSI_RMNET_CTRL_NAME, sz);
+ else if (prot_id == IPA_USB_MBIM)
+ strlcat(gsi->c_port.name, GSI_MBIM_CTRL_NAME, sz);
+ else if (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;
+
+ gsi->c_port.ctrl_device.name = gsi->c_port.name;
+ gsi->c_port.ctrl_device.fops = &gsi_ctrl_dev_fops;
+ gsi->c_port.ctrl_device.minor = MISC_DYNAMIC_MINOR;
+
+ ret = misc_register(&gsi->c_port.ctrl_device);
+ if (ret) {
+ log_event_err("%s: misc register failed prot id %d",
+ __func__, prot_id);
+ return ret;
+ }
+
+ return 0;
+}
+
+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 *rndis)
+{
+ struct usb_composite_dev *cdev = rndis->function.config->cdev;
+
+ log_event_dbg("%s", __func__);
+
+ rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3,
+ gsi_xfer_bitrate(cdev->gadget) / 100);
+ rndis_signal_connect(rndis->config);
+}
+
+void gsi_rndis_ipa_reset_trigger(void)
+{
+ struct f_gsi *rndis = gsi_prot_ctx[IPA_USB_RNDIS];
+ unsigned long flags;
+
+ if (!rndis) {
+ log_event_err("%s: gsi prot ctx is %p", __func__, rndis);
+ return;
+ }
+
+ spin_lock_irqsave(&rndis->d_port.lock, flags);
+ if (!rndis) {
+ log_event_err("%s: No RNDIS instance", __func__);
+ spin_unlock_irqrestore(&rndis->d_port.lock, flags);
+ return;
+ }
+
+ rndis->d_port.net_ready_trigger = false;
+ spin_unlock_irqrestore(&rndis->d_port.lock, flags);
+}
+
+void gsi_rndis_flow_ctrl_enable(bool enable)
+{
+ struct f_gsi *rndis = gsi_prot_ctx[IPA_USB_RNDIS];
+ struct gsi_data_port *d_port;
+
+ if (!rndis) {
+ log_event_err("%s: gsi prot ctx is %p", __func__, rndis);
+ return;
+ }
+
+ d_port = &rndis->d_port;
+
+ if (enable) {
+ gsi_rndis_ipa_reset_trigger();
+ 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);
+ post_event(d_port, EVT_DISCONNECTED);
+ } else {
+ post_event(d_port, EVT_HOST_READY);
+ }
+
+ queue_work(rndis->d_port.ipa_usb_wq, &rndis->d_port.usb_ipa_w);
+}
+
+/*
+ * This function handles the Microsoft-specific OS descriptor control
+ * requests that are issued by Windows host drivers to determine the
+ * configuration containing the MBIM function.
+ *
+ * This function handles two specific device requests,
+ * and only when a configuration has not yet been selected.
+ */
+static int gsi_os_desc_ctrlrequest(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl)
+{
+ 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);
+
+ /* only respond to OS desc when no configuration selected */
+ if (cdev->config ||
+ !mbim_gsi_ext_config_desc.function.subCompatibleID[0])
+ return value;
+
+ log_event_dbg("%02x.%02x v%04x i%04x l%u",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+
+ /* Handle MSFT OS string */
+ if (ctrl->bRequestType ==
+ (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
+ && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+ && (w_value >> 8) == USB_DT_STRING
+ && (w_value & 0xFF) == GSI_MBIM_OS_STRING_ID) {
+
+ value = (w_length < sizeof(mbim_gsi_os_string) ?
+ w_length : sizeof(mbim_gsi_os_string));
+ memcpy(cdev->req->buf, mbim_gsi_os_string, value);
+
+ } else if (ctrl->bRequestType ==
+ (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE)
+ && ctrl->bRequest == MBIM_VENDOR_CODE && w_index == 4) {
+
+ /* Handle Extended OS descriptor */
+ value = (w_length < sizeof(mbim_gsi_ext_config_desc) ?
+ w_length : sizeof(mbim_gsi_ext_config_desc));
+ memcpy(cdev->req->buf, &mbim_gsi_ext_config_desc, value);
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ int rc;
+
+ cdev->req->zero = value < w_length;
+ cdev->req->length = value;
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ log_event_err("response queue error: %d", rc);
+ }
+ return value;
+}
+
+static int queue_notification_request(struct f_gsi *gsi)
+{
+ int ret;
+ unsigned long flags;
+ struct usb_cdc_notification *event;
+ struct gsi_ctrl_pkt *cpkt;
+
+ ret = usb_func_ep_queue(&gsi->function, gsi->c_port.notify,
+ gsi->c_port.notify_req, GFP_ATOMIC);
+ if (ret == -ENOTSUPP || (ret < 0 && ret != -EAGAIN)) {
+ spin_lock_irqsave(&gsi->c_port.lock, flags);
+ /* check if device disconnected while we dropped lock */
+ if (atomic_read(&gsi->connected) &&
+ !list_empty(&gsi->c_port.cpkt_resp_q)) {
+ cpkt = list_first_entry(&gsi->c_port.cpkt_resp_q,
+ struct gsi_ctrl_pkt, list);
+ list_del(&cpkt->list);
+ atomic_dec(&gsi->c_port.notify_count);
+ log_event_err("%s: drop ctrl pkt of len %d error %d",
+ __func__, cpkt->len, ret);
+ gsi_ctrl_pkt_free(cpkt);
+ }
+ gsi->c_port.cpkt_drop_cnt++;
+ spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+ } else {
+ ret = 0;
+ event = gsi->c_port.notify_req->buf;
+ log_event_dbg("%s: Queued Notify type %02x", __func__,
+ event->bNotificationType);
+ }
+
+ return ret;
+}
+static int gsi_ctrl_send_notification(struct f_gsi *gsi,
+ enum gsi_ctrl_notify_state state)
+{
+ __le32 *data;
+ struct usb_cdc_notification *event;
+ struct usb_request *req = gsi->c_port.notify_req;
+ struct usb_composite_dev *cdev = gsi->function.config->cdev;
+
+ if (!atomic_read(&gsi->connected)) {
+ log_event_dbg("%s: cable disconnect", __func__);
+ return -ENODEV;
+ }
+
+ event = req->buf;
+
+ switch (state) {
+ case GSI_CTRL_NOTIFY_NONE:
+ if (atomic_read(&gsi->c_port.notify_count) > 0)
+ log_event_dbg("GSI_CTRL_NOTIFY_NONE %d",
+ atomic_read(&gsi->c_port.notify_count));
+ else
+ log_event_dbg("No pending notifications");
+ return 0;
+ case GSI_CTRL_NOTIFY_CONNECT:
+ event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+ event->wValue = cpu_to_le16(1);
+ event->wLength = cpu_to_le16(0);
+ gsi->c_port.notify_state = GSI_CTRL_NOTIFY_SPEED;
+ break;
+ case GSI_CTRL_NOTIFY_SPEED:
+ 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));
+ gsi->c_port.notify_state = GSI_CTRL_NOTIFY_NONE;
+ break;
+ case GSI_CTRL_NOTIFY_OFFLINE:
+ event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+ event->wValue = cpu_to_le16(0);
+ event->wLength = cpu_to_le16(0);
+ gsi->c_port.notify_state = GSI_CTRL_NOTIFY_NONE;
+ 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);
+ gsi->c_port.notify_state = GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE;
+
+ if (gsi->prot_id == IPA_USB_RNDIS) {
+ data = req->buf;
+ data[0] = cpu_to_le32(1);
+ data[1] = cpu_to_le32(0);
+ }
+ break;
+ default:
+ log_event_err("%s:unknown notify state", __func__);
+ return -EINVAL;
+ }
+
+ log_event_dbg("send Notify type %02x", event->bNotificationType);
+
+ if (atomic_inc_return(&gsi->c_port.notify_count) != 1) {
+ log_event_dbg("delay ep_queue: notify req is busy %d",
+ atomic_read(&gsi->c_port.notify_count));
+ return 0;
+ }
+
+ 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;
+
+ switch (status) {
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ /* connection gone */
+ gsi->c_port.notify_state = GSI_CTRL_NOTIFY_NONE;
+ atomic_set(&gsi->c_port.notify_count, 0);
+ 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:
+ /*
+ * handle multiple pending resp available
+ * notifications by queuing same until we're done,
+ * rest of the notification require queuing new
+ * request.
+ */
+ if (!atomic_dec_and_test(&gsi->c_port.notify_count)) {
+ log_event_dbg("notify_count = %d",
+ atomic_read(&gsi->c_port.notify_count));
+ queue_notification_request(gsi);
+ } else if (gsi->c_port.notify_state != GSI_CTRL_NOTIFY_NONE &&
+ gsi->c_port.notify_state !=
+ GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE) {
+ gsi_ctrl_send_notification(gsi,
+ gsi->c_port.notify_state);
+ }
+ break;
+ }
+}
+
+static void gsi_rndis_response_available(void *_rndis)
+{
+ struct f_gsi *gsi = _rndis;
+
+ gsi_ctrl_send_notification(gsi, GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE);
+}
+
+static void gsi_rndis_command_complete(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct f_gsi *rndis = req->context;
+ int status;
+
+ status = rndis_msg_parser(rndis->config, (u8 *) req->buf);
+ if (status < 0)
+ log_event_err("RNDIS command error %d, %d/%d",
+ status, req->actual, req->length);
+}
+
+static void
+gsi_ctrl_set_ntb_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ /* now for SET_NTB_INPUT_SIZE only */
+ unsigned in_size = 0;
+ struct f_gsi *gsi = req->context;
+ struct gsi_ntb_info *ntb = NULL;
+
+ log_event_dbg("dev:%p", 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;
+
+ gsi_ctrl_send_cpkt_tomodem(gsi, req->buf, req->actual);
+}
+
+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 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;
+
+ 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;
+ 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) {
+ /* return the result */
+ buf = rndis_get_next_response(gsi->config, &n);
+ if (buf) {
+ memcpy(req->buf, buf, n);
+ rndis_free_response(gsi->config, 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);
+ spin_unlock(&gsi->c_port.lock);
+
+ value = min_t(unsigned, w_length, cpkt->len);
+ memcpy(req->buf, cpkt->buf, value);
+ gsi_ctrl_pkt_free(cpkt);
+
+ 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:
+ log_event_dbg("%s: USB_CDC_REQ_SET_CONTROL_LINE_STATE DTR:%d\n",
+ __func__, w_value & ACM_CTRL_DTR ? 1 : 0);
+ 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->context = gsi;
+ 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);
+ }
+
+ /* 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 intf)
+{
+ struct f_gsi *gsi = func_to_gsi(f);
+
+ if (intf == gsi->ctrl_id)
+ 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 intf, unsigned alt)
+{
+ struct f_gsi *gsi = func_to_gsi(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct net_device *net;
+ int ret;
+
+ 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) {
+ if (!gadget_is_dwc3(cdev->gadget))
+ goto notify_ep_disable;
+
+ 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 && !gsi->d_port.in_ep->desc
+ && config_ep_by_speed(cdev->gadget, f,
+ gsi->d_port.in_ep)) {
+ gsi->d_port.in_ep->desc = NULL;
+ goto notify_ep_disable;
+ }
+
+ if (gsi->d_port.out_ep && !gsi->d_port.out_ep->desc
+ && config_ep_by_speed(cdev->gadget, f,
+ gsi->d_port.out_ep)) {
+ gsi->d_port.out_ep->desc = NULL;
+ goto notify_ep_disable;
+ }
+
+ if (gsi->d_port.in_ep &&
+ msm_ep_config(gsi->d_port.in_ep)) {
+ log_event_err("%s: in ep config failed",
+ __func__);
+ goto notify_ep_disable;
+ }
+
+ if (gsi->d_port.out_ep &&
+ msm_ep_config(gsi->d_port.out_ep)) {
+ log_event_err("%s: out ep config failed",
+ __func__);
+ goto in_ep_unconfig;
+ }
+
+ /* Configure EPs for GSI */
+ if (gsi->d_port.in_ep) {
+ if (gsi->prot_id == IPA_USB_DIAG)
+ gsi->d_port.in_ep->ep_intr_num = 3;
+ else
+ gsi->d_port.in_ep->ep_intr_num = 2;
+ usb_gsi_ep_op(gsi->d_port.in_ep,
+ &gsi->d_port.in_request,
+ GSI_EP_OP_CONFIG);
+ }
+
+ if (gsi->d_port.out_ep) {
+ gsi->d_port.out_ep->ep_intr_num = 1;
+ usb_gsi_ep_op(gsi->d_port.out_ep,
+ &gsi->d_port.out_request,
+ GSI_EP_OP_CONFIG);
+ }
+
+ gsi->d_port.gadget = cdev->gadget;
+
+ if (gsi->prot_id == IPA_USB_RNDIS) {
+ gsi_rndis_open(gsi);
+ net = gsi_rndis_get_netdev("rndis0");
+ if (IS_ERR(net))
+ goto out_ep_unconfig;
+
+ log_event_dbg("RNDIS RX/TX early activation");
+ gsi->d_port.cdc_filter = 0;
+ rndis_set_param_dev(gsi->config, net,
+ &gsi->d_port.cdc_filter);
+ }
+
+ if (gsi->prot_id == IPA_USB_ECM)
+ gsi->d_port.cdc_filter = DEFAULT_FILTER;
+
+ /*
+ * Increment usage count upon cable connect. Decrement
+ * after IPA disconnect is done in disconnect work
+ * (due to cable disconnect) or in suspend work.
+ */
+ usb_gadget_autopm_get_noresume(gsi->d_port.gadget);
+
+ post_event(&gsi->d_port, EVT_CONNECT_IN_PROGRESS);
+ queue_work(gsi->d_port.ipa_usb_wq,
+ &gsi->d_port.usb_ipa_w);
+ }
+ 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))) {
+ ipa_disconnect_handler(&gsi->d_port);
+ if (gsi->data_interface_up) {
+ if ((gsi->d_port.in_ep &&
+ msm_ep_unconfig(gsi->d_port.in_ep)) ||
+ (gsi->d_port.out_ep &&
+ msm_ep_unconfig(gsi->d_port.out_ep))) {
+ log_event_err("ep_unconfig failed");
+ goto notify_ep_disable;
+ }
+ }
+ }
+ 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);
+
+ return 0;
+
+out_ep_unconfig:
+ if (gsi->d_port.out_ep)
+ msm_ep_unconfig(gsi->d_port.out_ep);
+in_ep_unconfig:
+ if (gsi->d_port.in_ep)
+ msm_ep_unconfig(gsi->d_port.in_ep);
+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->config);
+
+ /* 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->c_port.notify_state = GSI_CTRL_NOTIFY_NONE;
+ }
+
+ atomic_set(&gsi->c_port.notify_count, 0);
+
+ 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);
+
+ /* 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;
+
+ log_event_dbg("%s deactivated", gsi->function.name);
+ ipa_disconnect_handler(&gsi->d_port);
+ post_event(&gsi->d_port, EVT_DISCONNECTED);
+ queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+}
+
+static void gsi_suspend(struct usb_function *f)
+{
+ bool block_db;
+ struct f_gsi *gsi = func_to_gsi(f);
+ bool remote_wakeup_allowed;
+
+ /* Check if function is already suspended in gsi_func_suspend() */
+ if (f->func_is_suspended) {
+ log_event_dbg("%s: func already suspended, return\n", __func__);
+ return;
+ }
+
+ if (f->config->cdev->gadget->speed == USB_SPEED_SUPER)
+ remote_wakeup_allowed = f->func_wakeup_allowed;
+ else
+ remote_wakeup_allowed = f->config->cdev->gadget->remote_wakeup;
+
+ log_event_info("%s: remote_wakeup_allowed %d",
+ __func__, remote_wakeup_allowed);
+
+ if (!remote_wakeup_allowed) {
+ if (gsi->prot_id == IPA_USB_RNDIS)
+ rndis_flow_control(gsi->config, true);
+ /*
+ * When remote wakeup is disabled, IPA is disconnected
+ * because it cannot send new data until the USB bus is
+ * resumed. Endpoint descriptors info is saved before it
+ * gets reset by the BAM disconnect API. This lets us
+ * restore this info when the USB bus is resumed.
+ */
+ if (gsi->d_port.in_ep)
+ gsi->in_ep_desc_backup = gsi->d_port.in_ep->desc;
+ if (gsi->d_port.out_ep)
+ gsi->out_ep_desc_backup = gsi->d_port.out_ep->desc;
+
+ ipa_disconnect_handler(&gsi->d_port);
+
+ post_event(&gsi->d_port, EVT_DISCONNECTED);
+ queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+ log_event_dbg("%s: Disconnecting", __func__);
+ } else {
+ 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_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+ }
+
+ log_event_dbg("gsi suspended");
+}
+
+static void gsi_resume(struct usb_function *f)
+{
+ struct f_gsi *gsi = func_to_gsi(f);
+ bool remote_wakeup_allowed;
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ log_event_dbg("%s", __func__);
+
+ /*
+ * 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) &&
+ f->func_is_suspended)
+ return;
+
+ if (f->config->cdev->gadget->speed == USB_SPEED_SUPER)
+ remote_wakeup_allowed = f->func_wakeup_allowed;
+ else
+ remote_wakeup_allowed = f->config->cdev->gadget->remote_wakeup;
+
+ if (!remote_wakeup_allowed) {
+ gsi->d_port.in_ep->desc = gsi->in_ep_desc_backup;
+ gsi->d_port.out_ep->desc = gsi->out_ep_desc_backup;
+
+ /* Configure EPs for GSI */
+ gsi->d_port.out_ep->ep_intr_num = 1;
+ usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request,
+ GSI_EP_OP_CONFIG);
+ gsi->d_port.in_ep->ep_intr_num = 2;
+ usb_gsi_ep_op(gsi->d_port.in_ep, &gsi->d_port.in_request,
+ GSI_EP_OP_CONFIG);
+ post_event(&gsi->d_port, EVT_CONNECT_IN_PROGRESS);
+
+ /*
+ * Linux host does not send RNDIS_MSG_INIT or non-zero
+ * RNDIS_MESSAGE_PACKET_FILTER after performing bus resume.
+ * Trigger state machine explicitly on resume.
+ */
+ if (gsi->prot_id == IPA_USB_RNDIS)
+ rndis_flow_control(gsi->config, false);
+ } else
+ post_event(&gsi->d_port, EVT_RESUMED);
+
+ queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+
+ if (!gsi->c_port.notify->desc)
+ config_ep_by_speed(cdev->gadget, f, gsi->c_port.notify);
+
+ atomic_set(&gsi->c_port.notify_count, 0);
+ log_event_dbg("%s: completed", __func__);
+}
+
+static int gsi_func_suspend(struct usb_function *f, u8 options)
+{
+ bool func_wakeup_allowed;
+
+ log_event_dbg("func susp %u cmd for %s",
+ options, f->name ? f->name : "");
+
+ func_wakeup_allowed =
+ ((options & FUNC_SUSPEND_OPT_RW_EN_MASK) != 0);
+
+ if (options & FUNC_SUSPEND_OPT_SUSP_MASK) {
+ f->func_wakeup_allowed = func_wakeup_allowed;
+ if (!f->func_is_suspended) {
+ gsi_suspend(f);
+ f->func_is_suspended = true;
+ }
+ } else {
+ if (f->func_is_suspended) {
+ f->func_is_suspended = false;
+ gsi_resume(f);
+ }
+ f->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;
+ u32 len = 0;
+ int status;
+
+ /* maybe allocate device-global string IDs */
+ if (info->string_defs[0].id != 0)
+ goto skip_string_id_alloc;
+
+ 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;
+ }
+
+skip_string_id_alloc:
+ 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;
+ 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;
+ 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 */
+
+ atomic_set(&gsi->c_port.notify_count, 0);
+
+ /* 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->c_port.notify_state = GSI_CTRL_NOTIFY_NONE;
+ }
+
+ gsi->d_port.in_request.buf_len = info->in_req_buf_len;
+ gsi->d_port.in_request.num_bufs = info->in_req_num_buf;
+ len = gsi->d_port.in_request.buf_len * gsi->d_port.in_request.num_bufs;
+ dev_dbg(&cdev->gadget->dev, "%zu %zu\n", gsi->d_port.in_request.buf_len,
+ gsi->d_port.in_request.num_bufs);
+ gsi->d_port.in_request.buf_base_addr =
+ dma_zalloc_coherent(&cdev->gadget->dev, len,
+ &gsi->d_port.in_request.dma, GFP_KERNEL);
+ if (!gsi->d_port.in_request.buf_base_addr) {
+ dev_err(&cdev->gadget->dev,
+ "IN buf_base_addr allocate failed %s\n",
+ gsi->function.name);
+ goto fail;
+ }
+
+ 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;
+ len =
+ gsi->d_port.out_request.buf_len *
+ gsi->d_port.out_request.num_bufs;
+ dev_dbg(&cdev->gadget->dev, "%zu %zu\n",
+ gsi->d_port.out_request.buf_len,
+ gsi->d_port.out_request.num_bufs);
+ gsi->d_port.out_request.buf_base_addr =
+ dma_zalloc_coherent(&cdev->gadget->dev, len,
+ &gsi->d_port.out_request.dma, GFP_KERNEL);
+ if (!gsi->d_port.out_request.buf_base_addr) {
+ dev_err(&cdev->gadget->dev,
+ "OUT buf_base_addr allocate failed %s\n",
+ gsi->function.name);
+ goto fail;
+ }
+ }
+
+ /* 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;
+
+ /* copy descriptors, and track endpoint copies */
+ f->fs_descriptors = usb_copy_descriptors(info->fs_desc_hdr);
+ if (!gsi->function.fs_descriptors)
+ goto fail;
+
+ /* support all relevant hardware speeds... we expect that when
+ * hardware is dual speed, all bulk-capable endpoints work at
+ * both speeds
+ */
+ if (gadget_is_dualspeed(cdev->gadget)) {
+ if (info->fs_in_desc)
+ info->hs_in_desc->bEndpointAddress =
+ info->fs_in_desc->bEndpointAddress;
+ if (info->fs_out_desc)
+ info->hs_out_desc->bEndpointAddress =
+ info->fs_out_desc->bEndpointAddress;
+ if (info->fs_notify_desc)
+ info->hs_notify_desc->bEndpointAddress =
+ info->fs_notify_desc->bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->hs_descriptors = usb_copy_descriptors(info->hs_desc_hdr);
+ if (!f->hs_descriptors)
+ goto fail;
+ }
+
+ if (gadget_is_superspeed(cdev->gadget)) {
+ if (info->fs_in_desc)
+ info->ss_in_desc->bEndpointAddress =
+ info->fs_in_desc->bEndpointAddress;
+
+ if (info->fs_out_desc)
+ info->ss_out_desc->bEndpointAddress =
+ info->fs_out_desc->bEndpointAddress;
+ if (info->fs_notify_desc)
+ info->ss_notify_desc->bEndpointAddress =
+ info->fs_notify_desc->bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->ss_descriptors = usb_copy_descriptors(info->ss_desc_hdr);
+ if (!f->ss_descriptors)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (gadget_is_superspeed(cdev->gadget) && f->ss_descriptors)
+ usb_free_descriptors(f->ss_descriptors);
+ if (gadget_is_dualspeed(cdev->gadget) && f->hs_descriptors)
+ usb_free_descriptors(f->hs_descriptors);
+ if (f->fs_descriptors)
+ usb_free_descriptors(f->fs_descriptors);
+ 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)
+ gsi->d_port.out_ep->driver_data = NULL;
+ if (gsi->d_port.in_ep && gsi->d_port.in_ep->desc)
+ gsi->d_port.in_ep->driver_data = NULL;
+ if (len && gsi->d_port.in_request.buf_base_addr)
+ dma_free_coherent(&cdev->gadget->dev, len,
+ gsi->d_port.in_request.buf_base_addr,
+ gsi->d_port.in_request.dma);
+ if (len && gsi->d_port.out_request.buf_base_addr)
+ dma_free_coherent(&cdev->gadget->dev, len,
+ gsi->d_port.out_request.buf_base_addr,
+ gsi->d_port.out_request.dma);
+ log_event_err("%s: bind failed for %s", __func__, f->name);
+ return -ENOMEM;
+}
+
+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);
+ int status;
+
+ 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_req_buf_len = GSI_IN_BUFF_SIZE;
+ gsi->d_port.in_aggr_size = GSI_IN_RNDIS_AGGR_SIZE;
+ info.in_req_num_buf = num_in_bufs;
+ gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE;
+ info.out_req_buf_len = GSI_OUT_AGGR_SIZE;
+ info.out_req_num_buf = num_out_bufs;
+ info.notify_buf_len = sizeof(struct usb_cdc_notification);
+
+ status = rndis_register(gsi_rndis_response_available, gsi,
+ gsi_rndis_flow_ctrl_enable);
+ if (status < 0)
+ goto fail;
+
+ gsi->config = status;
+
+ rndis_set_param_medium(gsi->config, 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->config, gsi->ethaddr);
+
+ if (gsi->manufacturer && gsi->vendorID &&
+ rndis_set_param_vendor(gsi->config, gsi->vendorID,
+ gsi->manufacturer))
+ goto dereg_rndis;
+
+ log_event_dbg("%s: max_pkt_per_xfer : %d", __func__,
+ DEFAULT_MAX_PKT_PER_XFER);
+ rndis_set_max_pkt_xfer(gsi->config, 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);
+ rndis_set_pkt_alignment_factor(gsi->config,
+ DEFAULT_PKT_ALIGNMENT_FACTOR);
+ 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;
+ gsi->d_port.in_aggr_size = GSI_IN_MBIM_AGGR_SIZE;
+ info.in_req_buf_len = GSI_IN_BUFF_SIZE;
+ info.in_req_num_buf = num_in_bufs;
+ 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 = num_out_bufs;
+ 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;
+ gsi->d_port.in_aggr_size = GSI_IN_RMNET_AGGR_SIZE;
+ info.in_req_buf_len = GSI_IN_BUFF_SIZE;
+ info.in_req_num_buf = num_in_bufs;
+ 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 = num_out_bufs;
+ 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;
+ gsi->d_port.in_aggr_size = GSI_IN_ECM_AGGR_SIZE;
+ info.in_req_buf_len = GSI_IN_BUFF_SIZE;
+ info.in_req_num_buf = num_in_bufs;
+ gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE;
+ info.out_req_buf_len = GSI_OUT_ECM_BUF_LEN;
+ info.out_req_num_buf = num_out_bufs;
+ 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);
+
+ snprintf(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_hs_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_hs_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_req_buf_len = 16384;
+ info.in_req_num_buf = num_in_bufs;
+ 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);
+
+ post_event(&gsi->d_port, EVT_INITIALIZED);
+ queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+
+ 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->name, gsi->d_port.out_ep->name,
+ gsi->c_port.notify->name);
+ return 0;
+
+dereg_rndis:
+ rndis_deregister(gsi->config);
+fail:
+ return status;
+}
+
+static void gsi_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_gsi *gsi = func_to_gsi(f);
+ struct usb_composite_dev *cdev = c->cdev;
+ u32 len;
+
+ /*
+ * call flush_workqueue to make sure that any pending
+ * disconnect_work() is being flushed before calling
+ * ipa_usb_deinit_teth_prot ipa
+ */
+ flush_workqueue(gsi->d_port.ipa_usb_wq);
+ ipa_usb_deinit_teth_prot(gsi->prot_id);
+ gadget_restarted = false;
+
+ if (gsi->prot_id == IPA_USB_RNDIS) {
+ gsi->d_port.sm_state = STATE_UNINITIALIZED;
+ rndis_deregister(gsi->config);
+ }
+
+ if (gsi->prot_id == IPA_USB_MBIM)
+ mbim_gsi_ext_config_desc.function.subCompatibleID[0] = 0;
+
+ if (gadget_is_superspeed(c->cdev->gadget))
+ usb_free_descriptors(f->ss_descriptors);
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->fs_descriptors);
+
+ 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);
+
+ len =
+ gsi->d_port.out_request.buf_len *
+ gsi->d_port.out_request.num_bufs;
+ dma_free_coherent(&cdev->gadget->dev, len,
+ gsi->d_port.out_request.buf_base_addr,
+ gsi->d_port.out_request.dma);
+ }
+
+ len = gsi->d_port.in_request.buf_len * gsi->d_port.in_request.num_bufs;
+ dma_free_coherent(&cdev->gadget->dev, len,
+ gsi->d_port.in_request.buf_base_addr,
+ gsi->d_port.in_request.dma);
+}
+
+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);
+}
+
+int gsi_bind_config(struct usb_configuration *c, enum ipa_usb_teth_prot prot_id)
+{
+ struct f_gsi *gsi;
+ int status = 0;
+
+ log_event_dbg("%s: prot id %d", __func__, prot_id);
+
+ if (prot_id >= IPA_USB_MAX_TETH_PROT_SIZE) {
+ log_event_err("%s: invalid prot id %d", __func__, prot_id);
+ return -EINVAL;
+ }
+
+ gsi = gsi_prot_ctx[prot_id];
+
+ if (!gsi) {
+ log_event_err("%s: gsi prot ctx is NULL", __func__);
+ return -EINVAL;
+ }
+
+ if (!gadget_restarted) {
+ usb_gadget_restart(c->cdev->gadget);
+ gadget_restarted = true;
+ }
+
+ 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.suspend = gsi_suspend;
+ gsi->function.func_suspend = gsi_func_suspend;
+ gsi->function.resume = gsi_resume;
+
+ INIT_WORK(&gsi->d_port.usb_ipa_w, ipa_work_handler);
+
+ status = usb_add_function(c, &gsi->function);
+ if (status)
+ return status;
+
+ 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__);
+ return -ETIMEDOUT;
+ }
+ }
+
+ gsi->d_port.ipa_usb_notify_cb = ipa_usb_notify_cb;
+ status = ipa_usb_init_teth_prot(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",
+ __func__, prot_id);
+ return status;
+ }
+
+ return status;
+}
+
+static int 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) {
+ log_event_err("%s: invalid prot id %d", __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);
+
+ gsi->d_port.in_channel_handle = -EINVAL;
+ gsi->d_port.out_channel_handle = -EINVAL;
+
+ gsi->prot_id = prot_id;
+
+ gsi_prot_ctx[prot_id] = gsi;
+
+ gsi->d_port.ipa_usb_wq = ipa_usb_wq;
+
+ ret = gsi_function_ctrl_port_init(prot_id);
+ if (ret) {
+ kfree(gsi);
+ gsi_prot_ctx[prot_id] = NULL;
+ }
+
+error:
+ return ret;
+}
+
+static void gsi_function_cleanup(enum ipa_usb_teth_prot prot_id)
+{
+ struct f_gsi *gsi = gsi_prot_ctx[prot_id];
+
+ if (prot_id >= IPA_USB_MAX_TETH_PROT_SIZE) {
+ log_event_err("%s: invalid prot id %d", __func__, prot_id);
+ return;
+ }
+
+ if (gsi->c_port.ctrl_device.fops) {
+ misc_deregister(&gsi->c_port.ctrl_device);
+ gsi->c_port.ctrl_device.fops = NULL;
+ }
+
+ kfree(gsi_prot_ctx[prot_id]);
+ gsi_prot_ctx[prot_id] = NULL;
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("GSI function driver");
+
+static int fgsi_init(void)
+{
+ ipa_usb_wq = alloc_workqueue("k_ipa_usb",
+ WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+ if (!ipa_usb_wq) {
+ log_event_err("Failed to create workqueue for IPA");
+ return -ENOMEM;
+ }
+ usb_gsi_debugfs_init();
+ return 0;
+}
+module_init(fgsi_init);
+
+static void __exit fgsi_exit(void)
+{
+ if (ipa_usb_wq)
+ destroy_workqueue(ipa_usb_wq);
+ 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..c0f61749fa82
--- /dev/null
+++ b/drivers/usb/gadget/function/f_gsi.h
@@ -0,0 +1,1330 @@
+/*
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details
+ */
+
+#ifndef _F_GSI_H
+#define _F_GSI_H
+
+#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 4096
+
+#define GSI_NUM_IN_BUFFERS 7
+#define GSI_IN_BUFF_SIZE 2048
+#define GSI_NUM_OUT_BUFFERS 7
+#define GSI_OUT_AGGR_SIZE 24576
+
+#define GSI_IN_RNDIS_AGGR_SIZE 9216
+#define GSI_IN_MBIM_AGGR_SIZE 16384
+#define GSI_IN_RMNET_AGGR_SIZE 16384
+#define GSI_IN_ECM_AGGR_SIZE 2048
+
+#define GSI_OUT_MBIM_BUF_LEN 16384
+#define GSI_OUT_RMNET_BUF_LEN 16384
+#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_IOCTL_MAGIC 'o'
+#define GSI_MBIM_GET_NTB_SIZE _IOR(GSI_MBIM_IOCTL_MAGIC, 2, u32)
+#define GSI_MBIM_GET_DATAGRAM_COUNT _IOR(GSI_MBIM_IOCTL_MAGIC, 3, u16)
+#define GSI_MBIM_EP_LOOKUP _IOR(GSI_MBIM_IOCTL_MAGIC, 4, struct ep_info)
+#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_CONNECT_IN_PROGRESS 3
+#define EVT_CONNECTED 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
+
+enum connection_state {
+ STATE_UNINITIALIZED,
+ STATE_INITIALIZED,
+ STATE_CONNECT_IN_PROGRESS,
+ STATE_CONNECTED,
+ STATE_DISCONNECTED,
+ STATE_SUSPEND_IN_PROGRESS,
+ STATE_SUSPENDED
+};
+
+#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;
+ 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;
+ u32 in_req_buf_len;
+ u32 in_req_num_buf;
+ u32 out_req_buf_len;
+ u32 out_req_num_buf;
+ u32 notify_buf_len;
+};
+
+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,
+};
+
+struct gsi_ctrl_port {
+ char name[GSI_CTRL_NAME_LEN];
+ struct miscdevice ctrl_device;
+
+ struct usb_ep *notify;
+ struct usb_request *notify_req;
+ int notify_state;
+ atomic_t notify_count;
+
+ 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 host_to_modem;
+ unsigned copied_to_modem;
+ unsigned copied_from_modem;
+ unsigned modem_to_host;
+ unsigned cpkt_drop_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;
+ 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_db_reg_phs_addr_lsb;
+ u32 in_db_reg_phs_addr_msb;
+ u32 out_db_reg_phs_addr_lsb;
+ u32 out_db_reg_phs_addr_msb;
+ 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 work_struct 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;
+ int config;
+ atomic_t connected;
+ bool data_interface_up;
+
+ 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;
+};
+
+static struct f_gsi *gsi_prot_ctx[IPA_USB_MAX_TETH_PROT_SIZE];
+
+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);
+}
+
+/* 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_CLASS_VENDOR_SPEC,
+ .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC,
+ /* .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 = 2,
+ /* .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: */
+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_COMM,
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
+ .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR,
+ /* .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 */
+};
+
+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_COMM,
+ .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET,
+ .bFunctionProtocol = USB_CDC_PROTO_NONE,
+ /* .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,
+
+ .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,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *gsi_eth_fs_function[] = {
+ (struct usb_descriptor_header *) &gsi_eth_fs_function,
+ /* 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 = 2,
+ /* .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 */
+
+static struct usb_cdc_ncm_ntb_parameters mbim_gsi_ntb_parameters = {
+ .wLength = sizeof(mbim_gsi_ntb_parameters),
+ .bmNtbFormatsSupported = cpu_to_le16(USB_CDC_NCM_NTB16_SUPPORTED),
+ .dwNtbInMaxSize = cpu_to_le32(0x4000),
+ .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 = 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 = 4*cpu_to_le16(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,
+};
+
+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,
+};
+
+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 = 4*cpu_to_le16(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 = 4*cpu_to_le16(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 = 4*cpu_to_le16(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 = 2,
+ /* .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 OS String */
+static u8 mbim_gsi_os_string[] = {
+ 18, /* sizeof(mtp_os_string) */
+ USB_DT_STRING,
+ /* Signature field: "MSFT100" */
+ 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
+ /* vendor code */
+ MBIM_VENDOR_CODE,
+ /* padding */
+ 0
+};
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mbim_gsi_ext_config_desc_header {
+ __le32 dwLength;
+ __u16 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_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,
+};
+
+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,
+};
+
+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 = 2,
+ /* .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 = 0xff,
+ .bInterfaceSubClass = 0xff,
+ .bInterfaceProtocol = 0xff,
+};
+
+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_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 = "QDSS 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