diff options
author | Hemant Kumar <hemantk@codeaurora.org> | 2016-01-28 19:55:06 -0800 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:03:08 -0700 |
commit | 7bc7efae3f8c2b9872b7d517a14f476a8b9a0c1d (patch) | |
tree | 50afc096c2e88d01a24dffc873a5fcfaa531737f /drivers/usb | |
parent | f8197f0801ee2d074c19a7a250f90562e52ae37f (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.c | 2997 | ||||
-rw-r--r-- | drivers/usb/gadget/function/f_gsi.h | 1330 |
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 |