From f845a7f24a4d645b0f968f628c758d77b5e5762f Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Tue, 15 Mar 2016 18:50:56 -0700 Subject: [PATCH] USB: Add USB BAM2BAM related function drivers and relevant glue drivers This change adds MSM specific USB function drivers and relevant glue drivers providing RMNET, RNDIS, DPL, ECM and MBIM functionality with BAM2BAM IPA mode. This snapshot is taken as of msm-3.18 kernel 'commit 8007444c107a ("Merge pinctrl: qcom: Update gcc_gp1_clk_b_groups for msm8953")'. Signed-off-by: Mayank Rana --- drivers/usb/gadget/function/f_mbim.c | 2150 ++++++++++++++++++ drivers/usb/gadget/function/f_qc_ecm.c | 1165 ++++++++++ drivers/usb/gadget/function/f_qc_rndis.c | 1421 ++++++++++++ drivers/usb/gadget/function/f_rmnet.c | 1469 +++++++++++++ drivers/usb/gadget/function/u_bam.c | 2523 ++++++++++++++++++++++ drivers/usb/gadget/function/u_bam_data.c | 2113 ++++++++++++++++++ drivers/usb/gadget/function/u_bam_data.h | 71 + drivers/usb/gadget/function/u_ctrl_qti.c | 837 +++++++ drivers/usb/gadget/function/u_data_ipa.c | 877 ++++++++ drivers/usb/gadget/function/u_data_ipa.h | 35 + drivers/usb/gadget/function/u_qc_ether.c | 454 ++++ drivers/usb/gadget/function/u_qc_ether.h | 101 + drivers/usb/gadget/function/u_rmnet.h | 79 + 13 files changed, 13295 insertions(+) create mode 100644 drivers/usb/gadget/function/f_mbim.c create mode 100644 drivers/usb/gadget/function/f_qc_ecm.c create mode 100644 drivers/usb/gadget/function/f_qc_rndis.c create mode 100644 drivers/usb/gadget/function/f_rmnet.c create mode 100644 drivers/usb/gadget/function/u_bam.c create mode 100644 drivers/usb/gadget/function/u_bam_data.c create mode 100644 drivers/usb/gadget/function/u_bam_data.h create mode 100644 drivers/usb/gadget/function/u_ctrl_qti.c create mode 100644 drivers/usb/gadget/function/u_data_ipa.c create mode 100644 drivers/usb/gadget/function/u_data_ipa.h create mode 100644 drivers/usb/gadget/function/u_qc_ether.c create mode 100644 drivers/usb/gadget/function/u_qc_ether.h create mode 100644 drivers/usb/gadget/function/u_rmnet.h diff --git a/drivers/usb/gadget/function/f_mbim.c b/drivers/usb/gadget/function/f_mbim.c new file mode 100644 index 000000000000..017a2a9cb6ea --- /dev/null +++ b/drivers/usb/gadget/function/f_mbim.c @@ -0,0 +1,2150 @@ +/* Copyright (c) 2012-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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include + +#include + +#include +#include + +#include + +/* + * This function is a "Mobile Broadband Interface Model" (MBIM) link. + * MBIM is intended to be used with high-speed network attachments. + * + * Note that MBIM requires the use of "alternate settings" for its data + * interface. This means that the set_alt() method has real work to do, + * and also means that a get_alt() method is required. + */ + +#define MBIM_BULK_BUFFER_SIZE 4096 +#define MAX_CTRL_PKT_SIZE 4096 + +enum mbim_peripheral_ep_type { + MBIM_DATA_EP_TYPE_RESERVED = 0x0, + MBIM_DATA_EP_TYPE_HSIC = 0x1, + MBIM_DATA_EP_TYPE_HSUSB = 0x2, + MBIM_DATA_EP_TYPE_PCIE = 0x3, + MBIM_DATA_EP_TYPE_EMBEDDED = 0x4, + MBIM_DATA_EP_TYPE_BAM_DMUX = 0x5, +}; + +struct mbim_peripheral_ep_info { + enum peripheral_ep_type ep_type; + u32 peripheral_iface_id; +}; + +struct mbim_ipa_ep_pair { + u32 cons_pipe_num; + u32 prod_pipe_num; +}; + +struct mbim_ipa_ep_info { + struct mbim_peripheral_ep_info ph_ep_info; + struct mbim_ipa_ep_pair ipa_ep_pair; +}; + +#define MBIM_IOCTL_MAGIC 'o' +#define MBIM_GET_NTB_SIZE _IOR(MBIM_IOCTL_MAGIC, 2, u32) +#define MBIM_GET_DATAGRAM_COUNT _IOR(MBIM_IOCTL_MAGIC, 3, u16) + +#define MBIM_EP_LOOKUP _IOR(MBIM_IOCTL_MAGIC, 4, struct mbim_ipa_ep_info) + + +#define NR_MBIM_PORTS 1 +#define MBIM_DEFAULT_PORT 0 + +/* ID for Microsoft OS String */ +#define MBIM_OS_STRING_ID 0xEE + +struct ctrl_pkt { + void *buf; + int len; + struct list_head list; +}; + +struct mbim_ep_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; + struct usb_endpoint_descriptor *notify; +}; + +struct mbim_notify_port { + struct usb_ep *notify; + struct usb_request *notify_req; + u8 notify_state; + atomic_t notify_count; +}; + +enum mbim_notify_state { + MBIM_NOTIFY_NONE, + MBIM_NOTIFY_CONNECT, + MBIM_NOTIFY_SPEED, + MBIM_NOTIFY_RESPONSE_AVAILABLE, +}; + +struct f_mbim { + struct usb_function function; + struct usb_composite_dev *cdev; + + atomic_t online; + + atomic_t open_excl; + atomic_t ioctl_excl; + atomic_t read_excl; + atomic_t write_excl; + + wait_queue_head_t read_wq; + + enum transport_type xport; + u8 port_num; + struct data_port bam_port; + struct mbim_notify_port not_port; + + struct mbim_ep_descs fs; + struct mbim_ep_descs hs; + + u8 ctrl_id, data_id; + bool data_interface_up; + + spinlock_t lock; + + struct list_head cpkt_req_q; + struct list_head cpkt_resp_q; + + u32 ntb_input_size; + u16 ntb_max_datagrams; + + atomic_t error; + unsigned int cpkt_drop_cnt; + bool remote_wakeup_enabled; +}; + +struct mbim_ntb_input_size { + u32 ntb_input_size; + u16 ntb_max_datagrams; + u16 reserved; +}; + +/* temporary variable used between mbim_open() and mbim_gadget_bind() */ +static struct f_mbim *_mbim_dev; + +static unsigned int nr_mbim_ports; + +static struct mbim_ports { + struct f_mbim *port; + unsigned port_num; +} mbim_ports[NR_MBIM_PORTS]; + +static inline struct f_mbim *func_to_mbim(struct usb_function *f) +{ + return container_of(f, struct f_mbim, function); +} + +/*-------------------------------------------------------------------------*/ + +#define MBIM_NTB_DEFAULT_IN_SIZE (0x4000) +#define MBIM_NTB_OUT_SIZE (0x1000) +#define MBIM_NDP_IN_DIVISOR (0x4) + +#define NTB_DEFAULT_IN_SIZE_IPA (0x4000) +#define MBIM_NTB_OUT_SIZE_IPA (0x4000) + +#define MBIM_FORMATS_SUPPORTED USB_CDC_NCM_NTB16_SUPPORTED + +static struct usb_cdc_ncm_ntb_parameters mbim_ntb_parameters = { + .wLength = sizeof mbim_ntb_parameters, + .bmNtbFormatsSupported = cpu_to_le16(MBIM_FORMATS_SUPPORTED), + .dwNtbInMaxSize = cpu_to_le32(MBIM_NTB_DEFAULT_IN_SIZE), + .wNdpInDivisor = cpu_to_le16(MBIM_NDP_IN_DIVISOR), + .wNdpInPayloadRemainder = cpu_to_le16(0), + .wNdpInAlignment = cpu_to_le16(4), + + .dwNtbOutMaxSize = cpu_to_le32(MBIM_NTB_OUT_SIZE), + .wNdpOutDivisor = cpu_to_le16(4), + .wNdpOutPayloadRemainder = cpu_to_le16(0), + .wNdpOutAlignment = cpu_to_le16(4), + .wNtbOutMaxDatagrams = 0, +}; + +/* + * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; and a big transfer interval, to + * waste less bandwidth. + */ + +#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +static struct usb_interface_assoc_descriptor mbim_iad_desc = { + .bLength = sizeof mbim_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_control_intf = { + .bLength = sizeof mbim_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_header_desc = { + .bLength = sizeof mbim_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc mbim_union_desc = { + .bLength = sizeof(mbim_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_mbim_desc mbim_desc = { + .bLength = sizeof mbim_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(0x800), + .bmNetworkCapabilities = 0x20, +}; + +static struct usb_cdc_mbim_extended_desc ext_mbb_desc = { + .bLength = sizeof 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_data_nop_intf = { + .bLength = sizeof mbim_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_data_intf = { + .bLength = sizeof mbim_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 fs_mbim_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 fs_mbim_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 fs_mbim_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_fs_function[] = { + (struct usb_descriptor_header *) &mbim_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_control_intf, + (struct usb_descriptor_header *) &mbim_header_desc, + (struct usb_descriptor_header *) &mbim_union_desc, + (struct usb_descriptor_header *) &mbim_desc, + (struct usb_descriptor_header *) &ext_mbb_desc, + (struct usb_descriptor_header *) &fs_mbim_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_data_nop_intf, + (struct usb_descriptor_header *) &mbim_data_intf, + (struct usb_descriptor_header *) &fs_mbim_in_desc, + (struct usb_descriptor_header *) &fs_mbim_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_mbim_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 hs_mbim_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 hs_mbim_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_hs_function[] = { + (struct usb_descriptor_header *) &mbim_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_control_intf, + (struct usb_descriptor_header *) &mbim_header_desc, + (struct usb_descriptor_header *) &mbim_union_desc, + (struct usb_descriptor_header *) &mbim_desc, + (struct usb_descriptor_header *) &ext_mbb_desc, + (struct usb_descriptor_header *) &hs_mbim_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_data_nop_intf, + (struct usb_descriptor_header *) &mbim_data_intf, + (struct usb_descriptor_header *) &hs_mbim_in_desc, + (struct usb_descriptor_header *) &hs_mbim_out_desc, + NULL, +}; + +/* Super Speed Support */ +static struct usb_endpoint_descriptor ss_mbim_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 ss_mbim_notify_comp_desc = { + .bLength = sizeof(ss_mbim_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 ss_mbim_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_mbim_in_comp_desc = { + .bLength = sizeof(ss_mbim_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor ss_mbim_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_mbim_out_comp_desc = { + .bLength = sizeof(ss_mbim_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *mbim_ss_function[] = { + (struct usb_descriptor_header *) &mbim_iad_desc, + /* MBIM control descriptors */ + (struct usb_descriptor_header *) &mbim_control_intf, + (struct usb_descriptor_header *) &mbim_header_desc, + (struct usb_descriptor_header *) &mbim_union_desc, + (struct usb_descriptor_header *) &mbim_desc, + (struct usb_descriptor_header *) &ext_mbb_desc, + (struct usb_descriptor_header *) &ss_mbim_notify_desc, + (struct usb_descriptor_header *) &ss_mbim_notify_comp_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &mbim_data_nop_intf, + (struct usb_descriptor_header *) &mbim_data_intf, + (struct usb_descriptor_header *) &ss_mbim_in_desc, + (struct usb_descriptor_header *) &ss_mbim_in_comp_desc, + (struct usb_descriptor_header *) &ss_mbim_out_desc, + (struct usb_descriptor_header *) &ss_mbim_out_comp_desc, + NULL, +}; + +/* string descriptors: */ + +#define STRING_CTRL_IDX 0 +#define STRING_DATA_IDX 1 + +static struct usb_string mbim_string_defs[] = { + [STRING_CTRL_IDX].s = "MBIM Control", + [STRING_DATA_IDX].s = "MBIM Data", + { } /* end of list */ +}; + +static struct usb_gadget_strings mbim_string_table = { + .language = 0x0409, /* en-us */ + .strings = mbim_string_defs, +}; + +static struct usb_gadget_strings *mbim_strings[] = { + &mbim_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_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_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mbim_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_ext_config_desc_header header; + struct mbim_ext_config_desc_function function; +} mbim_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32(sizeof mbim_ext_config_desc), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = 1, + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'A', 'L', 'T', 'R', 'C', 'F', 'G' }, + /* .subCompatibleID = DYNAMIC */ + }, +}; + +static inline int mbim_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -EBUSY; + } +} + +static inline void mbim_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +static struct ctrl_pkt *mbim_alloc_ctrl_pkt(unsigned len, gfp_t flags) +{ + struct ctrl_pkt *pkt; + + pkt = kzalloc(sizeof(struct 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 mbim_free_ctrl_pkt(struct ctrl_pkt *pkt) +{ + if (pkt) { + kfree(pkt->buf); + kfree(pkt); + } +} + +static struct usb_request *mbim_alloc_req(struct usb_ep *ep, int buffer_size, + size_t extra_buf) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + req->buf = kmalloc(buffer_size + extra_buf, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + req->length = buffer_size; + return req; +} + +void fmbim_free_req(struct usb_ep *ep, struct usb_request *req) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/* ---------------------------- BAM INTERFACE ----------------------------- */ + +static int mbim_bam_setup(int no_ports) +{ + int ret; + + pr_info("no_ports:%d\n", no_ports); + + ret = bam_data_setup(USB_FUNC_MBIM, no_ports); + if (ret) { + pr_err("bam_data_setup failed err: %d\n", ret); + return ret; + } + + pr_info("Initialized %d ports\n", no_ports); + return 0; +} + +/* -------------------------------------------------------------------------*/ + +static inline void mbim_reset_values(struct f_mbim *mbim) +{ + mbim->ntb_input_size = MBIM_NTB_DEFAULT_IN_SIZE; + + atomic_set(&mbim->online, 0); +} + +static void mbim_reset_function_queue(struct f_mbim *dev) +{ + struct ctrl_pkt *cpkt = NULL; + + pr_debug("Queue empty packet for QBI\n"); + + spin_lock(&dev->lock); + + cpkt = mbim_alloc_ctrl_pkt(0, GFP_ATOMIC); + if (!cpkt) { + pr_err("%s: Unable to allocate reset function pkt\n", __func__); + spin_unlock(&dev->lock); + return; + } + + list_add_tail(&cpkt->list, &dev->cpkt_req_q); + spin_unlock(&dev->lock); + + pr_debug("%s: Wake up read queue\n", __func__); + wake_up(&dev->read_wq); +} + +static void fmbim_reset_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_mbim *dev = req->context; + + mbim_reset_function_queue(dev); +} + +static void mbim_clear_queues(struct f_mbim *mbim) +{ + struct ctrl_pkt *cpkt = NULL; + struct list_head *act, *tmp; + + spin_lock(&mbim->lock); + list_for_each_safe(act, tmp, &mbim->cpkt_req_q) { + cpkt = list_entry(act, struct ctrl_pkt, list); + list_del(&cpkt->list); + mbim_free_ctrl_pkt(cpkt); + } + list_for_each_safe(act, tmp, &mbim->cpkt_resp_q) { + cpkt = list_entry(act, struct ctrl_pkt, list); + list_del(&cpkt->list); + mbim_free_ctrl_pkt(cpkt); + } + spin_unlock(&mbim->lock); +} + +/* + * Context: mbim->lock held + */ +static void mbim_do_notify(struct f_mbim *mbim) +{ + struct usb_request *req = mbim->not_port.notify_req; + struct usb_cdc_notification *event; + int status; + + pr_debug("notify_state: %d\n", mbim->not_port.notify_state); + + if (!req) + return; + + event = req->buf; + + switch (mbim->not_port.notify_state) { + + case MBIM_NOTIFY_NONE: + if (atomic_read(&mbim->not_port.notify_count) > 0) + pr_err("Pending notifications in MBIM_NOTIFY_NONE\n"); + else + pr_debug("No pending notifications\n"); + + return; + + case MBIM_NOTIFY_RESPONSE_AVAILABLE: + pr_debug("Notification %02x sent\n", event->bNotificationType); + + if (atomic_read(&mbim->not_port.notify_count) <= 0) { + pr_debug("notify_response_avaliable: done\n"); + return; + } + + spin_unlock(&mbim->lock); + status = usb_func_ep_queue(&mbim->function, + mbim->not_port.notify, + req, GFP_ATOMIC); + spin_lock(&mbim->lock); + if (status) { + atomic_dec(&mbim->not_port.notify_count); + pr_err("Queue notify request failed, err: %d\n", + status); + } + + return; + } + + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(mbim->ctrl_id); + + /* + * In double buffering if there is a space in FIFO, + * completion callback can be called right after the call, + * so unlocking + */ + atomic_inc(&mbim->not_port.notify_count); + pr_debug("queue request: notify_count = %d\n", + atomic_read(&mbim->not_port.notify_count)); + spin_unlock(&mbim->lock); + status = usb_func_ep_queue(&mbim->function, mbim->not_port.notify, req, + GFP_ATOMIC); + spin_lock(&mbim->lock); + if (status) { + atomic_dec(&mbim->not_port.notify_count); + pr_err("usb_func_ep_queue failed, err: %d\n", status); + } +} + +static void mbim_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_mbim *mbim = req->context; + struct usb_cdc_notification *event = req->buf; + + pr_debug("dev:%p\n", mbim); + + spin_lock(&mbim->lock); + switch (req->status) { + case 0: + atomic_dec(&mbim->not_port.notify_count); + pr_debug("notify_count = %d\n", + atomic_read(&mbim->not_port.notify_count)); + break; + + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + mbim->not_port.notify_state = MBIM_NOTIFY_NONE; + atomic_set(&mbim->not_port.notify_count, 0); + pr_info("ESHUTDOWN/ECONNRESET, connection gone\n"); + spin_unlock(&mbim->lock); + mbim_clear_queues(mbim); + mbim_reset_function_queue(mbim); + spin_lock(&mbim->lock); + break; + default: + pr_err("Unknown event %02x --> %d\n", + event->bNotificationType, req->status); + break; + } + + mbim_do_notify(mbim); + spin_unlock(&mbim->lock); + + pr_debug("dev:%p Exit\n", mbim); +} + +static void mbim_ep0out_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* now for SET_NTB_INPUT_SIZE only */ + unsigned in_size = 0; + struct usb_function *f = req->context; + struct f_mbim *mbim = func_to_mbim(f); + struct mbim_ntb_input_size *ntb = NULL; + + pr_debug("dev:%p\n", mbim); + + req->context = NULL; + if (req->status || req->actual != req->length) { + pr_err("Bad control-OUT transfer\n"); + 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_ntb_parameters.dwNtbInMaxSize)) { + pr_err("Illegal INPUT SIZE (%d) from host\n", in_size); + goto invalid; + } + } else if (req->length == 8) { + ntb = (struct mbim_ntb_input_size *)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_ntb_parameters.dwNtbInMaxSize)) { + pr_err("Illegal INPUT SIZE (%d) from host\n", in_size); + goto invalid; + } + mbim->ntb_max_datagrams = + get_unaligned_le16(&(ntb->ntb_max_datagrams)); + } else { + pr_err("Illegal NTB length %d\n", in_size); + goto invalid; + } + + pr_debug("Set NTB INPUT SIZE %d\n", in_size); + + mbim->ntb_input_size = in_size; + return; + +invalid: + usb_ep_set_halt(ep); + + pr_err("dev:%p Failed\n", mbim); + + return; +} + +static void +fmbim_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_mbim *dev = req->context; + struct ctrl_pkt *cpkt = NULL; + int len = req->actual; + static bool first_command_sent; + + if (!dev) { + pr_err("mbim dev is null\n"); + return; + } + + if (req->status < 0) { + pr_err("mbim command error %d\n", req->status); + return; + } + + /* + * Wait for user to process prev MBIM_OPEN cmd before handling new one. + * However don't drop first command during bootup as file may not be + * opened by now. Queue the command in this case. + */ + if (!atomic_read(&dev->open_excl) && first_command_sent) { + pr_err("mbim not opened yet, dropping cmd pkt = %d\n", len); + return; + } + if (!first_command_sent) + first_command_sent = true; + + pr_debug("dev:%p port#%d\n", dev, dev->port_num); + + cpkt = mbim_alloc_ctrl_pkt(len, GFP_ATOMIC); + if (!cpkt) { + pr_err("Unable to allocate ctrl pkt\n"); + return; + } + + pr_debug("Add to cpkt_req_q packet with len = %d\n", len); + memcpy(cpkt->buf, req->buf, len); + + spin_lock(&dev->lock); + + list_add_tail(&cpkt->list, &dev->cpkt_req_q); + spin_unlock(&dev->lock); + + /* wakeup read thread */ + pr_debug("Wake up read queue\n"); + wake_up(&dev->read_wq); + + return; +} + +static int +mbim_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_mbim *mbim = func_to_mbim(f); + struct usb_composite_dev *cdev = mbim->cdev; + struct usb_request *req = cdev->req; + struct ctrl_pkt *cpkt = NULL; + 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); + + /* + * composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + + if (!atomic_read(&mbim->online)) { + pr_warning("usb cable is not connected\n"); + return -ENOTCONN; + } + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_RESET_FUNCTION: + + pr_debug("USB_CDC_RESET_FUNCTION\n"); + value = 0; + req->complete = fmbim_reset_cmd_complete; + req->context = mbim; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + + pr_debug("USB_CDC_SEND_ENCAPSULATED_COMMAND\n"); + + if (w_length > req->length) { + pr_debug("w_length > req->length: %d > %d\n", + w_length, req->length); + } + value = w_length; + req->complete = fmbim_cmd_complete; + req->context = mbim; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + + pr_debug("USB_CDC_GET_ENCAPSULATED_RESPONSE\n"); + + if (w_value) { + pr_err("w_length > 0: %d\n", w_length); + break; + } + + pr_debug("req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + spin_lock(&mbim->lock); + if (list_empty(&mbim->cpkt_resp_q)) { + pr_err("ctrl resp queue empty\n"); + spin_unlock(&mbim->lock); + break; + } + + cpkt = list_first_entry(&mbim->cpkt_resp_q, + struct ctrl_pkt, list); + list_del(&cpkt->list); + spin_unlock(&mbim->lock); + + value = min_t(unsigned, w_length, cpkt->len); + memcpy(req->buf, cpkt->buf, value); + mbim_free_ctrl_pkt(cpkt); + + pr_debug("copied encapsulated_response %d bytes\n", + value); + + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_PARAMETERS: + + pr_debug("USB_CDC_GET_NTB_PARAMETERS\n"); + + if (w_length == 0 || w_value != 0 || w_index != mbim->ctrl_id) + break; + + value = w_length > sizeof mbim_ntb_parameters ? + sizeof mbim_ntb_parameters : w_length; + memcpy(req->buf, &mbim_ntb_parameters, value); + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_INPUT_SIZE: + + pr_debug("USB_CDC_GET_NTB_INPUT_SIZE\n"); + + if (w_length < 4 || w_value != 0 || w_index != mbim->ctrl_id) + break; + + put_unaligned_le32(mbim->ntb_input_size, req->buf); + value = 4; + pr_debug("Reply to host INPUT SIZE %d\n", + mbim->ntb_input_size); + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_INPUT_SIZE: + + pr_debug("USB_CDC_SET_NTB_INPUT_SIZE\n"); + + if (w_length != 4 && w_length != 8) { + pr_err("wrong NTB length %d\n", w_length); + break; + } + + if (w_value != 0 || w_index != mbim->ctrl_id) + break; + + req->complete = mbim_ep0out_complete; + req->length = w_length; + req->context = f; + + value = req->length; + break; + + /* optional in mbim descriptor: */ + /* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */ + /* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */ + + default: + pr_err("invalid control req: %02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + pr_debug("control request: %02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (value < w_length); + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + + if (value < 0) { + pr_err("queueing req failed: %02x.%02x, err %d\n", + ctrl->bRequestType, + ctrl->bRequest, value); + } + } else { + pr_err("ctrl req err %d: %02x.%02x v%04x i%04x l%d\n", + value, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +/* + * 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. + * + * Unlike mbim_setup() this function handles two specific device requests, + * and only when a configuration has not yet been selected. + */ +static int mbim_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 desciptors when no configuration selected */ + if (cdev->config || !mbim_ext_config_desc.function.subCompatibleID[0]) + return value; + + pr_debug("%02x.%02x v%04x i%04x l%u\n", + 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) == MBIM_OS_STRING_ID) { + + value = (w_length < sizeof(mbim_os_string) ? + w_length : sizeof(mbim_os_string)); + memcpy(cdev->req->buf, mbim_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_ext_config_desc) ? + w_length : sizeof(mbim_ext_config_desc)); + memcpy(cdev->req->buf, &mbim_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) + pr_err("response queue error: %d\n", rc); + } + return value; +} + +static int mbim_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_mbim *mbim = func_to_mbim(f); + struct usb_composite_dev *cdev = mbim->cdev; + int ret = 0; + + pr_debug("intf=%u, alt=%u\n", intf, alt); + + /* Control interface has only altsetting 0 */ + if (intf == mbim->ctrl_id) { + + pr_info("CONTROL_INTERFACE\n"); + + if (alt != 0) + goto fail; + + if (mbim->not_port.notify->driver_data) { + pr_info("reset mbim control %d\n", intf); + usb_ep_disable(mbim->not_port.notify); + } + + ret = config_ep_by_speed(cdev->gadget, f, + mbim->not_port.notify); + if (ret) { + mbim->not_port.notify->desc = NULL; + pr_err("Failed configuring notify ep %s: err %d\n", + mbim->not_port.notify->name, ret); + return ret; + } + + ret = usb_ep_enable(mbim->not_port.notify); + if (ret) { + pr_err("usb ep#%s enable failed, err#%d\n", + mbim->not_port.notify->name, ret); + return ret; + } + mbim->not_port.notify->driver_data = mbim; + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == mbim->data_id) { + + pr_info("DATA_INTERFACE id %d, data interface status %d\n", + mbim->data_id, mbim->data_interface_up); + + if (alt > 1) + goto fail; + + if (mbim->data_interface_up == alt) + return 0; + + if (mbim->bam_port.in->driver_data) { + pr_info("reset mbim, alt-%d\n", alt); + mbim_reset_values(mbim); + } + + if (alt == 0) { + /* + * perform bam data disconnect handshake upon usb + * disconnect + */ + switch (mbim->xport) { + case USB_GADGET_XPORT_BAM_DMUX: + gbam_mbim_disconnect(); + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + bam_data_disconnect(&mbim->bam_port, + USB_FUNC_MBIM, mbim->port_num); + if (!gadget_is_dwc3(cdev->gadget)) + break; + + if (msm_ep_unconfig(mbim->bam_port.in) || + msm_ep_unconfig(mbim->bam_port.out)) { + pr_err("ep_unconfig failed\n"); + goto fail; + } + default: + pr_err("unknown transport\n"); + } + goto notify_ready; + } + + pr_info("Alt set 1, initialize ports\n"); + + /* + * CDC Network only sends data in non-default altsettings. + * Changing altsettings resets filters, statistics, etc. + */ + pr_info("Choose endpoints\n"); + + ret = config_ep_by_speed(cdev->gadget, f, + mbim->bam_port.in); + if (ret) { + mbim->bam_port.in->desc = NULL; + pr_err("IN ep %s failed: %d\n", + mbim->bam_port.in->name, ret); + return ret; + } + + pr_info("Set mbim port in_desc = 0x%p\n", + mbim->bam_port.in->desc); + + ret = config_ep_by_speed(cdev->gadget, f, + mbim->bam_port.out); + if (ret) { + mbim->bam_port.out->desc = NULL; + pr_err("OUT ep %s failed: %d\n", + mbim->bam_port.out->name, ret); + return ret; + } + + pr_info("Set mbim port out_desc = 0x%p\n", + mbim->bam_port.out->desc); + + pr_debug("Activate mbim\n"); + switch (mbim->xport) { + case USB_GADGET_XPORT_BAM_DMUX: + gbam_mbim_connect(cdev->gadget, mbim->bam_port.in, + mbim->bam_port.out); + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + ret = bam_data_connect(&mbim->bam_port, + mbim->xport, mbim->port_num, + USB_FUNC_MBIM); + if (ret) { + pr_err("bam_data_setup failed:err:%d\n", + ret); + goto fail; + } + break; + default: + pr_err("unknown transport\n"); + } +notify_ready: + mbim->data_interface_up = alt; + spin_lock(&mbim->lock); + mbim->not_port.notify_state = MBIM_NOTIFY_RESPONSE_AVAILABLE; + spin_unlock(&mbim->lock); + } else { + goto fail; + } + + atomic_set(&mbim->online, 1); + + pr_info("SET DEVICE ONLINE\n"); + + return 0; + +fail: + pr_err("ERROR: Illegal Interface\n"); + return -EINVAL; +} + +/* + * Because the data interface supports multiple altsettings, + * this MBIM function *MUST* implement a get_alt() method. + */ +static int mbim_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_mbim *mbim = func_to_mbim(f); + + if (intf == mbim->ctrl_id) + return 0; + else if (intf == mbim->data_id) + return mbim->data_interface_up; + + return -EINVAL; +} + +static void mbim_disable(struct usb_function *f) +{ + struct f_mbim *mbim = func_to_mbim(f); + struct usb_composite_dev *cdev = mbim->cdev; + + pr_info("SET DEVICE OFFLINE\n"); + atomic_set(&mbim->online, 0); + mbim->remote_wakeup_enabled = 0; + + /* Disable Control Path */ + if (mbim->not_port.notify->driver_data) { + usb_ep_disable(mbim->not_port.notify); + mbim->not_port.notify->driver_data = NULL; + } + atomic_set(&mbim->not_port.notify_count, 0); + mbim->not_port.notify_state = MBIM_NOTIFY_NONE; + + mbim_clear_queues(mbim); + mbim_reset_function_queue(mbim); + + /* Disable Data Path - only if it was initialized already (alt=1) */ + if (!mbim->data_interface_up) { + pr_debug("MBIM data interface is not opened. Returning\n"); + return; + } + + switch (mbim->xport) { + case USB_GADGET_XPORT_BAM_DMUX: + gbam_mbim_disconnect(); + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + if (gadget_is_dwc3(cdev->gadget)) { + msm_ep_unconfig(mbim->bam_port.out); + msm_ep_unconfig(mbim->bam_port.in); + } + bam_data_disconnect(&mbim->bam_port, USB_FUNC_MBIM, + mbim->port_num); + break; + default: + pr_err("unknown transport\n"); + } + + mbim->data_interface_up = false; + pr_info("mbim deactivated\n"); +} + +#define MBIM_ACTIVE_PORT 0 + +static void mbim_suspend(struct usb_function *f) +{ + struct f_mbim *mbim = func_to_mbim(f); + + pr_info("mbim suspended\n"); + + pr_debug("%s(): remote_wakeup:%d\n:", __func__, + mbim->cdev->gadget->remote_wakeup); + + if (mbim->xport == USB_GADGET_XPORT_BAM_DMUX) + return; + + /* If the function is in Function Suspend state, avoid suspending the + * MBIM function again. + */ + if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) && + f->func_is_suspended) + return; + + if (mbim->cdev->gadget->speed == USB_SPEED_SUPER) + mbim->remote_wakeup_enabled = f->func_wakeup_allowed; + else + mbim->remote_wakeup_enabled = mbim->cdev->gadget->remote_wakeup; + + /* MBIM data interface is up only when alt setting is set to 1. */ + if (!mbim->data_interface_up) { + pr_debug("MBIM data interface is not opened. Returning\n"); + return; + } + + if (!mbim->remote_wakeup_enabled) + atomic_set(&mbim->online, 0); + + bam_data_suspend(&mbim->bam_port, mbim->port_num, USB_FUNC_MBIM, + mbim->remote_wakeup_enabled); +} + +static void mbim_resume(struct usb_function *f) +{ + struct f_mbim *mbim = func_to_mbim(f); + + pr_info("mbim resumed\n"); + + if (mbim->xport == USB_GADGET_XPORT_BAM_DMUX) + return; + + /* + * If the function is in USB3 Function Suspend state, resume is + * canceled. In this case resume is done by a Function Resume request. + */ + if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) && + f->func_is_suspended) + return; + + /* resume control path by queuing notify req */ + spin_lock(&mbim->lock); + mbim_do_notify(mbim); + spin_unlock(&mbim->lock); + + /* MBIM data interface is up only when alt setting is set to 1. */ + if (!mbim->data_interface_up) { + pr_debug("MBIM data interface is not opened. Returning\n"); + return; + } + + if (!mbim->remote_wakeup_enabled) + atomic_set(&mbim->online, 1); + + bam_data_resume(&mbim->bam_port, mbim->port_num, USB_FUNC_MBIM, + mbim->remote_wakeup_enabled); +} + +static int mbim_func_suspend(struct usb_function *f, unsigned char options) +{ + enum { + MBIM_FUNC_SUSPEND_MASK = 0x1, + MBIM_FUNC_WAKEUP_EN_MASK = 0x2 + }; + + bool func_wakeup_allowed; + struct f_mbim *mbim = func_to_mbim(f); + + if (f == NULL) + return -EINVAL; + + pr_debug("Got Function Suspend(%u) command for %s function\n", + options, f->name ? f->name : ""); + + /* Function Suspend is supported by Super Speed devices only */ + if (mbim->cdev->gadget->speed != USB_SPEED_SUPER) + return -ENOTSUPP; + + func_wakeup_allowed = + ((options & MBIM_FUNC_WAKEUP_EN_MASK) != 0); + + if (options & MBIM_FUNC_SUSPEND_MASK) { + f->func_wakeup_allowed = func_wakeup_allowed; + if (!f->func_is_suspended) { + mbim_suspend(f); + f->func_is_suspended = true; + } + } else { + if (f->func_is_suspended) { + f->func_is_suspended = false; + mbim_resume(f); + } + f->func_wakeup_allowed = func_wakeup_allowed; + } + + return 0; +} + +static int mbim_get_status(struct usb_function *f) +{ + enum { + MBIM_STS_FUNC_WAKEUP_CAP_SHIFT = 0, + MBIM_STS_FUNC_WAKEUP_EN_SHIFT = 1 + }; + + unsigned remote_wakeup_enabled_bit; + const unsigned remote_wakeup_capable_bit = 1; + + remote_wakeup_enabled_bit = f->func_wakeup_allowed ? 1 : 0; + return (remote_wakeup_enabled_bit << MBIM_STS_FUNC_WAKEUP_EN_SHIFT) | + (remote_wakeup_capable_bit << MBIM_STS_FUNC_WAKEUP_CAP_SHIFT); +} + +/*---------------------- function driver setup/binding ---------------------*/ + +static int +mbim_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_mbim *mbim = func_to_mbim(f); + int status; + struct usb_ep *ep; + struct usb_cdc_notification *event; + + pr_info("Enter\n"); + + mbim->cdev = cdev; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + mbim->ctrl_id = status; + mbim_iad_desc.bFirstInterface = status; + + mbim_control_intf.bInterfaceNumber = status; + mbim_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + mbim->data_id = status; + mbim->data_interface_up = false; + + mbim_data_nop_intf.bInterfaceNumber = status; + mbim_data_intf.bInterfaceNumber = status; + mbim_union_desc.bSlaveInterface0 = status; + + mbim->bam_port.cdev = cdev; + mbim->bam_port.func = &mbim->function; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_in_desc); + if (!ep) { + pr_err("usb epin autoconfig failed\n"); + goto fail; + } + pr_info("usb epin autoconfig succeeded\n"); + ep->driver_data = cdev; /* claim */ + mbim->bam_port.in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_out_desc); + if (!ep) { + pr_err("usb epout autoconfig failed\n"); + goto fail; + } + pr_info("usb epout autoconfig succeeded\n"); + ep->driver_data = cdev; /* claim */ + mbim->bam_port.out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_mbim_notify_desc); + if (!ep) { + pr_err("usb notify ep autoconfig failed\n"); + goto fail; + } + pr_info("usb notify ep autoconfig succeeded\n"); + mbim->not_port.notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + mbim->not_port.notify_req = mbim_alloc_req(ep, NCM_STATUS_BYTECOUNT, + cdev->gadget->extra_buf_alloc); + if (!mbim->not_port.notify_req) { + pr_info("failed to allocate notify request\n"); + goto fail; + } + pr_info("allocated notify ep request & request buffer\n"); + + mbim->not_port.notify_req->context = mbim; + mbim->not_port.notify_req->complete = mbim_notify_complete; + mbim->not_port.notify_req->length = sizeof(*event); + event = mbim->not_port.notify_req->buf; + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(mbim->ctrl_id); + event->wLength = cpu_to_le16(0); + + /* copy descriptors, and track endpoint copies */ + f->fs_descriptors = usb_copy_descriptors(mbim_fs_function); + if (!f->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(c->cdev->gadget)) { + hs_mbim_in_desc.bEndpointAddress = + fs_mbim_in_desc.bEndpointAddress; + hs_mbim_out_desc.bEndpointAddress = + fs_mbim_out_desc.bEndpointAddress; + hs_mbim_notify_desc.bEndpointAddress = + fs_mbim_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(mbim_hs_function); + if (!f->hs_descriptors) + goto fail; + } + + if (gadget_is_superspeed(c->cdev->gadget)) { + ss_mbim_in_desc.bEndpointAddress = + fs_mbim_in_desc.bEndpointAddress; + ss_mbim_out_desc.bEndpointAddress = + fs_mbim_out_desc.bEndpointAddress; + ss_mbim_notify_desc.bEndpointAddress = + fs_mbim_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(mbim_ss_function); + if (!f->ss_descriptors) + goto fail; + } + + /* + * 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) { + pr_debug("MBIM in configuration %d\n", c->bConfigurationValue); + mbim_ext_config_desc.function.subCompatibleID[0] = + c->bConfigurationValue + '0'; + } + + pr_info("mbim(%d): %s speed IN/%s OUT/%s NOTIFY/%s\n", + mbim->port_num, + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + mbim->bam_port.in->name, mbim->bam_port.out->name, + mbim->not_port.notify->name); + + return 0; + +fail: + pr_err("%s failed to bind, err %d\n", f->name, status); + + if (f->ss_descriptors) + usb_free_descriptors(f->ss_descriptors); + if (f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + if (f->fs_descriptors) + usb_free_descriptors(f->fs_descriptors); + + if (mbim->not_port.notify_req) { + kfree(mbim->not_port.notify_req->buf); + usb_ep_free_request(mbim->not_port.notify, + mbim->not_port.notify_req); + } + + /* we might as well release our claims on endpoints */ + if (mbim->not_port.notify) + mbim->not_port.notify->driver_data = NULL; + if (mbim->bam_port.out) + mbim->bam_port.out->driver_data = NULL; + if (mbim->bam_port.in) + mbim->bam_port.in->driver_data = NULL; + + return status; +} + +static void mbim_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_mbim *mbim = func_to_mbim(f); + + pr_debug("unbinding mbim\n"); + + 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); + + kfree(mbim->not_port.notify_req->buf); + usb_ep_free_request(mbim->not_port.notify, mbim->not_port.notify_req); + + mbim_ext_config_desc.function.subCompatibleID[0] = 0; +} + +/** + * mbim_bind_config - add MBIM link to a configuration + * @c: the configuration to support the network link + * Context: single threaded during gadget setup + * Returns zero on success, else negative errno. + */ +int mbim_bind_config(struct usb_configuration *c, unsigned portno, + char *xport_name) +{ + struct f_mbim *mbim = NULL; + int status = 0; + + pr_info("port number %u\n", portno); + + if (portno >= nr_mbim_ports) { + pr_err("Can not add port %u. Max ports = %d\n", + portno, nr_mbim_ports); + return -ENODEV; + } + + /* allocate and initialize one new instance */ + mbim = mbim_ports[portno].port; + if (!mbim) { + pr_err("mbim struct not allocated\n"); + return -ENOMEM; + } + + mbim->xport = str_to_xport(xport_name); + switch (mbim->xport) { + case USB_GADGET_XPORT_BAM2BAM: + /* Override BAM2BAM to BAM_DMUX for old ABI compatibility */ + mbim->xport = USB_GADGET_XPORT_BAM_DMUX; + /* fall-through */ + case USB_GADGET_XPORT_BAM_DMUX: + status = gbam_mbim_setup(); + if (status) + break; + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + status = mbim_bam_setup(nr_mbim_ports); + if (status) + break; + mbim_ntb_parameters.wNtbOutMaxDatagrams = 16; + /* For IPA this is proven to give maximum throughput */ + mbim_ntb_parameters.dwNtbInMaxSize = + cpu_to_le32(NTB_DEFAULT_IN_SIZE_IPA); + mbim_ntb_parameters.dwNtbOutMaxSize = + cpu_to_le32(MBIM_NTB_OUT_SIZE_IPA); + /* update rx buffer size to be used by usb rx request buffer */ + mbim->bam_port.rx_buffer_size = MBIM_NTB_OUT_SIZE_IPA; + mbim_ntb_parameters.wNdpInDivisor = 1; + pr_debug("MBIM: dwNtbOutMaxSize:%d\n", MBIM_NTB_OUT_SIZE_IPA); + break; + default: + status = -EINVAL; + } + + if (status) { + pr_err("%s transport setup failed\n", xport_name); + return status; + } + + + /* maybe allocate device-global string IDs */ + if (mbim_string_defs[0].id == 0) { + + /* control interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + mbim_string_defs[STRING_CTRL_IDX].id = status; + mbim_control_intf.iInterface = status; + + /* data interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + mbim_string_defs[STRING_DATA_IDX].id = status; + mbim_data_nop_intf.iInterface = status; + mbim_data_intf.iInterface = status; + } + + mbim->cdev = c->cdev; + + mbim_reset_values(mbim); + + mbim->function.name = "usb_mbim"; + mbim->function.strings = mbim_strings; + mbim->function.bind = mbim_bind; + mbim->function.unbind = mbim_unbind; + mbim->function.set_alt = mbim_set_alt; + mbim->function.get_alt = mbim_get_alt; + mbim->function.setup = mbim_setup; + mbim->function.disable = mbim_disable; + mbim->function.suspend = mbim_suspend; + mbim->function.func_suspend = mbim_func_suspend; + mbim->function.get_status = mbim_get_status; + mbim->function.resume = mbim_resume; + + INIT_LIST_HEAD(&mbim->cpkt_req_q); + INIT_LIST_HEAD(&mbim->cpkt_resp_q); + + status = usb_add_function(c, &mbim->function); + + pr_info("Exit status %d\n", status); + + return status; +} + +/* ------------ MBIM DRIVER File Operations API for USER SPACE ------------ */ + +static ssize_t +mbim_read(struct file *fp, char __user *buf, size_t count, loff_t *pos) +{ + struct f_mbim *dev = fp->private_data; + struct ctrl_pkt *cpkt = NULL; + unsigned long flags; + int ret = 0; + + pr_debug("Enter(%zu)\n", count); + + if (!dev) { + pr_err("Received NULL mbim pointer\n"); + return -ENODEV; + } + + if (count > MBIM_BULK_BUFFER_SIZE) { + pr_err("Buffer size is too big %zu, should be at most %d\n", + count, MBIM_BULK_BUFFER_SIZE); + return -EINVAL; + } + + if (mbim_lock(&dev->read_excl)) { + pr_err("Previous reading is not finished yet\n"); + return -EBUSY; + } + + if (atomic_read(&dev->error)) { + mbim_unlock(&dev->read_excl); + return -EIO; + } + + spin_lock_irqsave(&dev->lock, flags); + while (list_empty(&dev->cpkt_req_q)) { + pr_debug("Requests list is empty. Wait.\n"); + spin_unlock_irqrestore(&dev->lock, flags); + ret = wait_event_interruptible(dev->read_wq, + !list_empty(&dev->cpkt_req_q)); + if (ret < 0) { + pr_err("Waiting failed\n"); + mbim_unlock(&dev->read_excl); + return -ERESTARTSYS; + } + pr_debug("Received request packet\n"); + spin_lock_irqsave(&dev->lock, flags); + } + + cpkt = list_first_entry(&dev->cpkt_req_q, struct ctrl_pkt, + list); + if (cpkt->len > count) { + spin_unlock_irqrestore(&dev->lock, flags); + mbim_unlock(&dev->read_excl); + pr_err("cpkt size too big:%d > buf size:%zu\n", + cpkt->len, count); + return -ENOMEM; + } + + pr_debug("cpkt size:%d\n", cpkt->len); + + list_del(&cpkt->list); + spin_unlock_irqrestore(&dev->lock, flags); + mbim_unlock(&dev->read_excl); + + ret = copy_to_user(buf, cpkt->buf, cpkt->len); + if (ret) { + pr_err("copy_to_user failed: err %d\n", ret); + ret = -ENOMEM; + } else { + pr_debug("copied %d bytes to user\n", cpkt->len); + ret = cpkt->len; + } + + mbim_free_ctrl_pkt(cpkt); + + return ret; +} + +static ssize_t +mbim_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos) +{ + struct f_mbim *dev = fp->private_data; + struct ctrl_pkt *cpkt = NULL; + struct usb_request *req = dev->not_port.notify_req; + int ret = 0; + unsigned long flags; + + pr_debug("Enter(%zu)\n", count); + + if (!dev || !req || !req->buf) { + pr_err("%s: dev %p req %p req->buf %p\n", + __func__, dev, req, req ? req->buf : req); + return -ENODEV; + } + + if (!count || count > MAX_CTRL_PKT_SIZE) { + pr_err("error: ctrl pkt lenght %zu\n", count); + return -EINVAL; + } + + if (mbim_lock(&dev->write_excl)) { + pr_err("Previous writing not finished yet\n"); + return -EBUSY; + } + + if (!atomic_read(&dev->online)) { + pr_err("USB cable not connected\n"); + mbim_unlock(&dev->write_excl); + return -EPIPE; + } + + if (dev->not_port.notify_state != MBIM_NOTIFY_RESPONSE_AVAILABLE) { + pr_err("dev:%p state=%d error\n", dev, + dev->not_port.notify_state); + mbim_unlock(&dev->write_excl); + return -EINVAL; + } + + if (dev->function.func_is_suspended && + !dev->function.func_wakeup_allowed) { + dev->cpkt_drop_cnt++; + pr_err("drop ctrl pkt of len %zu\n", count); + return -ENOTSUPP; + } + + cpkt = mbim_alloc_ctrl_pkt(count, GFP_KERNEL); + if (!cpkt) { + pr_err("failed to allocate ctrl pkt\n"); + mbim_unlock(&dev->write_excl); + return -ENOMEM; + } + + ret = copy_from_user(cpkt->buf, buf, count); + if (ret) { + pr_err("copy_from_user failed err:%d\n", ret); + mbim_free_ctrl_pkt(cpkt); + mbim_unlock(&dev->write_excl); + return ret; + } + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&cpkt->list, &dev->cpkt_resp_q); + + if (atomic_inc_return(&dev->not_port.notify_count) != 1) { + pr_debug("delay ep_queue: notifications queue is busy[%d]\n", + atomic_read(&dev->not_port.notify_count)); + spin_unlock_irqrestore(&dev->lock, flags); + mbim_unlock(&dev->write_excl); + return count; + } + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_func_ep_queue(&dev->function, dev->not_port.notify, + req, GFP_ATOMIC); + if (ret == -ENOTSUPP || (ret < 0 && ret != -EAGAIN)) { + spin_lock_irqsave(&dev->lock, flags); + /* check if device disconnected while we dropped lock */ + if (atomic_read(&dev->online)) { + list_del(&cpkt->list); + atomic_dec(&dev->not_port.notify_count); + mbim_free_ctrl_pkt(cpkt); + } + dev->cpkt_drop_cnt++; + spin_unlock_irqrestore(&dev->lock, flags); + pr_err("drop ctrl pkt of len %d error %d\n", cpkt->len, ret); + } else { + ret = 0; + } + mbim_unlock(&dev->write_excl); + + pr_debug("Exit(%zu)\n", count); + + return ret ? ret : count; +} + +static int mbim_open(struct inode *ip, struct file *fp) +{ + pr_info("Open mbim driver\n"); + + while (!_mbim_dev) { + pr_err("mbim_dev not created yet\n"); + return -ENODEV; + } + + if (mbim_lock(&_mbim_dev->open_excl)) { + pr_err("Already opened\n"); + return -EBUSY; + } + + pr_info("Lock mbim_dev->open_excl for open\n"); + + if (!atomic_read(&_mbim_dev->online)) + pr_err("USB cable not connected\n"); + + fp->private_data = _mbim_dev; + + atomic_set(&_mbim_dev->error, 0); + + pr_info("Exit, mbim file opened\n"); + + return 0; +} + +static int mbim_release(struct inode *ip, struct file *fp) +{ + pr_info("Close mbim file\n"); + + mbim_unlock(&_mbim_dev->open_excl); + + return 0; +} + +#define BAM_DMUX_CHANNEL_ID 8 +static long mbim_ioctl(struct file *fp, unsigned cmd, unsigned long arg) +{ + struct f_mbim *mbim = fp->private_data; + struct data_port *port; + struct mbim_ipa_ep_info info; + int ret = 0; + + pr_debug("Received command %d\n", cmd); + + if (!mbim) { + pr_err("Bad parameter\n"); + return -EINVAL; + } + + if (mbim_lock(&mbim->ioctl_excl)) + return -EBUSY; + + switch (cmd) { + case MBIM_GET_NTB_SIZE: + ret = copy_to_user((void __user *)arg, + &mbim->ntb_input_size, sizeof(mbim->ntb_input_size)); + if (ret) { + pr_err("copying to user space failed\n"); + ret = -EFAULT; + } + pr_info("Sent NTB size %d\n", mbim->ntb_input_size); + break; + case MBIM_GET_DATAGRAM_COUNT: + ret = copy_to_user((void __user *)arg, + &mbim->ntb_max_datagrams, + sizeof(mbim->ntb_max_datagrams)); + if (ret) { + pr_err("copying to user space failed\n"); + ret = -EFAULT; + } + pr_info("Sent NTB datagrams count %d\n", + mbim->ntb_max_datagrams); + break; + + case MBIM_EP_LOOKUP: + if (!atomic_read(&mbim->online)) { + pr_warn("usb cable is not connected\n"); + return -ENOTCONN; + } + + switch (mbim->xport) { + case USB_GADGET_XPORT_BAM_DMUX: + /* + * Rmnet and MBIM share the same BAM-DMUX channel. + * This channel number 8 should be in sync with + * the one defined in u_bam.c. + */ + info.ph_ep_info.ep_type = MBIM_DATA_EP_TYPE_BAM_DMUX; + info.ph_ep_info.peripheral_iface_id = + BAM_DMUX_CHANNEL_ID; + info.ipa_ep_pair.cons_pipe_num = 0; + info.ipa_ep_pair.prod_pipe_num = 0; + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + port = &mbim->bam_port; + if ((port->ipa_producer_ep == -1) || + (port->ipa_consumer_ep == -1)) { + pr_err("EP_LOOKUP failed - IPA pipes not updated\n"); + ret = -EAGAIN; + break; + } + + info.ph_ep_info.ep_type = MBIM_DATA_EP_TYPE_HSUSB; + info.ph_ep_info.peripheral_iface_id = mbim->data_id; + info.ipa_ep_pair.cons_pipe_num = port->ipa_consumer_ep; + info.ipa_ep_pair.prod_pipe_num = port->ipa_producer_ep; + break; + default: + ret = -ENODEV; + pr_err("unknown transport\n"); + break; + } + + ret = copy_to_user((void __user *)arg, &info, + sizeof(info)); + if (ret) { + pr_err("copying to user space failed\n"); + ret = -EFAULT; + } + break; + + default: + pr_err("wrong parameter\n"); + ret = -EINVAL; + } + + mbim_unlock(&mbim->ioctl_excl); + + return ret; +} + +/* file operations for MBIM device /dev/android_mbim */ +static const struct file_operations mbim_fops = { + .owner = THIS_MODULE, + .open = mbim_open, + .release = mbim_release, + .read = mbim_read, + .write = mbim_write, + .unlocked_ioctl = mbim_ioctl, +}; + +static struct miscdevice mbim_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_mbim", + .fops = &mbim_fops, +}; + +static int mbim_init(int instances) +{ + int i; + struct f_mbim *dev = NULL; + int ret; + + pr_info("initialize %d instances\n", instances); + + if (instances > NR_MBIM_PORTS) { + pr_err("Max-%d instances supported\n", NR_MBIM_PORTS); + return -EINVAL; + } + + for (i = 0; i < instances; i++) { + dev = kzalloc(sizeof(struct f_mbim), GFP_KERNEL); + if (!dev) { + pr_err("Failed to allocate mbim dev\n"); + ret = -ENOMEM; + goto fail_probe; + } + + dev->port_num = i; + dev->bam_port.ipa_consumer_ep = -1; + dev->bam_port.ipa_producer_ep = -1; + + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->cpkt_req_q); + INIT_LIST_HEAD(&dev->cpkt_resp_q); + + mbim_ports[i].port = dev; + mbim_ports[i].port_num = i; + + init_waitqueue_head(&dev->read_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + nr_mbim_ports++; + + } + + _mbim_dev = dev; + ret = misc_register(&mbim_device); + if (ret) { + pr_err("mbim driver failed to register\n"); + goto fail_probe; + } + + pr_info("Initialized %d ports\n", nr_mbim_ports); + + return ret; + +fail_probe: + pr_err("Failed\n"); + for (i = 0; i < nr_mbim_ports; i++) { + kfree(mbim_ports[i].port); + mbim_ports[i].port = NULL; + } + + return ret; +} + +static void fmbim_cleanup(void) +{ + int i = 0; + + pr_info("Enter\n"); + + for (i = 0; i < nr_mbim_ports; i++) { + kfree(mbim_ports[i].port); + mbim_ports[i].port = NULL; + } + nr_mbim_ports = 0; + + misc_deregister(&mbim_device); + + _mbim_dev = NULL; +} + diff --git a/drivers/usb/gadget/function/f_qc_ecm.c b/drivers/usb/gadget/function/f_qc_ecm.c new file mode 100644 index 000000000000..847eb953ec61 --- /dev/null +++ b/drivers/usb/gadget/function/f_qc_ecm.c @@ -0,0 +1,1165 @@ +/* + * f_qc_ecm.c -- USB CDC Ethernet (ECM) link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * Copyright (c) 2012-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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define VERBOSE_DEBUG */ + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include + +#include "u_ether.h" +#include "u_qc_ether.h" + +#include "u_bam_data.h" +#include + + +/* + * This function is a "CDC Ethernet Networking Control Model" (CDC ECM) + * Ethernet link. The data transfer model is simple (packets sent and + * received over bulk endpoints using normal short packet termination), + * and the control model exposes various data and optional notifications. + * + * ECM is well standardized and (except for Microsoft) supported by most + * operating systems with USB host support. It's the preferred interop + * solution for Ethernet over USB, at least for firmware based solutions. + * (Hardware solutions tend to be more minimalist.) A newer and simpler + * "Ethernet Emulation Model" (CDC EEM) hasn't yet caught on. + * + * Note that ECM requires the use of "alternate settings" for its data + * interface. This means that the set_alt() method has real work to do, + * and also means that a get_alt() method is required. + * + * This function is based on USB CDC Ethernet link function driver and + * contains MSM specific implementation. + */ + + +enum ecm_qc_notify_state { + ECM_QC_NOTIFY_NONE, /* don't notify */ + ECM_QC_NOTIFY_CONNECT, /* issue CONNECT next */ + ECM_QC_NOTIFY_SPEED, /* issue SPEED_CHANGE next */ +}; + +struct f_ecm_qc { + struct qc_gether port; + u8 ctrl_id, data_id; + enum transport_type xport; + u8 port_num; + char ethaddr[14]; + + struct usb_ep *notify; + struct usb_request *notify_req; + u8 notify_state; + bool is_open; + struct data_port bam_port; + bool ecm_mdm_ready_trigger; + + bool data_interface_up; +}; + +static struct f_ecm_qc *__ecm; + +static struct ecm_ipa_params ipa_params; + +static inline struct f_ecm_qc *func_to_ecm_qc(struct usb_function *f) +{ + return container_of(f, struct f_ecm_qc, port.func); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static inline unsigned ecm_qc_bitrate(struct usb_gadget *g) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Include the status endpoint if we can, even though it's optional. + * + * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; and a big transfer interval, to + * waste less bandwidth. + * + * Some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even + * if they ignore the connect/disconnect notifications that real aether + * can provide. More advanced cdc configurations might want to support + * encapsulated commands (vendor-specific, using control-OUT). + */ + +#define ECM_QC_LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define ECM_QC_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +/* Currently only one std ecm instance is supported - port index 0. */ +#define ECM_QC_NO_PORTS 1 +#define ECM_QC_DEFAULT_PORT 0 +#define ECM_QC_ACTIVE_PORT 0 + +/* interface descriptor: */ + +static struct usb_interface_descriptor ecm_qc_control_intf = { + .bLength = sizeof ecm_qc_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_qc_header_desc = { + .bLength = sizeof ecm_qc_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc ecm_qc_union_desc = { + .bLength = sizeof(ecm_qc_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_ether_desc ecm_qc_desc = { + .bLength = sizeof ecm_qc_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_qc_data_nop_intf = { + .bLength = sizeof ecm_qc_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_qc_data_intf = { + .bLength = sizeof ecm_qc_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_qc_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 << ECM_QC_LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor ecm_qc_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_qc_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_qc_fs_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_qc_control_intf, + (struct usb_descriptor_header *) &ecm_qc_header_desc, + (struct usb_descriptor_header *) &ecm_qc_union_desc, + (struct usb_descriptor_header *) &ecm_qc_desc, + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ecm_qc_fs_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_qc_data_nop_intf, + (struct usb_descriptor_header *) &ecm_qc_data_intf, + (struct usb_descriptor_header *) &ecm_qc_fs_in_desc, + (struct usb_descriptor_header *) &ecm_qc_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor ecm_qc_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 = ECM_QC_LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor ecm_qc_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_qc_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_qc_hs_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_qc_control_intf, + (struct usb_descriptor_header *) &ecm_qc_header_desc, + (struct usb_descriptor_header *) &ecm_qc_union_desc, + (struct usb_descriptor_header *) &ecm_qc_desc, + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ecm_qc_hs_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_qc_data_nop_intf, + (struct usb_descriptor_header *) &ecm_qc_data_intf, + (struct usb_descriptor_header *) &ecm_qc_hs_in_desc, + (struct usb_descriptor_header *) &ecm_qc_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor ecm_qc_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_qc_ss_notify_comp_desc = { + .bLength = sizeof(ecm_qc_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_qc_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ecm_qc_ss_in_comp_desc = { + .bLength = sizeof(ecm_qc_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor ecm_qc_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ecm_qc_ss_out_comp_desc = { + .bLength = sizeof(ecm_qc_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ecm_qc_ss_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_qc_control_intf, + (struct usb_descriptor_header *) &ecm_qc_header_desc, + (struct usb_descriptor_header *) &ecm_qc_union_desc, + (struct usb_descriptor_header *) &ecm_qc_desc, + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ecm_qc_ss_notify_desc, + (struct usb_descriptor_header *) &ecm_qc_ss_notify_comp_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_qc_data_nop_intf, + (struct usb_descriptor_header *) &ecm_qc_data_intf, + (struct usb_descriptor_header *) &ecm_qc_ss_in_desc, + (struct usb_descriptor_header *) &ecm_qc_ss_in_comp_desc, + (struct usb_descriptor_header *) &ecm_qc_ss_out_desc, + (struct usb_descriptor_header *) &ecm_qc_ss_out_comp_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string ecm_qc_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_qc_string_table = { + .language = 0x0409, /* en-us */ + .strings = ecm_qc_string_defs, +}; + +static struct usb_gadget_strings *ecm_qc_strings[] = { + &ecm_qc_string_table, + NULL, +}; + +static void ecm_qc_do_notify(struct f_ecm_qc *ecm) +{ + struct usb_request *req = ecm->notify_req; + struct usb_cdc_notification *event; + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + __le32 *data; + int status; + + /* notification already in flight? */ + if (!req) + return; + + event = req->buf; + switch (ecm->notify_state) { + case ECM_QC_NOTIFY_NONE: + return; + + case ECM_QC_NOTIFY_CONNECT: + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + if (ecm->is_open) { + event->wValue = cpu_to_le16(1); + ecm->notify_state = ECM_QC_NOTIFY_SPEED; + } else { + event->wValue = cpu_to_le16(0); + ecm->notify_state = ECM_QC_NOTIFY_NONE; + } + event->wLength = 0; + req->length = sizeof *event; + + DBG(cdev, "notify connect %s\n", + ecm->is_open ? "true" : "false"); + break; + + case ECM_QC_NOTIFY_SPEED: + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(8); + req->length = ECM_QC_STATUS_BYTECOUNT; + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data = req->buf + sizeof *event; + data[0] = cpu_to_le32(ecm_qc_bitrate(cdev->gadget)); + data[1] = data[0]; + + DBG(cdev, "notify speed %d\n", ecm_qc_bitrate(cdev->gadget)); + ecm->notify_state = ECM_QC_NOTIFY_NONE; + break; + } + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(ecm->ctrl_id); + + ecm->notify_req = NULL; + status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC); + if (status < 0) { + ecm->notify_req = req; + DBG(cdev, "notify --> %d\n", status); + } +} + +static void ecm_qc_notify(struct f_ecm_qc *ecm) +{ + /* NOTE on most versions of Linux, host side cdc-ethernet + * won't listen for notifications until its netdevice opens. + * The first notification then sits in the FIFO for a long + * time, and the second one is queued. + */ + ecm->notify_state = ECM_QC_NOTIFY_CONNECT; + ecm_qc_do_notify(ecm); +} + +void *ecm_qc_get_ipa_rx_cb(void) +{ + return ipa_params.ecm_ipa_rx_dp_notify; +} + +void *ecm_qc_get_ipa_tx_cb(void) +{ + return ipa_params.ecm_ipa_tx_dp_notify; +} + +void *ecm_qc_get_ipa_priv(void) +{ + return ipa_params.private; +} + +bool ecm_qc_get_skip_ep_config(void) +{ + return ipa_params.skip_ep_cfg; +} +/*-------------------------------------------------------------------------*/ + + + +static void ecm_qc_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ecm_qc *ecm = req->context; + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + struct usb_cdc_notification *event = req->buf; + + switch (req->status) { + case 0: + /* no fault */ + break; + case -ECONNRESET: + case -ESHUTDOWN: + ecm->notify_state = ECM_QC_NOTIFY_NONE; + break; + default: + DBG(cdev, "event %02x --> %d\n", + event->bNotificationType, req->status); + break; + } + ecm->notify_req = req; + ecm_qc_do_notify(ecm); +} + +static int ecm_qc_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + pr_debug("Enter\n"); + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + 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 != ecm->ctrl_id) + goto invalid; + DBG(cdev, "packet filter %02x\n", 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... + */ + ecm->port.cdc_filter = w_value; + value = 0; + break; + + /* and optionally: + * case USB_CDC_SEND_ENCAPSULATED_COMMAND: + * case USB_CDC_GET_ENCAPSULATED_RESPONSE: + * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: + * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_STATISTIC: + */ + + default: +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "ecm req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + pr_err("ecm req %02x.%02x response err %d\n", + ctrl->bRequestType, ctrl->bRequest, + value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int ecm_qc_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* Control interface has only altsetting 0 */ + if (intf == ecm->ctrl_id) { + if (alt != 0) { + pr_warning("fail, alt setting is not 0\n"); + goto fail; + } + + if (ecm->notify->driver_data) { + VDBG(cdev, "reset ecm control %d\n", intf); + usb_ep_disable(ecm->notify); + } + if (!(ecm->notify->desc)) { + VDBG(cdev, "init ecm ctrl %d\n", intf); + if (config_ep_by_speed(cdev->gadget, f, ecm->notify)) + goto fail; + } + usb_ep_enable(ecm->notify); + ecm->notify->driver_data = ecm; + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == ecm->data_id) { + if (alt > 1) + goto fail; + + if (ecm->data_interface_up == alt) + return 0; + + if (!ecm->port.in_ep->desc || + !ecm->port.out_ep->desc) { + DBG(cdev, "init ecm\n"); + __ecm->ecm_mdm_ready_trigger = false; + if (config_ep_by_speed(cdev->gadget, f, + ecm->port.in_ep) || + config_ep_by_speed(cdev->gadget, f, + ecm->port.out_ep)) { + ecm->port.in_ep->desc = NULL; + ecm->port.out_ep->desc = NULL; + goto fail; + } + } + + if (alt == 0 && ecm->port.in_ep->driver_data) { + DBG(cdev, "reset ecm\n"); + __ecm->ecm_mdm_ready_trigger = false; + /* ecm->port is needed for disconnecting the BAM data + * path. Only after the BAM data path is disconnected, + * we can disconnect the port from the network layer. + */ + bam_data_disconnect(&ecm->bam_port, USB_FUNC_ECM, + ecm->port_num); + if (ecm->xport != USB_GADGET_XPORT_BAM2BAM_IPA) { + gether_qc_disconnect_name(&ecm->port, "ecm0"); + } else if (ecm->data_interface_up && + gadget_is_dwc3(cdev->gadget)) { + if (msm_ep_unconfig(ecm->port.in_ep) || + msm_ep_unconfig(ecm->port.out_ep)) { + pr_err("%s: ep_unconfig failed\n", + __func__); + goto fail; + } + } + } + /* CDC Ethernet only sends data in non-default altsettings. + * Changing altsettings resets filters, statistics, etc. + */ + if (alt == 1) { + struct net_device *net; + + /* Enable zlps by default for ECM conformance; + * override for musb_hdrc (avoids txdma ovhead). + */ + ecm->port.is_zlp_ok = !(gadget_is_musbhdrc(cdev->gadget) + ); + ecm->port.cdc_filter = DEFAULT_FILTER; + DBG(cdev, "activate ecm\n"); + if (ecm->xport != USB_GADGET_XPORT_BAM2BAM_IPA) { + net = gether_qc_connect_name(&ecm->port, + "ecm0", true); + if (IS_ERR(net)) + return PTR_ERR(net); + } + + ecm->bam_port.cdev = cdev; + ecm->bam_port.func = &ecm->port.func; + ecm->bam_port.in = ecm->port.in_ep; + ecm->bam_port.out = ecm->port.out_ep; + if (bam_data_connect(&ecm->bam_port, ecm->xport, + ecm->port_num, USB_FUNC_ECM)) + goto fail; + } + + ecm->data_interface_up = alt; + /* NOTE this can be a minor disagreement with the ECM spec, + * which says speed notifications will "always" follow + * connection notifications. But we allow one connect to + * follow another (if the first is in flight), and instead + * just guarantee that a speed notification is always sent. + */ + ecm_qc_notify(ecm); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +/* Because the data interface supports multiple altsettings, + * this ECM function *MUST* implement a get_alt() method. + */ +static int ecm_qc_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + + if (intf == ecm->ctrl_id) + return 0; + return ecm->port.in_ep->driver_data ? 1 : 0; +} + +static void ecm_qc_disable(struct usb_function *f) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + + DBG(cdev, "ecm deactivated\n"); + + if (ecm->port.in_ep->driver_data) { + bam_data_disconnect(&ecm->bam_port, USB_FUNC_ECM, + ecm->port_num); + if (ecm->xport != USB_GADGET_XPORT_BAM2BAM_IPA) + gether_qc_disconnect_name(&ecm->port, "ecm0"); + } else { + /* release EPs incase no set_alt(1) yet */ + ecm->port.in_ep->desc = NULL; + ecm->port.out_ep->desc = NULL; + } + + if (ecm->xport == USB_GADGET_XPORT_BAM2BAM_IPA && + gadget_is_dwc3(cdev->gadget)) { + msm_ep_unconfig(ecm->port.out_ep); + msm_ep_unconfig(ecm->port.in_ep); + } + + if (ecm->notify->driver_data) { + usb_ep_disable(ecm->notify); + ecm->notify->driver_data = NULL; + ecm->notify->desc = NULL; + } + + ecm->data_interface_up = false; +} + +static void ecm_qc_suspend(struct usb_function *f) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + bool remote_wakeup_allowed; + + /* Is DATA interface initialized? */ + if (!ecm->data_interface_up) { + pr_err("%s(): data interface not up\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; + + pr_debug("%s(): remote_wakeup:%d\n:", __func__, remote_wakeup_allowed); + if (!remote_wakeup_allowed) + __ecm->ecm_mdm_ready_trigger = false; + + bam_data_suspend(&ecm->bam_port, ecm->port_num, USB_FUNC_ECM, + remote_wakeup_allowed); + + pr_debug("ecm suspended\n"); +} + +static void ecm_qc_resume(struct usb_function *f) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + bool remote_wakeup_allowed; + + if (!ecm->data_interface_up) { + pr_err("%s(): data interface was not up\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; + + bam_data_resume(&ecm->bam_port, ecm->port_num, USB_FUNC_ECM, + remote_wakeup_allowed); + + if (!remote_wakeup_allowed) { + ecm->is_open = ecm->ecm_mdm_ready_trigger ? true : false; + ecm_qc_notify(ecm); + } + + pr_debug("ecm resumed\n"); +} + +/*-------------------------------------------------------------------------*/ + +/* + * Callbacks let us notify the host about connect/disconnect when the + * net device is opened or closed. + * + * For testing, note that link states on this side include both opened + * and closed variants of: + * + * - disconnected/unconfigured + * - configured but inactive (data alt 0) + * - configured and active (data alt 1) + * + * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and + * SET_INTERFACE (altsetting). Remember also that "configured" doesn't + * imply the host is actually polling the notification endpoint, and + * likewise that "active" doesn't imply it's actually using the data + * endpoints for traffic. + */ + +static void ecm_qc_open(struct qc_gether *geth) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(&geth->func); + DBG(ecm->port.func.config->cdev, "%s\n", __func__); + + ecm->is_open = true; + ecm_qc_notify(ecm); +} + +static void ecm_qc_close(struct qc_gether *geth) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(&geth->func); + + DBG(ecm->port.func.config->cdev, "%s\n", __func__); + + ecm->is_open = false; + ecm_qc_notify(ecm); +} + +/* Callback to let ECM_IPA trigger us when network interface is up */ +void ecm_mdm_ready(void) +{ + struct f_ecm_qc *ecm = __ecm; + int port_num; + + if (!ecm) { + pr_err("can't set ecm_ready_trigger, no ecm instance\n"); + return; + } + + if (ecm->ecm_mdm_ready_trigger) { + pr_err("already triggered - can't set ecm_ready_trigger\n"); + return; + } + + pr_debug("set ecm_ready_trigger\n"); + ecm->ecm_mdm_ready_trigger = true; + ecm->is_open = true; + ecm_qc_notify(ecm); + port_num = (u_bam_data_func_to_port(USB_FUNC_ECM, + ECM_QC_ACTIVE_PORT)); + if (port_num < 0) + return; + bam_data_start_rx_tx(port_num); +} + +/*-------------------------------------------------------------------------*/ + +/* ethernet function driver setup/binding */ + +static int +ecm_qc_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + + ecm->ctrl_id = status; + + ecm_qc_control_intf.bInterfaceNumber = status; + ecm_qc_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) { + pr_debug("no more interface IDs can be allocated\n"); + goto fail; + } + + ecm->data_id = status; + + ecm_qc_data_nop_intf.bInterfaceNumber = status; + ecm_qc_data_intf.bInterfaceNumber = status; + ecm_qc_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &ecm_qc_fs_in_desc); + if (!ep) { + pr_debug("can not allocate endpoint (fs_in)\n"); + goto fail; + } + + ecm->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &ecm_qc_fs_out_desc); + if (!ep) { + pr_debug("can not allocate endpoint (fs_out)\n"); + goto fail; + } + + ecm->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + /* NOTE: a status/notification endpoint is *OPTIONAL* but we + * don't treat it that way. It's simpler, and some newer CDC + * profiles (wireless handsets) no longer treat it as optional. + */ + ep = usb_ep_autoconfig(cdev->gadget, &ecm_qc_fs_notify_desc); + if (!ep) { + pr_debug("can not allocate endpoint (fs_notify)\n"); + goto fail; + } + ecm->notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!ecm->notify_req) { + pr_debug("can not allocate notification request\n"); + goto fail; + } + ecm->notify_req->buf = kmalloc(ECM_QC_STATUS_BYTECOUNT, GFP_KERNEL); + if (!ecm->notify_req->buf) + goto fail; + ecm->notify_req->context = ecm; + ecm->notify_req->complete = ecm_qc_notify_complete; + + /* copy descriptors, and track endpoint copies */ + f->fs_descriptors = usb_copy_descriptors(ecm_qc_fs_function); + if (!f->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(c->cdev->gadget)) { + ecm_qc_hs_in_desc.bEndpointAddress = + ecm_qc_fs_in_desc.bEndpointAddress; + ecm_qc_hs_out_desc.bEndpointAddress = + ecm_qc_fs_out_desc.bEndpointAddress; + ecm_qc_hs_notify_desc.bEndpointAddress = + ecm_qc_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(ecm_qc_hs_function); + if (!f->hs_descriptors) + goto fail; + } + + if (gadget_is_superspeed(c->cdev->gadget)) { + ecm_qc_ss_in_desc.bEndpointAddress = + ecm_qc_fs_in_desc.bEndpointAddress; + ecm_qc_ss_out_desc.bEndpointAddress = + ecm_qc_fs_out_desc.bEndpointAddress; + ecm_qc_ss_notify_desc.bEndpointAddress = + ecm_qc_fs_notify_desc.bEndpointAddress; + + f->ss_descriptors = usb_copy_descriptors(ecm_qc_ss_function); + if (!f->hs_descriptors) + goto fail; + } + + /* NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + ecm->port.open = ecm_qc_open; + ecm->port.close = ecm_qc_close; + + DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + ecm->port.in_ep->name, ecm->port.out_ep->name, + ecm->notify->name); + return 0; + +fail: + + if (f->ss_descriptors) + usb_free_descriptors(f->ss_descriptors); + if (f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + if (f->fs_descriptors) + usb_free_descriptors(f->fs_descriptors); + + if (ecm->notify_req) { + kfree(ecm->notify_req->buf); + usb_ep_free_request(ecm->notify, ecm->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (ecm->notify) + ecm->notify->driver_data = NULL; + if (ecm->port.out_ep->desc) + ecm->port.out_ep->driver_data = NULL; + if (ecm->port.in_ep->desc) + ecm->port.in_ep->driver_data = NULL; + + pr_err("%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +ecm_qc_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ecm_qc *ecm = func_to_ecm_qc(f); + + DBG(c->cdev, "ecm unbind\n"); + + 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); + + kfree(ecm->notify_req->buf); + usb_ep_free_request(ecm->notify, ecm->notify_req); + + ecm_qc_string_defs[1].s = NULL; + + if (ecm->xport == USB_GADGET_XPORT_BAM2BAM_IPA) { + /* + * call flush_workqueue to make sure that any pending + * disconnect_work() from u_bam_data.c file is being + * flushed before calling this rndis_ipa_cleanup API + * as rndis ipa disconnect API is required to be + * called before this. + */ + bam_data_flush_workqueue(); + ecm_ipa_cleanup(ipa_params.private); + } + + kfree(ecm); + __ecm = NULL; +} + +/** + * ecm_qc_bind_config - add CDC Ethernet network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * @xport_name: data path transport type name ("BAM2BAM" or "BAM2BAM_IPA") + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_qc_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int +ecm_qc_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + char *xport_name) +{ + struct f_ecm_qc *ecm; + int status; + + if (!can_support_ecm(c->cdev->gadget) || !ethaddr) + return -EINVAL; + + pr_debug("data transport type is %s\n", xport_name); + + /* maybe allocate device-global string IDs */ + if (ecm_qc_string_defs[0].id == 0) { + + /* control interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ecm_qc_string_defs[0].id = status; + ecm_qc_control_intf.iInterface = status; + + /* data interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ecm_qc_string_defs[2].id = status; + ecm_qc_data_intf.iInterface = status; + + /* MAC address */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ecm_qc_string_defs[1].id = status; + ecm_qc_desc.iMACAddress = status; + } + + /* allocate and initialize one new instance */ + ecm = kzalloc(sizeof *ecm, GFP_KERNEL); + if (!ecm) + return -ENOMEM; + __ecm = ecm; + + ecm->xport = str_to_xport(xport_name); + pr_debug("set xport = %d\n", ecm->xport); + + /* export host's Ethernet address in CDC format */ + if (ecm->xport == USB_GADGET_XPORT_BAM2BAM_IPA) { + gether_qc_get_macs(ipa_params.device_ethaddr, + ipa_params.host_ethaddr); + snprintf(ecm->ethaddr, sizeof ecm->ethaddr, + "%02X%02X%02X%02X%02X%02X", + ipa_params.host_ethaddr[0], ipa_params.host_ethaddr[1], + ipa_params.host_ethaddr[2], ipa_params.host_ethaddr[3], + ipa_params.host_ethaddr[4], ipa_params.host_ethaddr[5]); + ipa_params.device_ready_notify = ecm_mdm_ready; + } else + snprintf(ecm->ethaddr, sizeof ecm->ethaddr, + "%02X%02X%02X%02X%02X%02X", + ethaddr[0], ethaddr[1], ethaddr[2], + ethaddr[3], ethaddr[4], ethaddr[5]); + + ecm_qc_string_defs[1].s = ecm->ethaddr; + + ecm->port.cdc_filter = DEFAULT_FILTER; + + ecm->port.func.name = "cdc_ethernet"; + ecm->port.func.strings = ecm_qc_strings; + /* descriptors are per-instance copies */ + ecm->port.func.bind = ecm_qc_bind; + ecm->port.func.unbind = ecm_qc_unbind; + ecm->port.func.set_alt = ecm_qc_set_alt; + ecm->port.func.get_alt = ecm_qc_get_alt; + ecm->port.func.setup = ecm_qc_setup; + ecm->port.func.disable = ecm_qc_disable; + ecm->port.func.suspend = ecm_qc_suspend; + ecm->port.func.resume = ecm_qc_resume; + ecm->ecm_mdm_ready_trigger = false; + + status = usb_add_function(c, &ecm->port.func); + if (status) { + pr_err("failed to add function\n"); + ecm_qc_string_defs[1].s = NULL; + kfree(ecm); + __ecm = NULL; + return status; + } + + if (ecm->xport != USB_GADGET_XPORT_BAM2BAM_IPA) + return status; + + pr_debug("setting ecm_ipa, host_ethaddr=%pM, device_ethaddr=%pM", + ipa_params.host_ethaddr, ipa_params.device_ethaddr); + status = ecm_ipa_init(&ipa_params); + if (status) { + pr_err("failed to initialize ecm_ipa\n"); + ecm_qc_string_defs[1].s = NULL; + kfree(ecm); + __ecm = NULL; + + } else { + pr_debug("ecm_ipa successful created\n"); + } + + return status; +} + +static int ecm_qc_init(void) +{ + int ret; + + pr_debug("initialize ecm qc port instance\n"); + + ret = bam_data_setup(USB_FUNC_ECM, ECM_QC_NO_PORTS); + if (ret) { + pr_err("bam_data_setup failed err: %d\n", ret); + return ret; + } + + return ret; +} diff --git a/drivers/usb/gadget/function/f_qc_rndis.c b/drivers/usb/gadget/function/f_qc_rndis.c new file mode 100644 index 000000000000..6a616595f4d4 --- /dev/null +++ b/drivers/usb/gadget/function/f_qc_rndis.c @@ -0,0 +1,1421 @@ +/* + * f_qc_rndis.c -- RNDIS link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (mina86@mina86.com) + * Copyright (c) 2012-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 + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include + +#include + +#include "u_ether.h" +#include "u_qc_ether.h" +#include "rndis.h" +#include "u_bam_data.h" +#include + +unsigned int rndis_dl_max_xfer_size = 9216; +module_param(rndis_dl_max_xfer_size, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(rndis_dl_max_xfer_size, + "Max size of bus transfer to host"); + +/* + * This function is an RNDIS Ethernet port -- a Microsoft protocol that's + * been promoted instead of the standard CDC Ethernet. The published RNDIS + * spec is ambiguous, incomplete, and needlessly complex. Variants such as + * ActiveSync have even worse status in terms of specification. + * + * In short: it's a protocol controlled by (and for) Microsoft, not for an + * Open ecosystem or markets. Linux supports it *only* because Microsoft + * doesn't support the CDC Ethernet standard. + * + * The RNDIS data transfer model is complex, with multiple Ethernet packets + * per USB message, and out of band data. The control model is built around + * what's essentially an "RNDIS RPC" protocol. It's all wrapped in a CDC ACM + * (modem, not Ethernet) veneer, with those ACM descriptors being entirely + * useless (they're ignored). RNDIS expects to be the only function in its + * configuration, so it's no real help if you need composite devices; and + * it expects to be the first configuration too. + * + * There is a single technical advantage of RNDIS over CDC Ethernet, if you + * discount the fluff that its RPC can be made to deliver: it doesn't need + * a NOP altsetting for the data interface. That lets it work on some of the + * "so smart it's stupid" hardware which takes over configuration changes + * from the software, and adds restrictions like "no altsettings". + * + * Unfortunately MSFT's RNDIS drivers are buggy. They hang or oops, and + * have all sorts of contrary-to-specification oddities that can prevent + * them from working sanely. Since bugfixes (or accurate specs, letting + * Linux work around those bugs) are unlikely to ever come from MSFT, you + * may want to avoid using RNDIS on purely operational grounds. + * + * Omissions from the RNDIS 1.0 specification include: + * + * - Power management ... references data that's scattered around lots + * of other documentation, which is incorrect/incomplete there too. + * + * - There are various undocumented protocol requirements, like the need + * to send garbage in some control-OUT messages. + * + * - MS-Windows drivers sometimes emit undocumented requests. + * + * This function is based on RNDIS link function driver and + * contains MSM specific implementation. + */ + +struct f_rndis_qc { + struct qc_gether port; + u8 ctrl_id, data_id; + u8 ethaddr[ETH_ALEN]; + u32 vendorID; + u8 ul_max_pkt_per_xfer; + u8 pkt_alignment_factor; + u32 max_pkt_size; + const char *manufacturer; + int config; + atomic_t ioctl_excl; + atomic_t open_excl; + + struct usb_ep *notify; + struct usb_request *notify_req; + atomic_t notify_count; + struct data_port bam_port; + enum transport_type xport; + u8 port_num; + bool net_ready_trigger; +}; + +static struct ipa_usb_init_params rndis_ipa_params; +static spinlock_t rndis_lock; +static bool rndis_ipa_supported; +static void rndis_qc_open(struct qc_gether *geth); + +static inline struct f_rndis_qc *func_to_rndis_qc(struct usb_function *f) +{ + return container_of(f, struct f_rndis_qc, port.func); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static unsigned int rndis_qc_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; +} + +/*-------------------------------------------------------------------------*/ + +#define RNDIS_QC_LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define RNDIS_QC_STATUS_BYTECOUNT 8 /* 8 bytes data */ + +/* currently only one rndis instance is supported - port + * index 0. + */ +#define RNDIS_QC_NO_PORTS 1 +#define RNDIS_QC_ACTIVE_PORT 0 + +/* default max packets per tarnsfer value */ +#define DEFAULT_MAX_PKT_PER_XFER 15 + +/* default pkt alignment factor */ +#define DEFAULT_PKT_ALIGNMENT_FACTOR 4 + +#define RNDIS_QC_IOCTL_MAGIC 'i' +#define RNDIS_QC_GET_MAX_PKT_PER_XFER _IOR(RNDIS_QC_IOCTL_MAGIC, 1, u8) +#define RNDIS_QC_GET_MAX_PKT_SIZE _IOR(RNDIS_QC_IOCTL_MAGIC, 2, u32) + + +/* interface descriptor: */ + +static struct usb_interface_descriptor rndis_qc_control_intf = { + .bLength = sizeof rndis_qc_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_qc_header_desc = { + .bLength = sizeof rndis_qc_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_qc_call_mgmt_descriptor = { + .bLength = sizeof rndis_qc_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_qc_acm_descriptor = { + .bLength = sizeof rndis_qc_acm_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + + .bmCapabilities = 0x00, +}; + +static struct usb_cdc_union_desc rndis_qc_union_desc = { + .bLength = sizeof(rndis_qc_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_qc_data_intf = { + .bLength = sizeof rndis_qc_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_qc_iad_descriptor = { + .bLength = sizeof rndis_qc_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_qc_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(RNDIS_QC_STATUS_BYTECOUNT), + .bInterval = 1 << RNDIS_QC_LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor rndis_qc_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_qc_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 *eth_qc_fs_function[] = { + (struct usb_descriptor_header *) &rndis_qc_iad_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_qc_control_intf, + (struct usb_descriptor_header *) &rndis_qc_header_desc, + (struct usb_descriptor_header *) &rndis_qc_call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_qc_acm_descriptor, + (struct usb_descriptor_header *) &rndis_qc_union_desc, + (struct usb_descriptor_header *) &rndis_qc_fs_notify_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_qc_data_intf, + (struct usb_descriptor_header *) &rndis_qc_fs_in_desc, + (struct usb_descriptor_header *) &rndis_qc_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor rndis_qc_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(RNDIS_QC_STATUS_BYTECOUNT), + .bInterval = RNDIS_QC_LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor rndis_qc_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_qc_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 *eth_qc_hs_function[] = { + (struct usb_descriptor_header *) &rndis_qc_iad_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_qc_control_intf, + (struct usb_descriptor_header *) &rndis_qc_header_desc, + (struct usb_descriptor_header *) &rndis_qc_call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_qc_acm_descriptor, + (struct usb_descriptor_header *) &rndis_qc_union_desc, + (struct usb_descriptor_header *) &rndis_qc_hs_notify_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_qc_data_intf, + (struct usb_descriptor_header *) &rndis_qc_hs_in_desc, + (struct usb_descriptor_header *) &rndis_qc_hs_out_desc, + NULL, +}; + +/* super speed support: */ + +static struct usb_endpoint_descriptor rndis_qc_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(STATUS_BYTECOUNT), + .bInterval = RNDIS_QC_LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_ss_ep_comp_descriptor rndis_qc_ss_intr_comp_desc = { + .bLength = sizeof 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(STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor rndis_qc_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_qc_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_qc_ss_bulk_comp_desc = { + .bLength = sizeof ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *eth_qc_ss_function[] = { + (struct usb_descriptor_header *) &rndis_qc_iad_descriptor, + + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_qc_control_intf, + (struct usb_descriptor_header *) &rndis_qc_header_desc, + (struct usb_descriptor_header *) &rndis_qc_call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_qc_acm_descriptor, + (struct usb_descriptor_header *) &rndis_qc_union_desc, + (struct usb_descriptor_header *) &rndis_qc_ss_notify_desc, + (struct usb_descriptor_header *) &rndis_qc_ss_intr_comp_desc, + + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_qc_data_intf, + (struct usb_descriptor_header *) &rndis_qc_ss_in_desc, + (struct usb_descriptor_header *) &rndis_qc_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &rndis_qc_ss_out_desc, + (struct usb_descriptor_header *) &rndis_qc_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string rndis_qc_string_defs[] = { + [0].s = "RNDIS Communications Control", + [1].s = "RNDIS Ethernet Data", + [2].s = "RNDIS", + { } /* end of list */ +}; + +static struct usb_gadget_strings rndis_qc_string_table = { + .language = 0x0409, /* en-us */ + .strings = rndis_qc_string_defs, +}; + +static struct usb_gadget_strings *rndis_qc_strings[] = { + &rndis_qc_string_table, + NULL, +}; + +struct f_rndis_qc *_rndis_qc; + +static inline int rndis_qc_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -EBUSY; + } +} + +static inline void rndis_qc_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/*-------------------------------------------------------------------------*/ + +static struct sk_buff *rndis_qc_add_header(struct qc_gether *port, + struct sk_buff *skb) +{ + struct sk_buff *skb2; + + skb2 = skb_realloc_headroom(skb, sizeof(struct rndis_packet_msg_type)); + if (skb2) + rndis_add_hdr(skb2); + + dev_kfree_skb_any(skb); + return skb2; +} + +int rndis_qc_rm_hdr(struct qc_gether *port, + struct sk_buff *skb, + struct sk_buff_head *list) +{ + /* tmp points to a struct rndis_packet_msg_type */ + __le32 *tmp = (void *)skb->data; + + /* MessageType, MessageLength */ + if (cpu_to_le32(RNDIS_MSG_PACKET) + != get_unaligned(tmp++)) { + dev_kfree_skb_any(skb); + return -EINVAL; + } + tmp++; + + /* DataOffset, DataLength */ + if (!skb_pull(skb, get_unaligned_le32(tmp++) + 8)) { + dev_kfree_skb_any(skb); + return -EOVERFLOW; + } + skb_trim(skb, get_unaligned_le32(tmp++)); + + skb_queue_tail(list, skb); + return 0; +} + + +static void rndis_qc_response_available(void *_rndis) +{ + struct f_rndis_qc *rndis = _rndis; + struct usb_request *req = rndis->notify_req; + __le32 *data = req->buf; + int status; + + if (atomic_inc_return(&rndis->notify_count) != 1) + return; + + if (!rndis->notify->driver_data) + return; + + /* Send RNDIS RESPONSE_AVAILABLE notification; a + * USB_CDC_NOTIFY_RESPONSE_AVAILABLE "should" work too + * + * This is the only notification defined by RNDIS. + */ + data[0] = cpu_to_le32(1); + data[1] = cpu_to_le32(0); + + status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&rndis->notify_count); + pr_info("notify/0 --> %d\n", status); + } +} + +static void rndis_qc_response_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_rndis_qc *rndis = req->context; + int status = req->status; + struct usb_composite_dev *cdev; + + if (!rndis->port.func.config || !rndis->port.func.config->cdev) { + pr_err("%s(): cdev or config is NULL.\n", __func__); + return; + } else { + cdev = rndis->port.func.config->cdev; + } + + /* after TX: + * - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control) + * - RNDIS_RESPONSE_AVAILABLE (status/irq) + */ + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + atomic_set(&rndis->notify_count, 0); + break; + default: + pr_info("RNDIS %s response error %d, %d/%d\n", + ep->name, status, + req->actual, req->length); + /* FALLTHROUGH */ + case 0: + if (ep != rndis->notify) + break; + + /* handle multiple pending RNDIS_RESPONSE_AVAILABLE + * notifications by resending until we're done + */ + if (atomic_dec_and_test(&rndis->notify_count)) + break; + status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&rndis->notify_count); + DBG(cdev, "notify/1 --> %d\n", status); + } + break; + } +} + +static void rndis_qc_command_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_rndis_qc *rndis = req->context; + int status; + rndis_init_msg_type *buf; + u32 ul_max_xfer_size, dl_max_xfer_size; + + /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */ + status = rndis_msg_parser(rndis->config, (u8 *) req->buf); + if (status < 0) + pr_err("RNDIS command error %d, %d/%d\n", + status, req->actual, req->length); + + buf = (rndis_init_msg_type *)req->buf; + + if (buf->MessageType == RNDIS_MSG_INIT) { + ul_max_xfer_size = rndis_get_ul_max_xfer_size(rndis->config); + u_bam_data_set_ul_max_xfer_size(ul_max_xfer_size); + /* + * For consistent data throughput from IPA, it is required to + * fine tune aggregation byte limit as 7KB. RNDIS IPA driver + * use provided this value to calculate aggregation byte limit + * and program IPA hardware for aggregation. + * Host provides 8KB or 16KB as Max Transfer size, hence select + * minimum out of host provided value and optimum transfer size + * to get 7KB as aggregation byte limit. + */ + if (rndis_dl_max_xfer_size) + dl_max_xfer_size = min_t(u32, rndis_dl_max_xfer_size, + rndis_get_dl_max_xfer_size(rndis->config)); + else + dl_max_xfer_size = + rndis_get_dl_max_xfer_size(rndis->config); + u_bam_data_set_dl_max_xfer_size(dl_max_xfer_size); + } +} + +static int +rndis_qc_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + pr_debug("%s: Enter\n", __func__); + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* RNDIS uses the CDC command encapsulation mechanism to implement + * an RPC scheme, with much getting/setting of attributes by OID. + */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_value || w_index != rndis->ctrl_id) + goto invalid; + /* read the request; process it later */ + value = w_length; + req->complete = rndis_qc_command_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: + if (w_value || w_index != rndis->ctrl_id) + goto invalid; + else { + u8 *buf; + u32 n; + + /* return the result */ + buf = rndis_get_next_response(rndis->config, &n); + if (buf) { + memcpy(req->buf, buf, n); + req->complete = rndis_qc_response_complete; + rndis_free_response(rndis->config, buf); + value = n; + } + /* else stalls ... spec says to avoid that */ + } + break; + + default: +invalid: + VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "rndis req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->context = rndis; + req->zero = (value < w_length); + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + pr_err("rndis response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int rndis_qc_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0 */ + + if (intf == rndis->ctrl_id) { + if (rndis->notify->driver_data) { + VDBG(cdev, "reset rndis control %d\n", intf); + usb_ep_disable(rndis->notify); + } + if (!rndis->notify->desc) { + VDBG(cdev, "init rndis ctrl %d\n", intf); + if (config_ep_by_speed(cdev->gadget, f, rndis->notify)) + goto fail; + } + usb_ep_enable(rndis->notify); + rndis->notify->driver_data = rndis; + + } else if (intf == rndis->data_id) { + struct net_device *net; + + rndis->net_ready_trigger = false; + if (rndis->port.in_ep->driver_data) { + DBG(cdev, "reset rndis\n"); + /* rndis->port is needed for disconnecting the BAM data + * path. Only after the BAM data path is disconnected, + * we can disconnect the port from the network layer. + */ + bam_data_disconnect(&rndis->bam_port, USB_FUNC_RNDIS, + rndis->port_num); + + if (rndis->xport != USB_GADGET_XPORT_BAM2BAM_IPA) + gether_qc_disconnect_name(&rndis->port, + "rndis0"); + } + + if (!rndis->port.in_ep->desc || !rndis->port.out_ep->desc) { + DBG(cdev, "init rndis\n"); + if (config_ep_by_speed(cdev->gadget, f, + rndis->port.in_ep) || + config_ep_by_speed(cdev->gadget, f, + rndis->port.out_ep)) { + rndis->port.in_ep->desc = NULL; + rndis->port.out_ep->desc = NULL; + goto fail; + } + } + + /* Avoid ZLPs; they can be troublesome. */ + rndis->port.is_zlp_ok = false; + + /* RNDIS should be in the "RNDIS uninitialized" state, + * either never activated or after rndis_uninit(). + * + * We don't want data to flow here until a nonzero packet + * filter is set, at which point it enters "RNDIS data + * initialized" state ... but we do want the endpoints + * to be activated. It's a strange little state. + * + * REVISIT the RNDIS gadget code has done this wrong for a + * very long time. We need another call to the link layer + * code -- gether_updown(...bool) maybe -- to do it right. + */ + rndis->port.cdc_filter = 0; + + rndis->bam_port.cdev = cdev; + rndis->bam_port.func = &rndis->port.func; + rndis->bam_port.in = rndis->port.in_ep; + rndis->bam_port.out = rndis->port.out_ep; + + if (bam_data_connect(&rndis->bam_port, rndis->xport, + rndis->port_num, USB_FUNC_RNDIS)) + goto fail; + + DBG(cdev, "RNDIS RX/TX early activation ...\n"); + if (rndis->xport != USB_GADGET_XPORT_BAM2BAM_IPA) { + net = gether_qc_connect_name(&rndis->port, "rndis0", + false); + } else { + rndis_qc_open(&rndis->port); + net = gether_qc_get_net("rndis0"); + } + if (IS_ERR(net)) + return PTR_ERR(net); + + rndis_set_param_dev(rndis->config, net, + &rndis->port.cdc_filter); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +static void rndis_qc_disable(struct usb_function *f) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (!rndis->notify->driver_data) + return; + + pr_info("rndis deactivated\n"); + + rndis_uninit(rndis->config); + bam_data_disconnect(&rndis->bam_port, USB_FUNC_RNDIS, rndis->port_num); + if (rndis->xport != USB_GADGET_XPORT_BAM2BAM_IPA) + gether_qc_disconnect_name(&rndis->port, "rndis0"); + + if (rndis->xport == USB_GADGET_XPORT_BAM2BAM_IPA && + gadget_is_dwc3(cdev->gadget)) { + msm_ep_unconfig(rndis->port.out_ep); + msm_ep_unconfig(rndis->port.in_ep); + } + usb_ep_disable(rndis->notify); + rndis->notify->driver_data = NULL; +} + +static void rndis_qc_suspend(struct usb_function *f) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(f); + bool remote_wakeup_allowed; + + 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; + + pr_info("%s(): start rndis suspend: remote_wakeup_allowed:%d\n:", + __func__, remote_wakeup_allowed); + + if (!remote_wakeup_allowed) { + /* This is required as Linux host side RNDIS driver doesn't + * send RNDIS_MESSAGE_PACKET_FILTER before suspending USB bus. + * Hence we perform same operations explicitly here for Linux + * host case. In case of windows, this RNDIS state machine is + * already updated due to receiving of PACKET_FILTER. + */ + rndis_flow_control(rndis->config, true); + pr_debug("%s(): Disconnecting\n", __func__); + } + + bam_data_suspend(&rndis->bam_port, rndis->port_num, USB_FUNC_RNDIS, + remote_wakeup_allowed); + pr_debug("rndis suspended\n"); +} + +static void rndis_qc_resume(struct usb_function *f) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(f); + bool remote_wakeup_allowed; + + pr_debug("%s: rndis resumed\n", __func__); + + /* Nothing to do if DATA interface wasn't initialized */ + if (!rndis->bam_port.cdev) { + pr_debug("data interface was not up\n"); + 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; + + bam_data_resume(&rndis->bam_port, rndis->port_num, USB_FUNC_RNDIS, + remote_wakeup_allowed); + + if (!remote_wakeup_allowed) { + if (rndis->xport == USB_GADGET_XPORT_BAM2BAM_IPA) + rndis_qc_open(&rndis->port); + /* + * Linux Host doesn't sends RNDIS_MSG_INIT or non-zero value + * set with RNDIS_MESSAGE_PACKET_FILTER after performing bus + * resume. Hence trigger USB IPA transfer functionality + * explicitly here. For Windows host case is also being + * handle with RNDIS state machine. + */ + rndis_flow_control(rndis->config, false); + } + + pr_debug("%s: RNDIS resume completed\n", __func__); +} + +/*-------------------------------------------------------------------------*/ + +/* + * This isn't quite the same mechanism as CDC Ethernet, since the + * notification scheme passes less data, but the same set of link + * states must be tested. A key difference is that altsettings are + * not used to tell whether the link should send packets or not. + */ + +static void rndis_qc_open(struct qc_gether *geth) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(&geth->func); + struct usb_composite_dev *cdev = geth->func.config->cdev; + + DBG(cdev, "%s\n", __func__); + + rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, + rndis_qc_bitrate(cdev->gadget) / 100); + rndis_signal_connect(rndis->config); +} + +static void rndis_qc_close(struct qc_gether *geth) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(&geth->func); + + DBG(geth->func.config->cdev, "%s\n", __func__); + + rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0); + rndis_signal_disconnect(rndis->config); +} + +/*-------------------------------------------------------------------------*/ + +/* ethernet function driver setup/binding */ + +static int +rndis_qc_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_rndis_qc *rndis = func_to_rndis_qc(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + rndis->ctrl_id = status; + rndis_qc_iad_descriptor.bFirstInterface = status; + + rndis_qc_control_intf.bInterfaceNumber = status; + rndis_qc_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + rndis->data_id = status; + + rndis_qc_data_intf.bInterfaceNumber = status; + rndis_qc_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &rndis_qc_fs_in_desc); + if (!ep) + goto fail; + rndis->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &rndis_qc_fs_out_desc); + if (!ep) + goto fail; + rndis->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + /* NOTE: a status/notification endpoint is, strictly speaking, + * optional. We don't treat it that way though! It's simpler, + * and some newer profiles don't treat it as optional. + */ + ep = usb_ep_autoconfig(cdev->gadget, &rndis_qc_fs_notify_desc); + if (!ep) + goto fail; + rndis->notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!rndis->notify_req) + goto fail; + rndis->notify_req->buf = kmalloc(RNDIS_QC_STATUS_BYTECOUNT, GFP_KERNEL); + if (!rndis->notify_req->buf) + goto fail; + rndis->notify_req->length = RNDIS_QC_STATUS_BYTECOUNT; + rndis->notify_req->context = rndis; + rndis->notify_req->complete = rndis_qc_response_complete; + + /* copy descriptors, and track endpoint copies */ + f->fs_descriptors = usb_copy_descriptors(eth_qc_fs_function); + if (!f->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(c->cdev->gadget)) { + rndis_qc_hs_in_desc.bEndpointAddress = + rndis_qc_fs_in_desc.bEndpointAddress; + rndis_qc_hs_out_desc.bEndpointAddress = + rndis_qc_fs_out_desc.bEndpointAddress; + rndis_qc_hs_notify_desc.bEndpointAddress = + rndis_qc_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(eth_qc_hs_function); + + if (!f->hs_descriptors) + goto fail; + } + + if (gadget_is_superspeed(c->cdev->gadget)) { + rndis_qc_ss_in_desc.bEndpointAddress = + rndis_qc_fs_in_desc.bEndpointAddress; + rndis_qc_ss_out_desc.bEndpointAddress = + rndis_qc_fs_out_desc.bEndpointAddress; + rndis_qc_ss_notify_desc.bEndpointAddress = + rndis_qc_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(eth_qc_ss_function); + if (!f->ss_descriptors) + goto fail; + } + + rndis->port.open = rndis_qc_open; + rndis->port.close = rndis_qc_close; + + status = rndis_register(rndis_qc_response_available, rndis, + bam_data_flow_control_enable); + if (status < 0) + goto fail; + rndis->config = status; + + rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0); + rndis_set_host_mac(rndis->config, rndis->ethaddr); + + if (rndis->manufacturer && rndis->vendorID && + rndis_set_param_vendor(rndis->config, rndis->vendorID, + rndis->manufacturer)) + goto fail; + + pr_debug("%s(): max_pkt_per_xfer:%d\n", __func__, + rndis->ul_max_pkt_per_xfer); + rndis_set_max_pkt_xfer(rndis->config, rndis->ul_max_pkt_per_xfer); + + /* In case of aggregated packets QC device will request + * aliment to 4 (2^2). + */ + pr_debug("%s(): pkt_alignment_factor:%d\n", __func__, + rndis->pkt_alignment_factor); + rndis_set_pkt_alignment_factor(rndis->config, + rndis->pkt_alignment_factor); + + /* NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + rndis->port.in_ep->name, rndis->port.out_ep->name, + rndis->notify->name); + return 0; + +fail: + if (gadget_is_superspeed(c->cdev->gadget) && f->ss_descriptors) + usb_free_descriptors(f->ss_descriptors); + if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + if (f->fs_descriptors) + usb_free_descriptors(f->fs_descriptors); + + if (rndis->notify_req) { + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (rndis->notify) + rndis->notify->driver_data = NULL; + if (rndis->port.out_ep->desc) + rndis->port.out_ep->driver_data = NULL; + if (rndis->port.in_ep->desc) + rndis->port.in_ep->driver_data = NULL; + + pr_err("%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +rndis_qc_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rndis_qc *rndis = func_to_rndis_qc(f); + unsigned long flags; + + pr_debug("rndis_qc_unbind: free\n"); + rndis_deregister(rndis->config); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->fs_descriptors); + + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + + if (rndis->xport == USB_GADGET_XPORT_BAM2BAM_IPA) { + /* + * call flush_workqueue to make sure that any pending + * disconnect_work() from u_bam_data.c file is being + * flushed before calling this rndis_ipa_cleanup API + * as rndis ipa disconnect API is required to be + * called before this. + */ + bam_data_flush_workqueue(); + rndis_ipa_cleanup(rndis_ipa_params.private); + rndis_ipa_supported = false; + } + + spin_lock_irqsave(&rndis_lock, flags); + kfree(rndis); + _rndis_qc = NULL; + spin_unlock_irqrestore(&rndis_lock, flags); +} + +void rndis_ipa_reset_trigger(void) +{ + struct f_rndis_qc *rndis; + unsigned long flags; + + spin_lock_irqsave(&rndis_lock, flags); + rndis = _rndis_qc; + if (!rndis) { + pr_err("%s: No RNDIS instance", __func__); + spin_unlock_irqrestore(&rndis_lock, flags); + return; + } + + rndis->net_ready_trigger = false; + spin_unlock_irqrestore(&rndis_lock, flags); +} + +/* + * Callback let RNDIS_IPA trigger us when network interface is up + * and userspace is ready to answer DHCP requests + */ +void rndis_net_ready_notify(void) +{ + struct f_rndis_qc *rndis; + unsigned long flags; + int port_num; + + spin_lock_irqsave(&rndis_lock, flags); + rndis = _rndis_qc; + if (!rndis) { + pr_err("%s: No RNDIS instance", __func__); + spin_unlock_irqrestore(&rndis_lock, flags); + return; + } + if (rndis->net_ready_trigger) { + pr_err("%s: Already triggered", __func__); + spin_unlock_irqrestore(&rndis_lock, flags); + return; + } + + pr_debug("%s: Set net_ready_trigger", __func__); + rndis->net_ready_trigger = true; + spin_unlock_irqrestore(&rndis_lock, flags); + port_num = (u_bam_data_func_to_port(USB_FUNC_RNDIS, + RNDIS_QC_ACTIVE_PORT)); + if (port_num < 0) + return; + bam_data_start_rx_tx(port_num); +} + + +/* Some controllers can't support RNDIS ... */ +static inline bool can_support_rndis_qc(struct usb_configuration *c) +{ + /* everything else is *presumably* fine */ + return true; +} + +/** + * rndis_qc_bind_config - add RNDIS network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int +rndis_qc_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +{ + return rndis_qc_bind_config_vendor(c, ethaddr, 0, NULL, 1, 0, NULL); +} + +int +rndis_qc_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer, + u8 max_pkt_per_xfer, + u8 pkt_alignment_factor, + char *xport_name) +{ + struct f_rndis_qc *rndis; + int status; + + if (!can_support_rndis_qc(c) || !ethaddr) { + pr_debug("%s: invalid argument\n", __func__); + return -EINVAL; + } + + /* maybe allocate device-global string IDs */ + if (rndis_qc_string_defs[0].id == 0) { + + /* control interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + rndis_qc_string_defs[0].id = status; + rndis_qc_control_intf.iInterface = status; + + /* data interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + rndis_qc_string_defs[1].id = status; + rndis_qc_data_intf.iInterface = status; + + /* IAD iFunction label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + rndis_qc_string_defs[2].id = status; + rndis_qc_iad_descriptor.iFunction = status; + } + + /* allocate and initialize one new instance */ + status = -ENOMEM; + rndis = kzalloc(sizeof *rndis, GFP_KERNEL); + if (!rndis) { + pr_err("%s: fail allocate and initialize new instance\n", + __func__); + goto fail; + } + + rndis->xport = str_to_xport(xport_name); + + /* export host's Ethernet address in CDC format */ + if (rndis->xport == USB_GADGET_XPORT_BAM2BAM_IPA) { + gether_qc_get_macs(rndis_ipa_params.device_ethaddr, + rndis_ipa_params.host_ethaddr); + pr_debug("setting host_ethaddr=%pM, device_ethaddr=%pM\n", + rndis_ipa_params.host_ethaddr, + rndis_ipa_params.device_ethaddr); + rndis_ipa_supported = true; + memcpy(rndis->ethaddr, &rndis_ipa_params.host_ethaddr, + ETH_ALEN); + rndis_ipa_params.device_ready_notify = rndis_net_ready_notify; + } else + memcpy(rndis->ethaddr, ethaddr, ETH_ALEN); + + rndis->vendorID = vendorID; + rndis->manufacturer = manufacturer; + + /* if max_pkt_per_xfer was not configured set to default value */ + rndis->ul_max_pkt_per_xfer = + max_pkt_per_xfer ? max_pkt_per_xfer : + DEFAULT_MAX_PKT_PER_XFER; + u_bam_data_set_ul_max_pkt_num(rndis->ul_max_pkt_per_xfer); + + /* + * Check no RNDIS aggregation, and alignment if not mentioned, + * use alignment factor as zero. If aggregated RNDIS data transfer, + * max packet per transfer would be default if it is not set + * explicitly, and same way use alignment factor as 2 by default. + * This would eliminate need of writing to sysfs if default RNDIS + * aggregation setting required. Writing to both sysfs entries, + * those values will always override default values. + */ + if ((rndis->pkt_alignment_factor == 0) && + (rndis->ul_max_pkt_per_xfer == 1)) + rndis->pkt_alignment_factor = 0; + else + rndis->pkt_alignment_factor = pkt_alignment_factor ? + pkt_alignment_factor : + DEFAULT_PKT_ALIGNMENT_FACTOR; + + /* RNDIS activates when the host changes this filter */ + rndis->port.cdc_filter = 0; + + /* RNDIS has special (and complex) framing */ + rndis->port.header_len = sizeof(struct rndis_packet_msg_type); + rndis->port.wrap = rndis_qc_add_header; + rndis->port.unwrap = rndis_qc_rm_hdr; + + rndis->port.func.name = "rndis"; + rndis->port.func.strings = rndis_qc_strings; + /* descriptors are per-instance copies */ + rndis->port.func.bind = rndis_qc_bind; + rndis->port.func.unbind = rndis_qc_unbind; + rndis->port.func.set_alt = rndis_qc_set_alt; + rndis->port.func.setup = rndis_qc_setup; + rndis->port.func.disable = rndis_qc_disable; + rndis->port.func.suspend = rndis_qc_suspend; + rndis->port.func.resume = rndis_qc_resume; + + _rndis_qc = rndis; + + if (rndis->xport == USB_GADGET_XPORT_BAM2BAM_IPA) { + status = rndis_ipa_init(&rndis_ipa_params); + if (status) { + pr_err("%s: failed to init rndis_ipa\n", __func__); + goto fail; + } + } + + status = usb_add_function(c, &rndis->port.func); + if (status) { + if (rndis->xport == USB_GADGET_XPORT_BAM2BAM_IPA) + rndis_ipa_cleanup(rndis_ipa_params.private); + goto fail; + } + + return 0; + +fail: + kfree(rndis); + _rndis_qc = NULL; + return status; +} + +static int rndis_qc_open_dev(struct inode *ip, struct file *fp) +{ + pr_info("Open rndis QC driver\n"); + + if (!_rndis_qc) { + pr_err("rndis_qc_dev not created yet\n"); + return -ENODEV; + } + + if (rndis_qc_lock(&_rndis_qc->open_excl)) { + pr_err("Already opened\n"); + return -EBUSY; + } + + fp->private_data = _rndis_qc; + pr_info("rndis QC file opened\n"); + + return 0; +} + +static int rndis_qc_release_dev(struct inode *ip, struct file *fp) +{ + struct f_rndis_qc *rndis = fp->private_data; + + pr_info("Close rndis QC file\n"); + rndis_qc_unlock(&rndis->open_excl); + + return 0; +} + +static long rndis_qc_ioctl(struct file *fp, unsigned cmd, unsigned long arg) +{ + struct f_rndis_qc *rndis = fp->private_data; + int ret = 0; + + pr_info("Received command %d\n", cmd); + + if (rndis_qc_lock(&rndis->ioctl_excl)) + return -EBUSY; + + switch (cmd) { + case RNDIS_QC_GET_MAX_PKT_PER_XFER: + ret = copy_to_user((void __user *)arg, + &rndis->ul_max_pkt_per_xfer, + sizeof(rndis->ul_max_pkt_per_xfer)); + if (ret) { + pr_err("copying to user space failed\n"); + ret = -EFAULT; + } + pr_info("Sent UL max packets per xfer %d\n", + rndis->ul_max_pkt_per_xfer); + break; + case RNDIS_QC_GET_MAX_PKT_SIZE: + ret = copy_to_user((void __user *)arg, + &rndis->max_pkt_size, + sizeof(rndis->max_pkt_size)); + if (ret) { + pr_err("copying to user space failed\n"); + ret = -EFAULT; + } + pr_debug("Sent max packet size %d\n", + rndis->max_pkt_size); + break; + default: + pr_err("Unsupported IOCTL\n"); + ret = -EINVAL; + } + + rndis_qc_unlock(&rndis->ioctl_excl); + + return ret; +} + +static const struct file_operations rndis_qc_fops = { + .owner = THIS_MODULE, + .open = rndis_qc_open_dev, + .release = rndis_qc_release_dev, + .unlocked_ioctl = rndis_qc_ioctl, +}; + +static struct miscdevice rndis_qc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_rndis_qc", + .fops = &rndis_qc_fops, +}; + +static int rndis_qc_init(void) +{ + int ret; + + pr_info("initialize rndis QC instance\n"); + + ret = misc_register(&rndis_qc_device); + if (ret) + pr_err("rndis QC driver failed to register\n"); + spin_lock_init(&rndis_lock); + + ret = bam_data_setup(USB_FUNC_RNDIS, RNDIS_QC_NO_PORTS); + if (ret) { + pr_err("bam_data_setup failed err: %d\n", ret); + return ret; + } + + return ret; +} + +static void rndis_qc_cleanup(void) +{ + pr_info("rndis QC cleanup\n"); + + misc_deregister(&rndis_qc_device); +} + +void *rndis_qc_get_ipa_rx_cb(void) +{ + return rndis_ipa_params.ipa_rx_notify; +} + +void *rndis_qc_get_ipa_tx_cb(void) +{ + return rndis_ipa_params.ipa_tx_notify; +} + +void *rndis_qc_get_ipa_priv(void) +{ + return rndis_ipa_params.private; +} + +bool rndis_qc_get_skip_ep_config(void) +{ + return rndis_ipa_params.skip_ep_cfg; +} diff --git a/drivers/usb/gadget/function/f_rmnet.c b/drivers/usb/gadget/function/f_rmnet.c new file mode 100644 index 000000000000..efce7c382f2b --- /dev/null +++ b/drivers/usb/gadget/function/f_rmnet.c @@ -0,0 +1,1469 @@ +/* + * Copyright (c) 2011-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. + */ + +#include +#include +#include +#include +#include +#include + +#include "usb_gadget_xport.h" +#include "u_ether.h" +#include "u_rmnet.h" +#include "gadget_chips.h" + +static unsigned int rmnet_dl_max_pkt_per_xfer = 7; +module_param(rmnet_dl_max_pkt_per_xfer, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(rmnet_dl_max_pkt_per_xfer, + "Maximum packets per transfer for DL aggregation"); + +#define RMNET_NOTIFY_INTERVAL 5 +#define RMNET_MAX_NOTIFY_SIZE sizeof(struct usb_cdc_notification) + + +#define ACM_CTRL_DTR (1 << 0) + +/* TODO: use separate structures for data and + * control paths + */ +struct f_rmnet { + struct gether gether_port; + struct grmnet port; + int ifc_id; + u8 port_num; + atomic_t online; + atomic_t ctrl_online; + struct usb_composite_dev *cdev; + + spinlock_t lock; + + /* usb eps*/ + struct usb_ep *notify; + struct usb_request *notify_req; + + /* control info */ + struct list_head cpkt_resp_q; + unsigned long notify_count; + unsigned long cpkts_len; + const struct usb_endpoint_descriptor *in_ep_desc_backup; + const struct usb_endpoint_descriptor *out_ep_desc_backup; +}; + +static unsigned int nr_rmnet_ports; +static unsigned int no_ctrl_smd_ports; +static unsigned int no_ctrl_qti_ports; +static unsigned int no_ctrl_hsic_ports; +static unsigned int no_ctrl_hsuart_ports; +static unsigned int no_data_bam_ports; +static unsigned int no_data_bam2bam_ports; +static unsigned int no_data_hsic_ports; +static unsigned int no_data_hsuart_ports; +static struct rmnet_ports { + enum transport_type data_xport; + enum transport_type ctrl_xport; + unsigned data_xport_num; + unsigned ctrl_xport_num; + unsigned port_num; + struct f_rmnet *port; +} rmnet_ports[NR_RMNET_PORTS]; + +static struct usb_interface_descriptor rmnet_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_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = 1 << RMNET_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor rmnet_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_descriptor_header *rmnet_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = RMNET_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rmnet_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *rmnet_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_hs_out_desc, + NULL, +}; + +/* Super speed support */ +static struct usb_endpoint_descriptor rmnet_ss_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = RMNET_NOTIFY_INTERVAL + 4, +}; + +static struct usb_ss_ep_comp_descriptor rmnet_ss_notify_comp_desc = { + .bLength = sizeof rmnet_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(RMNET_MAX_NOTIFY_SIZE), +}; + +static struct usb_endpoint_descriptor rmnet_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor rmnet_ss_in_comp_desc = { + .bLength = sizeof rmnet_ss_in_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor rmnet_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor rmnet_ss_out_comp_desc = { + .bLength = sizeof rmnet_ss_out_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *rmnet_ss_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_ss_notify_desc, + (struct usb_descriptor_header *) &rmnet_ss_notify_comp_desc, + (struct usb_descriptor_header *) &rmnet_ss_in_desc, + (struct usb_descriptor_header *) &rmnet_ss_in_comp_desc, + (struct usb_descriptor_header *) &rmnet_ss_out_desc, + (struct usb_descriptor_header *) &rmnet_ss_out_comp_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_string_defs[] = { + [0].s = "RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_string_defs, +}; + +static struct usb_gadget_strings *rmnet_strings[] = { + &rmnet_string_table, + NULL, +}; + +static void frmnet_ctrl_response_available(struct f_rmnet *dev); + +/* ------- misc functions --------------------*/ + +static inline struct f_rmnet *func_to_rmnet(struct usb_function *f) +{ + return container_of(f, struct f_rmnet, gether_port.func); +} + +static inline struct f_rmnet *port_to_rmnet(struct grmnet *r) +{ + return container_of(r, struct f_rmnet, port); +} + +static struct usb_request * +frmnet_alloc_req(struct usb_ep *ep, unsigned len, size_t extra_buf_alloc, + gfp_t flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, flags); + if (!req) + return ERR_PTR(-ENOMEM); + + req->buf = kmalloc(len + extra_buf_alloc, flags); + if (!req->buf) { + usb_ep_free_request(ep, req); + return ERR_PTR(-ENOMEM); + } + + req->length = len; + + return req; +} + +void frmnet_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static struct rmnet_ctrl_pkt *rmnet_alloc_ctrl_pkt(unsigned len, gfp_t flags) +{ + struct rmnet_ctrl_pkt *pkt; + + pkt = kzalloc(sizeof(struct rmnet_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 rmnet_free_ctrl_pkt(struct rmnet_ctrl_pkt *pkt) +{ + kfree(pkt->buf); + kfree(pkt); +} + +/* -------------------------------------------*/ + +static int rmnet_gport_setup(void) +{ + int ret; + int port_idx; + int i; + u8 base; + + pr_debug("%s: bam ports: %u bam2bam ports: %u data hsic ports: %u data hsuart ports: %u" + " smd ports: %u ctrl hsic ports: %u ctrl hsuart ports: %u" + " nr_rmnet_ports: %u\n", + __func__, no_data_bam_ports, no_data_bam2bam_ports, + no_data_hsic_ports, no_data_hsuart_ports, no_ctrl_smd_ports, + no_ctrl_hsic_ports, no_ctrl_hsuart_ports, nr_rmnet_ports); + + if (no_data_bam_ports) { + ret = gbam_setup(no_data_bam_ports); + if (ret < 0) + return ret; + } + + if (no_data_bam2bam_ports) { + ret = gbam2bam_setup(no_data_bam2bam_ports); + if (ret < 0) + return ret; + } + + if (no_ctrl_smd_ports) { + ret = gsmd_ctrl_setup(FRMNET_CTRL_CLIENT, + no_ctrl_smd_ports, &base); + if (ret) + return ret; + for (i = 0; i < nr_rmnet_ports; i++) + if (rmnet_ports[i].port) + rmnet_ports[i].port->port_num += base; + } + + if (no_data_hsic_ports) { + port_idx = ghsic_data_setup(no_data_hsic_ports, + USB_GADGET_RMNET); + if (port_idx < 0) + return port_idx; + for (i = 0; i < nr_rmnet_ports; i++) { + if (rmnet_ports[i].data_xport == + USB_GADGET_XPORT_HSIC) { + rmnet_ports[i].data_xport_num = port_idx; + port_idx++; + } + } + } + + if (no_ctrl_hsic_ports) { + port_idx = ghsic_ctrl_setup(no_ctrl_hsic_ports, + USB_GADGET_RMNET); + if (port_idx < 0) + return port_idx; + for (i = 0; i < nr_rmnet_ports; i++) { + if (rmnet_ports[i].ctrl_xport == + USB_GADGET_XPORT_HSIC) { + rmnet_ports[i].ctrl_xport_num = port_idx; + port_idx++; + } + } + } + + return 0; +} + +static int gport_rmnet_connect(struct f_rmnet *dev, unsigned intf) +{ + int ret; + unsigned port_num; + enum transport_type cxport = rmnet_ports[dev->port_num].ctrl_xport; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + int src_connection_idx = 0, dst_connection_idx = 0; + struct usb_gadget *gadget = dev->cdev->gadget; + enum usb_ctrl usb_bam_type; + void *net; + + pr_debug("%s: ctrl xport: %s data xport: %s dev: %p portno: %d\n", + __func__, xport_to_str(cxport), xport_to_str(dxport), + dev, dev->port_num); + + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + switch (cxport) { + case USB_GADGET_XPORT_SMD: + ret = gsmd_ctrl_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: gsmd_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + break; + case USB_GADGET_XPORT_QTI: + ret = gqti_ctrl_connect(&dev->port, port_num, dev->ifc_id, + dxport, USB_GADGET_RMNET); + if (ret) { + pr_err("%s: gqti_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + break; + case USB_GADGET_XPORT_HSIC: + ret = ghsic_ctrl_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: ghsic_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(cxport)); + return -ENODEV; + } + + port_num = rmnet_ports[dev->port_num].data_xport_num; + + switch (dxport) { + case USB_GADGET_XPORT_BAM_DMUX: + ret = gbam_connect(&dev->port, port_num, + dxport, src_connection_idx, dst_connection_idx); + if (ret) { + pr_err("%s: gbam_connect failed: err:%d\n", + __func__, ret); + gsmd_ctrl_disconnect(&dev->port, port_num); + return ret; + } + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + usb_bam_type = usb_bam_get_bam_type(gadget->name); + src_connection_idx = usb_bam_get_connection_idx(usb_bam_type, + IPA_P_BAM, USB_TO_PEER_PERIPHERAL, USB_BAM_DEVICE, + port_num); + dst_connection_idx = usb_bam_get_connection_idx(usb_bam_type, + IPA_P_BAM, PEER_PERIPHERAL_TO_USB, USB_BAM_DEVICE, + port_num); + if (dst_connection_idx < 0 || src_connection_idx < 0) { + pr_err("%s: usb_bam_get_connection_idx failed\n", + __func__); + gsmd_ctrl_disconnect(&dev->port, port_num); + return -EINVAL; + } + ret = gbam_connect(&dev->port, port_num, + dxport, src_connection_idx, dst_connection_idx); + if (ret) { + pr_err("%s: gbam_connect failed: err:%d\n", + __func__, ret); + if (cxport == USB_GADGET_XPORT_QTI) + gqti_ctrl_disconnect(&dev->port, port_num); + else + gsmd_ctrl_disconnect(&dev->port, port_num); + return ret; + } + break; + case USB_GADGET_XPORT_HSIC: + ret = ghsic_data_connect(&dev->port, port_num); + if (ret) { + pr_err("%s: ghsic_data_connect failed: err:%d\n", + __func__, ret); + ghsic_ctrl_disconnect(&dev->port, port_num); + return ret; + } + break; + case USB_GADGET_XPORT_ETHER: + gether_enable_sg(&dev->gether_port, true); + net = gether_connect(&dev->gether_port); + if (IS_ERR(net)) { + pr_err("%s: gether_connect failed: err:%ld\n", + __func__, PTR_ERR(net)); + if (cxport == USB_GADGET_XPORT_QTI) + gqti_ctrl_disconnect(&dev->port, port_num); + else + gsmd_ctrl_disconnect(&dev->port, port_num); + + return PTR_ERR(net); + } + gether_update_dl_max_pkts_per_xfer(&dev->gether_port, + rmnet_dl_max_pkt_per_xfer); + gether_update_dl_max_xfer_size(&dev->gether_port, 16384); + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + return -ENODEV; + } + + return 0; +} + +static int gport_rmnet_disconnect(struct f_rmnet *dev) +{ + unsigned port_num; + enum transport_type cxport = rmnet_ports[dev->port_num].ctrl_xport; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + + pr_debug("%s: ctrl xport: %s data xport: %s dev: %p portno: %d\n", + __func__, xport_to_str(cxport), xport_to_str(dxport), + dev, dev->port_num); + + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + switch (cxport) { + case USB_GADGET_XPORT_SMD: + gsmd_ctrl_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_QTI: + gqti_ctrl_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_HSIC: + ghsic_ctrl_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(cxport)); + return -ENODEV; + } + + port_num = rmnet_ports[dev->port_num].data_xport_num; + switch (dxport) { + case USB_GADGET_XPORT_BAM_DMUX: + case USB_GADGET_XPORT_BAM2BAM_IPA: + gbam_disconnect(&dev->port, port_num, dxport); + break; + case USB_GADGET_XPORT_HSIC: + ghsic_data_disconnect(&dev->port, port_num); + break; + case USB_GADGET_XPORT_ETHER: + gether_disconnect(&dev->gether_port); + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + return -ENODEV; + } + + return 0; +} + +static void frmnet_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + + pr_debug("%s: portno:%d\n", __func__, dev->port_num); + 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); + + frmnet_free_req(dev->notify, dev->notify_req); + + kfree(f->name); +} + +static void frmnet_purge_responses(struct f_rmnet *dev) +{ + unsigned long flags; + struct rmnet_ctrl_pkt *cpkt; + + pr_debug("%s: port#%d\n", __func__, dev->port_num); + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(&dev->cpkt_resp_q)) { + cpkt = list_first_entry(&dev->cpkt_resp_q, + struct rmnet_ctrl_pkt, list); + + list_del(&cpkt->list); + rmnet_free_ctrl_pkt(cpkt); + } + dev->notify_count = 0; + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void frmnet_suspend(struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + unsigned port_num; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + bool remote_wakeup_allowed; + + 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; + + pr_debug("%s: data xport: %s dev: %p portno: %d remote_wakeup: %d\n", + __func__, xport_to_str(dxport), + dev, dev->port_num, remote_wakeup_allowed); + + usb_ep_fifo_flush(dev->notify); + frmnet_purge_responses(dev); + + port_num = rmnet_ports[dev->port_num].data_xport_num; + switch (dxport) { + case USB_GADGET_XPORT_BAM_DMUX: + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + if (remote_wakeup_allowed) { + gbam_suspend(&dev->port, port_num, dxport); + } else { + /* + * 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. + */ + dev->in_ep_desc_backup = dev->port.in->desc; + dev->out_ep_desc_backup = dev->port.out->desc; + pr_debug("in_ep_desc_bkup = %p, out_ep_desc_bkup = %p", + dev->in_ep_desc_backup, dev->out_ep_desc_backup); + pr_debug("%s(): Disconnecting\n", __func__); + gport_rmnet_disconnect(dev); + } + break; + case USB_GADGET_XPORT_HSIC: + break; + case USB_GADGET_XPORT_HSUART: + break; + case USB_GADGET_XPORT_ETHER: + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + } +} + +static void frmnet_resume(struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + unsigned port_num; + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + int ret; + bool remote_wakeup_allowed; + + 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; + + pr_debug("%s: data xport: %s dev: %p portno: %d remote_wakeup: %d\n", + __func__, xport_to_str(dxport), + dev, dev->port_num, remote_wakeup_allowed); + + port_num = rmnet_ports[dev->port_num].data_xport_num; + switch (dxport) { + case USB_GADGET_XPORT_BAM_DMUX: + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + if (remote_wakeup_allowed) { + gbam_resume(&dev->port, port_num, dxport); + } else { + dev->port.in->desc = dev->in_ep_desc_backup; + dev->port.out->desc = dev->out_ep_desc_backup; + pr_debug("%s(): Connecting\n", __func__); + ret = gport_rmnet_connect(dev, dev->ifc_id); + if (ret) { + pr_err("%s: gport_rmnet_connect failed: err:%d\n", + __func__, ret); + } + } + break; + case USB_GADGET_XPORT_HSIC: + break; + case USB_GADGET_XPORT_HSUART: + break; + case USB_GADGET_XPORT_ETHER: + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + xport_to_str(dxport)); + } +} + +static void frmnet_disable(struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + enum transport_type dxport = rmnet_ports[dev->port_num].data_xport; + struct usb_composite_dev *cdev = dev->cdev; + + pr_debug("%s: port#%d\n", __func__, dev->port_num); + + usb_ep_disable(dev->notify); + dev->notify->driver_data = NULL; + + atomic_set(&dev->online, 0); + + frmnet_purge_responses(dev); + + if (dxport == USB_GADGET_XPORT_BAM2BAM_IPA && + gadget_is_dwc3(cdev->gadget)) { + msm_ep_unconfig(dev->port.out); + msm_ep_unconfig(dev->port.in); + } + gport_rmnet_disconnect(dev); +} + +static int +frmnet_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_rmnet *dev = func_to_rmnet(f); + struct usb_composite_dev *cdev = dev->cdev; + int ret; + struct list_head *cpkt; + + pr_debug("%s:dev:%p port#%d\n", __func__, dev, dev->port_num); + + if (dev->notify->driver_data) { + pr_debug("%s: reset port:%d\n", __func__, dev->port_num); + usb_ep_disable(dev->notify); + } + + ret = config_ep_by_speed(cdev->gadget, f, dev->notify); + if (ret) { + dev->notify->desc = NULL; + ERROR(cdev, "config_ep_by_speed failes for ep %s, result %d\n", + dev->notify->name, ret); + return ret; + } + ret = usb_ep_enable(dev->notify); + + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, dev->notify->name, ret); + dev->notify->desc = NULL; + return ret; + } + dev->notify->driver_data = dev; + + if (!dev->port.in->desc || !dev->port.out->desc) { + if (config_ep_by_speed(cdev->gadget, f, dev->port.in) || + config_ep_by_speed(cdev->gadget, f, dev->port.out)) { + pr_err("%s(): config_ep_by_speed failed.\n", + __func__); + ret = -EINVAL; + goto err_disable_ep; + } + dev->port.gadget = dev->cdev->gadget; + } + + ret = gport_rmnet_connect(dev, intf); + if (ret) { + pr_err("%s(): gport_rmnet_connect fail with err:%d\n", + __func__, ret); + goto err_disable_ep; + } + + atomic_set(&dev->online, 1); + + /* In case notifications were aborted, but there are pending control + packets in the response queue, re-add the notifications */ + list_for_each(cpkt, &dev->cpkt_resp_q) + frmnet_ctrl_response_available(dev); + + return ret; +err_disable_ep: + dev->port.in->desc = NULL; + dev->port.out->desc = NULL; + usb_ep_disable(dev->notify); + + return ret; +} + +static void frmnet_ctrl_response_available(struct f_rmnet *dev) +{ + struct usb_request *req = dev->notify_req; + struct usb_cdc_notification *event; + unsigned long flags; + int ret; + struct rmnet_ctrl_pkt *cpkt; + + pr_debug("%s:dev:%p portno#%d\n", __func__, dev, dev->port_num); + + spin_lock_irqsave(&dev->lock, flags); + if (!atomic_read(&dev->online) || !req || !req->buf) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + if (++dev->notify_count != 1) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + event = req->buf; + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(dev->ifc_id); + event->wLength = cpu_to_le16(0); + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(dev->notify, dev->notify_req, GFP_ATOMIC); + if (ret) { + spin_lock_irqsave(&dev->lock, flags); + if (!list_empty(&dev->cpkt_resp_q)) { + if (dev->notify_count > 0) + dev->notify_count--; + else { + pr_debug("%s: Invalid notify_count=%lu to decrement\n", + __func__, dev->notify_count); + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + cpkt = list_first_entry(&dev->cpkt_resp_q, + struct rmnet_ctrl_pkt, list); + list_del(&cpkt->list); + rmnet_free_ctrl_pkt(cpkt); + } + spin_unlock_irqrestore(&dev->lock, flags); + pr_debug("ep enqueue error %d\n", ret); + } +} + +static void frmnet_connect(struct grmnet *gr) +{ + struct f_rmnet *dev; + + if (!gr) { + pr_err("%s: Invalid grmnet:%p\n", __func__, gr); + return; + } + + dev = port_to_rmnet(gr); + + atomic_set(&dev->ctrl_online, 1); +} + +static void frmnet_disconnect(struct grmnet *gr) +{ + struct f_rmnet *dev; + struct usb_cdc_notification *event; + int status; + + if (!gr) { + pr_err("%s: Invalid grmnet:%p\n", __func__, gr); + return; + } + + dev = port_to_rmnet(gr); + + atomic_set(&dev->ctrl_online, 0); + + if (!atomic_read(&dev->online)) { + pr_debug("%s: nothing to do\n", __func__); + return; + } + + usb_ep_fifo_flush(dev->notify); + + event = dev->notify_req->buf; + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(dev->ifc_id); + event->wLength = cpu_to_le16(0); + + status = usb_ep_queue(dev->notify, dev->notify_req, GFP_ATOMIC); + if (status < 0) { + if (!atomic_read(&dev->online)) + return; + pr_err("%s: rmnet notify ep enqueue error %d\n", + __func__, status); + } + + frmnet_purge_responses(dev); +} + +static int +frmnet_send_cpkt_response(void *gr, void *buf, size_t len) +{ + struct f_rmnet *dev; + struct rmnet_ctrl_pkt *cpkt; + unsigned long flags; + + if (!gr || !buf) { + pr_err("%s: Invalid grmnet/buf, grmnet:%p buf:%p\n", + __func__, gr, buf); + return -ENODEV; + } + cpkt = rmnet_alloc_ctrl_pkt(len, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + pr_err("%s: Unable to allocate ctrl pkt\n", __func__); + return -ENOMEM; + } + memcpy(cpkt->buf, buf, len); + cpkt->len = len; + + dev = port_to_rmnet(gr); + + pr_debug("%s: dev:%p port#%d\n", __func__, dev, dev->port_num); + + if (!atomic_read(&dev->online) || !atomic_read(&dev->ctrl_online)) { + rmnet_free_ctrl_pkt(cpkt); + return 0; + } + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&cpkt->list, &dev->cpkt_resp_q); + spin_unlock_irqrestore(&dev->lock, flags); + + frmnet_ctrl_response_available(dev); + + return 0; +} + +static void +frmnet_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rmnet *dev = req->context; + struct usb_composite_dev *cdev; + unsigned port_num; + + if (!dev) { + pr_err("%s: rmnet dev is null\n", __func__); + return; + } + + pr_debug("%s: dev:%p port#%d\n", __func__, dev, dev->port_num); + + cdev = dev->cdev; + + if (dev->port.send_encap_cmd) { + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + dev->port.send_encap_cmd(port_num, req->buf, req->actual); + } +} + +static void frmnet_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rmnet *dev = req->context; + int status = req->status; + unsigned long flags; + struct rmnet_ctrl_pkt *cpkt; + + pr_debug("%s: dev:%p port#%d\n", __func__, dev, dev->port_num); + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + spin_lock_irqsave(&dev->lock, flags); + dev->notify_count = 0; + spin_unlock_irqrestore(&dev->lock, flags); + break; + default: + pr_err("rmnet notify ep error %d\n", status); + /* FALLTHROUGH */ + case 0: + if (!atomic_read(&dev->ctrl_online)) + break; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->notify_count > 0) { + dev->notify_count--; + if (dev->notify_count == 0) { + spin_unlock_irqrestore(&dev->lock, flags); + break; + } + } else { + pr_debug("%s: Invalid notify_count=%lu to decrement\n", + __func__, dev->notify_count); + spin_unlock_irqrestore(&dev->lock, flags); + break; + } + spin_unlock_irqrestore(&dev->lock, flags); + + status = usb_ep_queue(dev->notify, req, GFP_ATOMIC); + if (status) { + spin_lock_irqsave(&dev->lock, flags); + if (!list_empty(&dev->cpkt_resp_q)) { + if (dev->notify_count > 0) + dev->notify_count--; + else { + pr_err("%s: Invalid notify_count=%lu to decrement\n", + __func__, dev->notify_count); + spin_unlock_irqrestore(&dev->lock, + flags); + break; + } + cpkt = list_first_entry(&dev->cpkt_resp_q, + struct rmnet_ctrl_pkt, list); + list_del(&cpkt->list); + rmnet_free_ctrl_pkt(cpkt); + } + spin_unlock_irqrestore(&dev->lock, flags); + pr_debug("ep enqueue error %d\n", status); + } + break; + } +} + +static int +frmnet_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_rmnet *dev = func_to_rmnet(f); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = cdev->req; + unsigned port_num; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + int ret = -EOPNOTSUPP; + + pr_debug("%s:dev:%p port#%d\n", __func__, dev, dev->port_num); + + if (!atomic_read(&dev->online)) { + pr_warning("%s: usb cable is not connected\n", __func__); + return -ENOTCONN; + } + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + pr_debug("%s: USB_CDC_SEND_ENCAPSULATED_COMMAND\n" + , __func__); + ret = w_length; + req->complete = frmnet_cmd_complete; + req->context = dev; + break; + + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + pr_debug("%s: USB_CDC_GET_ENCAPSULATED_RESPONSE\n", __func__); + if (w_value) { + pr_err("%s: invalid w_value = %04x\n", + __func__ , w_value); + goto invalid; + } else { + unsigned len; + struct rmnet_ctrl_pkt *cpkt; + + spin_lock(&dev->lock); + if (list_empty(&dev->cpkt_resp_q)) { + pr_err("ctrl resp queue empty " + " req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + ret = 0; + spin_unlock(&dev->lock); + goto invalid; + } + + cpkt = list_first_entry(&dev->cpkt_resp_q, + struct rmnet_ctrl_pkt, list); + list_del(&cpkt->list); + spin_unlock(&dev->lock); + + len = min_t(unsigned, w_length, cpkt->len); + memcpy(req->buf, cpkt->buf, len); + ret = len; + + rmnet_free_ctrl_pkt(cpkt); + } + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + pr_debug("%s: USB_CDC_REQ_SET_CONTROL_LINE_STATE: DTR:%d\n", + __func__, w_value & ACM_CTRL_DTR ? 1 : 0); + if (dev->port.notify_modem) { + port_num = rmnet_ports[dev->port_num].ctrl_xport_num; + dev->port.notify_modem(&dev->port, port_num, w_value); + } + ret = 0; + + break; + default: + +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (ret >= 0) { + VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (ret < w_length); + req->length = ret; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static int frmnet_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + struct usb_ep *ep; + struct usb_composite_dev *cdev = c->cdev; + int ret = -ENODEV; + pr_debug("%s: start binding\n", __func__); + dev->ifc_id = usb_interface_id(c, f); + if (dev->ifc_id < 0) { + pr_err("%s: unable to allocate ifc id, err:%d\n", + __func__, dev->ifc_id); + return dev->ifc_id; + } + rmnet_interface_desc.bInterfaceNumber = dev->ifc_id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_in_desc); + if (!ep) { + pr_err("%s: usb epin autoconfig failed\n", __func__); + return -ENODEV; + } + dev->port.in = ep; + /* Update same for u_ether which uses gether port struct */ + dev->gether_port.in_ep = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_out_desc); + if (!ep) { + pr_err("%s: usb epout autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_out_fail; + } + dev->port.out = ep; + /* Update same for u_ether which uses gether port struct */ + dev->gether_port.out_ep = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_notify_desc); + if (!ep) { + pr_err("%s: usb epnotify autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_notify_fail; + } + dev->notify = ep; + ep->driver_data = cdev; + + dev->notify_req = frmnet_alloc_req(ep, + sizeof(struct usb_cdc_notification), + cdev->gadget->extra_buf_alloc, + GFP_KERNEL); + if (IS_ERR(dev->notify_req)) { + pr_err("%s: unable to allocate memory for notify req\n", + __func__); + ret = -ENOMEM; + goto ep_notify_alloc_fail; + } + + dev->notify_req->complete = frmnet_notify_complete; + dev->notify_req->context = dev; + + ret = -ENOMEM; + f->fs_descriptors = usb_copy_descriptors(rmnet_fs_function); + + if (!f->fs_descriptors) { + pr_err("%s: no descriptors,usb_copy descriptors(fs)failed\n", + __func__); + goto fail; + } + if (gadget_is_dualspeed(cdev->gadget)) { + rmnet_hs_in_desc.bEndpointAddress = + rmnet_fs_in_desc.bEndpointAddress; + rmnet_hs_out_desc.bEndpointAddress = + rmnet_fs_out_desc.bEndpointAddress; + rmnet_hs_notify_desc.bEndpointAddress = + rmnet_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(rmnet_hs_function); + + if (!f->hs_descriptors) { + pr_err("%s: no hs_descriptors,usb_copy descriptors(hs)failed\n", + __func__); + goto fail; + } + } + + if (gadget_is_superspeed(cdev->gadget)) { + rmnet_ss_in_desc.bEndpointAddress = + rmnet_fs_in_desc.bEndpointAddress; + rmnet_ss_out_desc.bEndpointAddress = + rmnet_fs_out_desc.bEndpointAddress; + rmnet_ss_notify_desc.bEndpointAddress = + rmnet_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(rmnet_ss_function); + + if (!f->ss_descriptors) { + pr_err("%s: no ss_descriptors,usb_copy descriptors(ss)failed\n", + __func__); + goto fail; + } + } + + pr_debug("%s: RmNet(%d) %s Speed, IN:%s OUT:%s\n", + __func__, dev->port_num, + gadget_is_dualspeed(cdev->gadget) ? "dual" : "full", + dev->port.in->name, dev->port.out->name); + + return 0; + +fail: + if (f->ss_descriptors) + usb_free_descriptors(f->ss_descriptors); + if (f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + if (f->fs_descriptors) + usb_free_descriptors(f->fs_descriptors); + if (dev->notify_req) + frmnet_free_req(dev->notify, dev->notify_req); +ep_notify_alloc_fail: + dev->notify->driver_data = NULL; + dev->notify = NULL; +ep_auto_notify_fail: + dev->port.out->driver_data = NULL; + dev->port.out = NULL; +ep_auto_out_fail: + dev->port.in->driver_data = NULL; + dev->port.in = NULL; + + return ret; +} + +static int frmnet_bind_config(struct usb_configuration *c, unsigned portno) +{ + int status; + struct f_rmnet *dev; + struct usb_function *f; + unsigned long flags; + + pr_debug("%s: usb config:%p\n", __func__, c); + + if (portno >= nr_rmnet_ports) { + pr_err("%s: supporting ports#%u port_id:%u\n", __func__, + nr_rmnet_ports, portno); + return -ENODEV; + } + + dev = rmnet_ports[portno].port; + + if (rmnet_ports[portno].data_xport == USB_GADGET_XPORT_ETHER) { + struct net_device *net = gether_setup_name_default("usb_rmnet"); + if (IS_ERR(net)) { + pr_err("%s: gether_setup failed\n", __func__); + return PTR_ERR(net); + } + dev->gether_port.ioport = netdev_priv(net); + gether_set_gadget(net, c->cdev->gadget); + status = gether_register_netdev(net); + if (status < 0) { + pr_err("%s: gether_register_netdev failed\n", __func__); + free_netdev(net); + return status; + } + } + + if (rmnet_string_defs[0].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) { + pr_err("%s: failed to get string id, err:%d\n", + __func__, status); + return status; + } + rmnet_string_defs[0].id = status; + } + + spin_lock_irqsave(&dev->lock, flags); + dev->cdev = c->cdev; + f = &dev->gether_port.func; + f->name = kasprintf(GFP_ATOMIC, "rmnet%d", portno); + spin_unlock_irqrestore(&dev->lock, flags); + if (!f->name) { + pr_err("%s: cannot allocate memory for name\n", __func__); + return -ENOMEM; + } + + f->strings = rmnet_strings; + f->bind = frmnet_bind; + f->unbind = frmnet_unbind; + f->disable = frmnet_disable; + f->set_alt = frmnet_set_alt; + f->setup = frmnet_setup; + f->suspend = frmnet_suspend; + f->resume = frmnet_resume; + dev->port.send_cpkt_response = frmnet_send_cpkt_response; + dev->port.disconnect = frmnet_disconnect; + dev->port.connect = frmnet_connect; + dev->gether_port.cdc_filter = 0; + + status = usb_add_function(c, f); + if (status) { + pr_err("%s: usb add function failed: %d\n", + __func__, status); + kfree(f->name); + return status; + } + + pr_debug("%s: complete\n", __func__); + + return status; +} + +static void frmnet_unbind_config(void) +{ + int i; + + for (i = 0; i < nr_rmnet_ports; i++) + if (rmnet_ports[i].data_xport == USB_GADGET_XPORT_ETHER) { + gether_cleanup(rmnet_ports[i].port->gether_port.ioport); + rmnet_ports[i].port->gether_port.ioport = NULL; + } +} + +static int rmnet_init(void) +{ + return gqti_ctrl_init(); +} + +static void frmnet_cleanup(void) +{ + int i; + + gqti_ctrl_cleanup(); + + for (i = 0; i < nr_rmnet_ports; i++) + kfree(rmnet_ports[i].port); + + gbam_cleanup(); + nr_rmnet_ports = 0; + no_ctrl_smd_ports = 0; + no_ctrl_qti_ports = 0; + no_data_bam_ports = 0; + no_data_bam2bam_ports = 0; + no_ctrl_hsic_ports = 0; + no_data_hsic_ports = 0; + no_ctrl_hsuart_ports = 0; + no_data_hsuart_ports = 0; +} + +static int frmnet_init_port(const char *ctrl_name, const char *data_name, + const char *port_name) +{ + struct f_rmnet *dev; + struct rmnet_ports *rmnet_port; + int ret; + int i; + + if (nr_rmnet_ports >= NR_RMNET_PORTS) { + pr_err("%s: Max-%d instances supported\n", + __func__, NR_RMNET_PORTS); + return -EINVAL; + } + + pr_debug("%s: port#:%d, ctrl port: %s data port: %s\n", + __func__, nr_rmnet_ports, ctrl_name, data_name); + + dev = kzalloc(sizeof(struct f_rmnet), GFP_KERNEL); + if (!dev) { + pr_err("%s: Unable to allocate rmnet device\n", __func__); + return -ENOMEM; + } + + dev->port_num = nr_rmnet_ports; + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->cpkt_resp_q); + + rmnet_port = &rmnet_ports[nr_rmnet_ports]; + rmnet_port->port = dev; + rmnet_port->port_num = nr_rmnet_ports; + rmnet_port->ctrl_xport = str_to_xport(ctrl_name); + rmnet_port->data_xport = str_to_xport(data_name); + + switch (rmnet_port->ctrl_xport) { + case USB_GADGET_XPORT_SMD: + rmnet_port->ctrl_xport_num = no_ctrl_smd_ports; + no_ctrl_smd_ports++; + break; + case USB_GADGET_XPORT_QTI: + rmnet_port->ctrl_xport_num = no_ctrl_qti_ports; + no_ctrl_qti_ports++; + break; + case USB_GADGET_XPORT_HSIC: + ghsic_ctrl_set_port_name(port_name, ctrl_name); + rmnet_port->ctrl_xport_num = no_ctrl_hsic_ports; + no_ctrl_hsic_ports++; + break; + case USB_GADGET_XPORT_HSUART: + rmnet_port->ctrl_xport_num = no_ctrl_hsuart_ports; + no_ctrl_hsuart_ports++; + break; + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %u\n", __func__, + rmnet_port->ctrl_xport); + ret = -ENODEV; + goto fail_probe; + } + + switch (rmnet_port->data_xport) { + case USB_GADGET_XPORT_BAM2BAM: + /* Override BAM2BAM to BAM_DMUX for old ABI compatibility */ + rmnet_port->data_xport = USB_GADGET_XPORT_BAM_DMUX; + /* fall-through */ + case USB_GADGET_XPORT_BAM_DMUX: + rmnet_port->data_xport_num = no_data_bam_ports; + no_data_bam_ports++; + break; + case USB_GADGET_XPORT_BAM2BAM_IPA: + rmnet_port->data_xport_num = no_data_bam2bam_ports; + no_data_bam2bam_ports++; + break; + case USB_GADGET_XPORT_HSIC: + ghsic_data_set_port_name(port_name, data_name); + rmnet_port->data_xport_num = no_data_hsic_ports; + no_data_hsic_ports++; + break; + case USB_GADGET_XPORT_HSUART: + rmnet_port->data_xport_num = no_data_hsuart_ports; + no_data_hsuart_ports++; + break; + case USB_GADGET_XPORT_ETHER: + case USB_GADGET_XPORT_NONE: + break; + default: + pr_err("%s: Un-supported transport: %u\n", __func__, + rmnet_port->data_xport); + ret = -ENODEV; + goto fail_probe; + } + nr_rmnet_ports++; + + return 0; + +fail_probe: + for (i = 0; i < nr_rmnet_ports; i++) + kfree(rmnet_ports[i].port); + + nr_rmnet_ports = 0; + no_ctrl_smd_ports = 0; + no_ctrl_qti_ports = 0; + no_data_bam_ports = 0; + no_ctrl_hsic_ports = 0; + no_data_hsic_ports = 0; + no_ctrl_hsuart_ports = 0; + no_data_hsuart_ports = 0; + + return ret; +} diff --git a/drivers/usb/gadget/function/u_bam.c b/drivers/usb/gadget/function/u_bam.c new file mode 100644 index 000000000000..aef5f1a0783f --- /dev/null +++ b/drivers/usb/gadget/function/u_bam.c @@ -0,0 +1,2523 @@ +/* Copyright (c) 2011-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "usb_gadget_xport.h" +#include "u_rmnet.h" + +#define BAM_N_PORTS 2 +#define BAM2BAM_N_PORTS 4 + +static struct workqueue_struct *gbam_wq; +static int n_bam_ports; +static int n_bam2bam_ports; +static unsigned n_tx_req_queued; + +static unsigned bam_ch_ids[BAM_N_PORTS] = { + BAM_DMUX_USB_RMNET_0, + BAM_DMUX_USB_DPL +}; + +static char bam_ch_names[BAM_N_PORTS][BAM_DMUX_CH_NAME_MAX_LEN]; + +static const enum ipa_client_type usb_prod[BAM2BAM_N_PORTS] = { + IPA_CLIENT_USB_PROD, IPA_CLIENT_USB2_PROD, + IPA_CLIENT_USB3_PROD, IPA_CLIENT_USB4_PROD +}; +static const enum ipa_client_type usb_cons[BAM2BAM_N_PORTS] = { + IPA_CLIENT_USB_CONS, IPA_CLIENT_USB2_CONS, + IPA_CLIENT_USB3_CONS, IPA_CLIENT_USB4_CONS +}; + +#define BAM_PENDING_PKTS_LIMIT 220 +#define BAM_MUX_TX_PKT_DROP_THRESHOLD 1000 +#define BAM_MUX_RX_PKT_FCTRL_EN_TSHOLD 500 +#define BAM_MUX_RX_PKT_FCTRL_DIS_TSHOLD 300 +#define BAM_MUX_RX_PKT_FLOW_CTRL_SUPPORT 1 + +#define BAM_MUX_HDR 8 + +#define BAM_MUX_RX_Q_SIZE 128 +#define BAM_MUX_TX_Q_SIZE 200 +#define BAM_MUX_RX_REQ_SIZE 2048 /* Must be 1KB aligned */ + +#define DL_INTR_THRESHOLD 20 +#define BAM_PENDING_BYTES_LIMIT (50 * BAM_MUX_RX_REQ_SIZE) +#define BAM_PENDING_BYTES_FCTRL_EN_TSHOLD (BAM_PENDING_BYTES_LIMIT / 3) + +/* Extra buffer size to allocate for tx */ +#define EXTRA_ALLOCATION_SIZE_U_BAM 128 + +static unsigned int bam_pending_pkts_limit = BAM_PENDING_PKTS_LIMIT; +module_param(bam_pending_pkts_limit, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_pending_bytes_limit = BAM_PENDING_BYTES_LIMIT; +module_param(bam_pending_bytes_limit, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_pending_bytes_fctrl_en_thold = + BAM_PENDING_BYTES_FCTRL_EN_TSHOLD; +module_param(bam_pending_bytes_fctrl_en_thold, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_mux_tx_pkt_drop_thld = BAM_MUX_TX_PKT_DROP_THRESHOLD; +module_param(bam_mux_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_mux_rx_fctrl_en_thld = BAM_MUX_RX_PKT_FCTRL_EN_TSHOLD; +module_param(bam_mux_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_mux_rx_fctrl_support = BAM_MUX_RX_PKT_FLOW_CTRL_SUPPORT; +module_param(bam_mux_rx_fctrl_support, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_mux_rx_fctrl_dis_thld = BAM_MUX_RX_PKT_FCTRL_DIS_TSHOLD; +module_param(bam_mux_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_mux_tx_q_size = BAM_MUX_TX_Q_SIZE; +module_param(bam_mux_tx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_mux_rx_q_size = BAM_MUX_RX_Q_SIZE; +module_param(bam_mux_rx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned long bam_mux_rx_req_size = BAM_MUX_RX_REQ_SIZE; +module_param(bam_mux_rx_req_size, ulong, S_IRUGO); + +static unsigned int dl_intr_threshold = DL_INTR_THRESHOLD; +module_param(dl_intr_threshold, uint, S_IRUGO | S_IWUSR); + +#define BAM_CH_OPENED BIT(0) +#define BAM_CH_READY BIT(1) +#define BAM_CH_WRITE_INPROGRESS BIT(2) + +enum u_bam_event_type { + U_BAM_DISCONNECT_E = 0, + U_BAM_CONNECT_E, + U_BAM_SUSPEND_E, + U_BAM_RESUME_E +}; + +struct sys2ipa_sw { + void *teth_priv; + ipa_notify_cb teth_cb; +}; + +struct bam_ch_info { + unsigned long flags; + unsigned id; + + struct list_head tx_idle; + struct sk_buff_head tx_skb_q; + + struct list_head rx_idle; + struct sk_buff_head rx_skb_q; + struct sk_buff_head rx_skb_idle; + + struct gbam_port *port; + struct work_struct write_tobam_w; + struct work_struct write_tohost_w; + + struct usb_request *rx_req; + struct usb_request *tx_req; + + u32 src_pipe_idx; + u32 dst_pipe_idx; + u8 src_connection_idx; + u8 dst_connection_idx; + enum usb_ctrl usb_bam_type; + + enum transport_type trans; + struct usb_bam_connect_ipa_params ipa_params; + + /* added to support sys to ipa sw UL path */ + struct sys2ipa_sw ul_params; + enum usb_bam_pipe_type src_pipe_type; + enum usb_bam_pipe_type dst_pipe_type; + + /* stats */ + unsigned int pending_pkts_with_bam; + unsigned int pending_bytes_with_bam; + unsigned int tohost_drp_cnt; + unsigned int tomodem_drp_cnt; + unsigned int tx_len; + unsigned int rx_len; + unsigned long to_modem; + unsigned long to_host; + unsigned int rx_flow_control_disable; + unsigned int rx_flow_control_enable; + unsigned int rx_flow_control_triggered; + unsigned int max_num_pkts_pending_with_bam; + unsigned int max_bytes_pending_with_bam; + unsigned int delayed_bam_mux_write_done; + unsigned long skb_expand_cnt; +}; + +struct gbam_port { + bool is_connected; + enum u_bam_event_type last_event; + unsigned port_num; + spinlock_t port_lock_ul; + spinlock_t port_lock_dl; + spinlock_t port_lock; + + struct grmnet *port_usb; + struct usb_gadget *gadget; + + struct bam_ch_info data_ch; + + struct work_struct connect_w; + struct work_struct disconnect_w; + struct work_struct suspend_w; + struct work_struct resume_w; +}; + +static struct bam_portmaster { + struct gbam_port *port; + struct platform_driver pdrv; +} bam_ports[BAM_N_PORTS]; + +struct u_bam_data_connect_info { + u32 usb_bam_pipe_idx; + u32 peer_pipe_idx; + unsigned long usb_bam_handle; +}; + +struct gbam_port *bam2bam_ports[BAM2BAM_N_PORTS]; +static void gbam_start_rx(struct gbam_port *port); +static void gbam_start_endless_rx(struct gbam_port *port); +static void gbam_start_endless_tx(struct gbam_port *port); +static void gbam_notify(void *p, int event, unsigned long data); +static void gbam_data_write_tobam(struct work_struct *w); + +/*---------------misc functions---------------- */ +static void gbam_free_requests(struct usb_ep *ep, struct list_head *head) +{ + struct usb_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ep, req); + } +} + +static int gbam_alloc_requests(struct usb_ep *ep, struct list_head *head, + int num, + void (*cb)(struct usb_ep *ep, struct usb_request *), + gfp_t flags) +{ + int i; + struct usb_request *req; + pr_debug("%s: ep:%p head:%p num:%d cb:%p", __func__, + ep, head, num, cb); + + for (i = 0; i < num; i++) { + req = usb_ep_alloc_request(ep, flags); + if (!req) { + pr_debug("%s: req allocated:%d\n", __func__, i); + return list_empty(head) ? -ENOMEM : 0; + } + req->complete = cb; + list_add(&req->list, head); + } + + return 0; +} + +static inline dma_addr_t gbam_get_dma_from_skb(struct sk_buff *skb) +{ + return *((dma_addr_t *)(skb->cb)); +} + +/* This function should be called with port_lock_ul lock held */ +static struct sk_buff *gbam_alloc_skb_from_pool(struct gbam_port *port) +{ + struct bam_ch_info *d; + struct sk_buff *skb; + dma_addr_t skb_buf_dma_addr; + struct usb_gadget *gadget; + + if (!port) + return NULL; + + d = &port->data_ch; + if (!d) + return NULL; + + if (d->rx_skb_idle.qlen == 0) { + /* + * In case skb idle pool is empty, we allow to allocate more + * skbs so we dynamically enlarge the pool size when needed. + * Therefore, in steady state this dynamic allocation will + * stop when the pool will arrive to its optimal size. + */ + pr_debug("%s: allocate skb\n", __func__); + skb = alloc_skb(bam_mux_rx_req_size + BAM_MUX_HDR, GFP_ATOMIC); + + if (!skb) { + pr_err("%s: alloc skb failed\n", __func__); + goto alloc_exit; + } + + skb_reserve(skb, BAM_MUX_HDR); + + if ((d->trans == USB_GADGET_XPORT_BAM2BAM_IPA)) { + + gadget = port->port_usb->gadget; + + skb_buf_dma_addr = + dma_map_single(&gadget->dev, skb->data, + bam_mux_rx_req_size, DMA_BIDIRECTIONAL); + + if (dma_mapping_error(&gadget->dev, skb_buf_dma_addr)) { + pr_err("%s: Could not DMA map SKB buffer\n", + __func__); + skb_buf_dma_addr = DMA_ERROR_CODE; + } + } else { + skb_buf_dma_addr = DMA_ERROR_CODE; + } + + + memcpy(skb->cb, &skb_buf_dma_addr, + sizeof(skb_buf_dma_addr)); + + } else { + pr_debug("%s: pull skb from pool\n", __func__); + skb = __skb_dequeue(&d->rx_skb_idle); + if (skb_headroom(skb) < BAM_MUX_HDR) + skb_reserve(skb, BAM_MUX_HDR); + } + +alloc_exit: + return skb; +} + +/* This function should be called with port_lock_ul lock held */ +static void gbam_free_skb_to_pool(struct gbam_port *port, struct sk_buff *skb) +{ + struct bam_ch_info *d; + + if (!port) + return; + d = &port->data_ch; + + skb->len = 0; + skb_reset_tail_pointer(skb); + __skb_queue_tail(&d->rx_skb_idle, skb); +} + +static void gbam_free_rx_skb_idle_list(struct gbam_port *port) +{ + struct bam_ch_info *d; + struct sk_buff *skb; + dma_addr_t dma_addr; + struct usb_gadget *gadget = NULL; + + if (!port) + return; + d = &port->data_ch; + + gadget = port->port_usb->gadget; + + while (d->rx_skb_idle.qlen > 0) { + skb = __skb_dequeue(&d->rx_skb_idle); + dma_addr = gbam_get_dma_from_skb(skb); + + if (gadget && dma_addr != DMA_ERROR_CODE) { + dma_unmap_single(&gadget->dev, dma_addr, + bam_mux_rx_req_size, DMA_BIDIRECTIONAL); + + dma_addr = DMA_ERROR_CODE; + memcpy(skb->cb, &dma_addr, + sizeof(dma_addr)); + } + dev_kfree_skb_any(skb); + } +} + +/*----- sys2bam towards the IPA --------------- */ +static void gbam_ipa_sys2bam_notify_cb(void *priv, enum ipa_dp_evt_type event, + unsigned long data) +{ + struct sys2ipa_sw *ul = (struct sys2ipa_sw *)priv; + struct gbam_port *port; + struct bam_ch_info *d; + + switch (event) { + case IPA_WRITE_DONE: + d = container_of(ul, struct bam_ch_info, ul_params); + port = container_of(d, struct gbam_port, data_ch); + /* call into bam_demux functionality that'll recycle the data */ + gbam_notify(port, BAM_DMUX_WRITE_DONE, data); + break; + case IPA_RECEIVE: + /* call the callback given by tethering driver init function + * (and was given to ipa_connect) + */ + if (ul->teth_cb) + ul->teth_cb(ul->teth_priv, event, data); + break; + default: + /* unexpected event */ + pr_err("%s: unexpected event %d\n", __func__, event); + break; + } +} + + +/*--------------------------------------------- */ + +/*------------data_path----------------------------*/ +static void gbam_write_data_tohost(struct gbam_port *port) +{ + unsigned long flags; + struct bam_ch_info *d = &port->data_ch; + struct sk_buff *skb; + struct sk_buff *new_skb; + int ret; + int tail_room = 0; + int extra_alloc = 0; + struct usb_request *req; + struct usb_ep *ep; + + spin_lock_irqsave(&port->port_lock_dl, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_dl, flags); + return; + } + + ep = port->port_usb->in; + + while (!list_empty(&d->tx_idle)) { + skb = __skb_dequeue(&d->tx_skb_q); + if (!skb) + break; + + /* + * Some UDC requires allocation of some extra bytes for + * TX buffer due to hardware requirement. Check if extra + * bytes are already there, otherwise allocate new buffer + * with extra bytes and do memcpy. + */ + if (port->gadget->extra_buf_alloc) + extra_alloc = EXTRA_ALLOCATION_SIZE_U_BAM; + tail_room = skb_tailroom(skb); + if (tail_room < extra_alloc) { + pr_debug("%s: tail_room %d less than %d\n", __func__, + tail_room, extra_alloc); + new_skb = skb_copy_expand(skb, 0, extra_alloc - + tail_room, GFP_ATOMIC); + if (!new_skb) { + pr_err("skb_copy_expand failed\n"); + break; + } + dev_kfree_skb_any(skb); + skb = new_skb; + d->skb_expand_cnt++; + } + + req = list_first_entry(&d->tx_idle, + struct usb_request, + list); + req->context = skb; + req->buf = skb->data; + req->length = skb->len; + n_tx_req_queued++; + if (n_tx_req_queued == dl_intr_threshold) { + req->no_interrupt = 0; + n_tx_req_queued = 0; + } else { + req->no_interrupt = 1; + } + + /* Send ZLP in case packet length is multiple of maxpacksize */ + req->zero = 1; + + list_del(&req->list); + + spin_unlock(&port->port_lock_dl); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&port->port_lock_dl); + if (ret) { + pr_err("%s: usb epIn failed with %d\n", __func__, ret); + list_add(&req->list, &d->tx_idle); + dev_kfree_skb_any(skb); + break; + } + d->to_host++; + } + spin_unlock_irqrestore(&port->port_lock_dl, flags); +} + +static void gbam_write_data_tohost_w(struct work_struct *w) +{ + struct bam_ch_info *d; + struct gbam_port *port; + + d = container_of(w, struct bam_ch_info, write_tohost_w); + port = d->port; + + gbam_write_data_tohost(port); +} + +void gbam_data_recv_cb(void *p, struct sk_buff *skb) +{ + struct gbam_port *port = p; + struct bam_ch_info *d = &port->data_ch; + unsigned long flags; + + if (!skb) + return; + + pr_debug("%s: p:%p#%d d:%p skb_len:%d\n", __func__, + port, port->port_num, d, skb->len); + + spin_lock_irqsave(&port->port_lock_dl, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_dl, flags); + dev_kfree_skb_any(skb); + return; + } + + if (d->tx_skb_q.qlen > bam_mux_tx_pkt_drop_thld) { + d->tohost_drp_cnt++; + if (printk_ratelimit()) + pr_err("%s: tx pkt dropped: tx_drop_cnt:%u\n", + __func__, d->tohost_drp_cnt); + spin_unlock_irqrestore(&port->port_lock_dl, flags); + dev_kfree_skb_any(skb); + return; + } + + __skb_queue_tail(&d->tx_skb_q, skb); + spin_unlock_irqrestore(&port->port_lock_dl, flags); + + gbam_write_data_tohost(port); +} + +void gbam_data_write_done(void *p, struct sk_buff *skb) +{ + struct gbam_port *port = p; + struct bam_ch_info *d = &port->data_ch; + unsigned long flags; + + if (!skb) + return; + + spin_lock_irqsave(&port->port_lock_ul, flags); + + d->pending_pkts_with_bam--; + d->pending_bytes_with_bam -= skb->len; + gbam_free_skb_to_pool(port, skb); + + pr_debug("%s:port:%p d:%p tom:%lu ppkt:%u pbytes:%u pno:%d\n", __func__, + port, d, d->to_modem, d->pending_pkts_with_bam, + d->pending_bytes_with_bam, port->port_num); + + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + /* + * If BAM doesn't have much pending data then push new data from here: + * write_complete notify only to avoid any underruns due to wq latency + */ + if (d->pending_bytes_with_bam <= bam_pending_bytes_fctrl_en_thold) { + gbam_data_write_tobam(&d->write_tobam_w); + } else { + d->delayed_bam_mux_write_done++; + queue_work(gbam_wq, &d->write_tobam_w); + } +} + +/* This function should be called with port_lock_ul spinlock acquired */ +static bool gbam_ul_bam_limit_reached(struct bam_ch_info *data_ch) +{ + unsigned int curr_pending_pkts = data_ch->pending_pkts_with_bam; + unsigned int curr_pending_bytes = data_ch->pending_bytes_with_bam; + struct sk_buff *skb; + + if (curr_pending_pkts >= bam_pending_pkts_limit) + return true; + + /* check if next skb length doesn't exceed pending_bytes_limit */ + skb = skb_peek(&data_ch->rx_skb_q); + if (!skb) + return false; + + if ((curr_pending_bytes + skb->len) > bam_pending_bytes_limit) + return true; + else + return false; +} + +static void gbam_data_write_tobam(struct work_struct *w) +{ + struct gbam_port *port; + struct bam_ch_info *d; + struct sk_buff *skb; + unsigned long flags; + int ret; + int qlen; + + d = container_of(w, struct bam_ch_info, write_tobam_w); + port = d->port; + + spin_lock_irqsave(&port->port_lock_ul, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + /* Bail out if already in progress */ + if (test_bit(BAM_CH_WRITE_INPROGRESS, &d->flags)) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + + set_bit(BAM_CH_WRITE_INPROGRESS, &d->flags); + + while (!gbam_ul_bam_limit_reached(d) && + (d->trans != USB_GADGET_XPORT_BAM2BAM_IPA || + usb_bam_get_prod_granted(d->usb_bam_type, + d->dst_connection_idx))) { + skb = __skb_dequeue(&d->rx_skb_q); + if (!skb) + break; + + d->pending_pkts_with_bam++; + d->pending_bytes_with_bam += skb->len; + d->to_modem++; + + pr_debug("%s: port:%p d:%p tom:%lu ppkts:%u pbytes:%u pno:%d\n", + __func__, port, d, + d->to_modem, d->pending_pkts_with_bam, + d->pending_bytes_with_bam, port->port_num); + + spin_unlock_irqrestore(&port->port_lock_ul, flags); + if (d->src_pipe_type == USB_BAM_PIPE_SYS2BAM) { + dma_addr_t skb_dma_addr; + struct ipa_tx_meta ipa_meta = {0x0}; + + skb_dma_addr = gbam_get_dma_from_skb(skb); + if (skb_dma_addr != DMA_ERROR_CODE) { + ipa_meta.dma_address = skb_dma_addr; + ipa_meta.dma_address_valid = true; + } + + ret = ipa_tx_dp(usb_prod[port->port_num], + skb, + &ipa_meta); + } else { + ret = msm_bam_dmux_write(d->id, skb); + } + + spin_lock_irqsave(&port->port_lock_ul, flags); + if (ret) { + pr_debug("%s: write error:%d\n", __func__, ret); + d->pending_pkts_with_bam--; + d->pending_bytes_with_bam -= skb->len; + d->to_modem--; + d->tomodem_drp_cnt++; + gbam_free_skb_to_pool(port, skb); + break; + } + if (d->pending_pkts_with_bam > d->max_num_pkts_pending_with_bam) + d->max_num_pkts_pending_with_bam = + d->pending_pkts_with_bam; + if (d->pending_bytes_with_bam > d->max_bytes_pending_with_bam) + d->max_bytes_pending_with_bam = + d->pending_bytes_with_bam; + } + + qlen = d->rx_skb_q.qlen; + + clear_bit(BAM_CH_WRITE_INPROGRESS, &d->flags); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + if (qlen < bam_mux_rx_fctrl_dis_thld) { + if (d->rx_flow_control_triggered) { + d->rx_flow_control_disable++; + d->rx_flow_control_triggered = 0; + } + gbam_start_rx(port); + } +} +/*-------------------------------------------------------------*/ + +static void gbam_epin_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gbam_port *port = ep->driver_data; + struct bam_ch_info *d; + struct sk_buff *skb = req->context; + int status = req->status; + + switch (status) { + case 0: + /* successful completion */ + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + dev_kfree_skb_any(skb); + usb_ep_free_request(ep, req); + return; + default: + pr_err("%s: data tx ep error %d\n", + __func__, status); + break; + } + + dev_kfree_skb_any(skb); + + if (!port) + return; + + spin_lock(&port->port_lock_dl); + d = &port->data_ch; + list_add_tail(&req->list, &d->tx_idle); + spin_unlock(&port->port_lock_dl); + + queue_work(gbam_wq, &d->write_tohost_w); +} + +static void +gbam_epout_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gbam_port *port = ep->driver_data; + struct bam_ch_info *d = &port->data_ch; + struct sk_buff *skb = req->context; + int status = req->status; + int queue = 0; + + switch (status) { + case 0: + skb_put(skb, req->actual); + queue = 1; + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* cable disconnection */ + spin_lock(&port->port_lock_ul); + gbam_free_skb_to_pool(port, skb); + spin_unlock(&port->port_lock_ul); + req->buf = 0; + usb_ep_free_request(ep, req); + return; + default: + if (printk_ratelimit()) + pr_err("%s: %s response error %d, %d/%d\n", + __func__, ep->name, status, + req->actual, req->length); + spin_lock(&port->port_lock_ul); + gbam_free_skb_to_pool(port, skb); + spin_unlock(&port->port_lock_ul); + break; + } + + spin_lock(&port->port_lock_ul); + + if (queue) { + __skb_queue_tail(&d->rx_skb_q, skb); + if ((d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) && + !usb_bam_get_prod_granted(d->usb_bam_type, + d->dst_connection_idx)) { + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock_ul); + return; + } else + queue_work(gbam_wq, &d->write_tobam_w); + } + + /* TODO: Handle flow control gracefully by having + * having call back mechanism from bam driver + */ + if (bam_mux_rx_fctrl_support && + d->rx_skb_q.qlen >= bam_mux_rx_fctrl_en_thld) { + if (!d->rx_flow_control_triggered) { + d->rx_flow_control_triggered = 1; + d->rx_flow_control_enable++; + } + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock_ul); + return; + } + + skb = gbam_alloc_skb_from_pool(port); + if (!skb) { + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock_ul); + return; + } + spin_unlock(&port->port_lock_ul); + + req->buf = skb->data; + req->dma = gbam_get_dma_from_skb(skb); + req->length = bam_mux_rx_req_size; + + if (req->dma != DMA_ERROR_CODE) + req->dma_pre_mapped = true; + else + req->dma_pre_mapped = false; + + req->context = skb; + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status) { + spin_lock(&port->port_lock_ul); + gbam_free_skb_to_pool(port, skb); + spin_unlock(&port->port_lock_ul); + + if (printk_ratelimit()) + pr_err("%s: data rx enqueue err %d\n", + __func__, status); + + spin_lock(&port->port_lock_ul); + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock_ul); + } +} + +static void gbam_endless_rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + int status = req->status; + + pr_debug("%s status: %d\n", __func__, status); +} + +static void gbam_endless_tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + int status = req->status; + + pr_debug("%s status: %d\n", __func__, status); +} + +static void gbam_start_rx(struct gbam_port *port) +{ + struct usb_request *req; + struct bam_ch_info *d; + struct usb_ep *ep; + unsigned long flags; + int ret; + struct sk_buff *skb; + + spin_lock_irqsave(&port->port_lock_ul, flags); + if (!port->port_usb || !port->port_usb->out) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + + d = &port->data_ch; + ep = port->port_usb->out; + + while (port->port_usb && !list_empty(&d->rx_idle)) { + + if (bam_mux_rx_fctrl_support && + d->rx_skb_q.qlen >= bam_mux_rx_fctrl_en_thld) + break; + + req = list_first_entry(&d->rx_idle, struct usb_request, list); + + skb = gbam_alloc_skb_from_pool(port); + if (!skb) + break; + + list_del(&req->list); + req->buf = skb->data; + req->dma = gbam_get_dma_from_skb(skb); + req->length = bam_mux_rx_req_size; + + if (req->dma != DMA_ERROR_CODE) + req->dma_pre_mapped = true; + else + req->dma_pre_mapped = false; + + req->context = skb; + + spin_unlock_irqrestore(&port->port_lock_ul, flags); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock_irqsave(&port->port_lock_ul, flags); + if (ret) { + gbam_free_skb_to_pool(port, skb); + + if (printk_ratelimit()) + pr_err("%s: rx queue failed %d\n", + __func__, ret); + + if (port->port_usb) + list_add(&req->list, &d->rx_idle); + else + usb_ep_free_request(ep, req); + break; + } + } + + spin_unlock_irqrestore(&port->port_lock_ul, flags); +} + +static void gbam_start_endless_rx(struct gbam_port *port) +{ + struct bam_ch_info *d = &port->data_ch; + int status; + struct usb_ep *ep; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock_ul, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + pr_err("%s: port->port_usb is NULL", __func__); + return; + } + + ep = port->port_usb->out; + spin_unlock_irqrestore(&port->port_lock_ul, flags); + pr_debug("%s: enqueue\n", __func__); + status = usb_ep_queue(ep, d->rx_req, GFP_ATOMIC); + if (status) + pr_err("%s: error enqueuing transfer, %d\n", __func__, status); +} + +static void gbam_start_endless_tx(struct gbam_port *port) +{ + struct bam_ch_info *d = &port->data_ch; + int status; + struct usb_ep *ep; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock_dl, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_dl, flags); + pr_err("%s: port->port_usb is NULL", __func__); + return; + } + + ep = port->port_usb->in; + spin_unlock_irqrestore(&port->port_lock_dl, flags); + pr_debug("%s: enqueue\n", __func__); + status = usb_ep_queue(ep, d->tx_req, GFP_ATOMIC); + if (status) + pr_err("%s: error enqueuing transfer, %d\n", __func__, status); +} + +static void gbam_stop_endless_rx(struct gbam_port *port) +{ + struct bam_ch_info *d = &port->data_ch; + int status; + unsigned long flags; + struct usb_ep *ep; + + spin_lock_irqsave(&port->port_lock_ul, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_ul, flags); + pr_err("%s: port->port_usb is NULL", __func__); + return; + } + + ep = port->port_usb->out; + spin_unlock_irqrestore(&port->port_lock_ul, flags); + pr_debug("%s: dequeue\n", __func__); + status = usb_ep_dequeue(ep, d->rx_req); + if (status) + pr_err("%s: error dequeuing transfer, %d\n", __func__, status); +} + +static void gbam_stop_endless_tx(struct gbam_port *port) +{ + struct bam_ch_info *d = &port->data_ch; + int status; + unsigned long flags; + struct usb_ep *ep; + + spin_lock_irqsave(&port->port_lock_dl, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock_dl, flags); + pr_err("%s: port->port_usb is NULL", __func__); + return; + } + + ep = port->port_usb->in; + spin_unlock_irqrestore(&port->port_lock_dl, flags); + pr_debug("%s: dequeue\n", __func__); + status = usb_ep_dequeue(ep, d->tx_req); + if (status) + pr_err("%s: error dequeuing transfer, %d\n", __func__, status); +} + + +/* + * This function configured data fifo based on index passed to get bam2bam + * configuration. + */ +static void configure_data_fifo(enum usb_ctrl bam_type, u8 idx, + struct usb_ep *ep, enum usb_bam_pipe_type pipe_type) +{ + struct u_bam_data_connect_info bam_info; + struct sps_mem_buffer data_fifo = {0}; + + if (pipe_type == USB_BAM_PIPE_BAM2BAM) { + get_bam2bam_connection_info(bam_type, idx, + &bam_info.usb_bam_pipe_idx, + NULL, &data_fifo, NULL); + + msm_data_fifo_config(ep, + data_fifo.phys_base, + data_fifo.size, + bam_info.usb_bam_pipe_idx); + } +} + + +static void gbam_start(void *param, enum usb_bam_pipe_dir dir) +{ + struct gbam_port *port = param; + struct usb_gadget *gadget = NULL; + struct bam_ch_info *d; + unsigned long flags; + + if (port == NULL) { + pr_err("%s: port is NULL\n", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb == NULL) { + pr_err("%s: port_usb is NULL, disconnected\n", __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + gadget = port->port_usb->gadget; + d = &port->data_ch; + spin_unlock_irqrestore(&port->port_lock, flags); + + if (gadget == NULL) { + pr_err("%s: gadget is NULL\n", __func__); + return; + } + + if (dir == USB_TO_PEER_PERIPHERAL) { + if (port->data_ch.src_pipe_type == USB_BAM_PIPE_BAM2BAM) + gbam_start_endless_rx(port); + else { + gbam_start_rx(port); + queue_work(gbam_wq, &d->write_tobam_w); + } + } else { + if (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)) { + configure_data_fifo(d->usb_bam_type, + d->dst_connection_idx, + port->port_usb->in, d->dst_pipe_type); + } + gbam_start_endless_tx(port); + } +} + +static void gbam_stop(void *param, enum usb_bam_pipe_dir dir) +{ + struct gbam_port *port = param; + + if (dir == USB_TO_PEER_PERIPHERAL) { + /* + * Only handling BAM2BAM, as there is no equivelant to + * gbam_stop_endless_rx() for the SYS2BAM use case + */ + if (port->data_ch.src_pipe_type == USB_BAM_PIPE_BAM2BAM) + gbam_stop_endless_rx(port); + } else { + gbam_stop_endless_tx(port); + } +} + +static int _gbam_start_io(struct gbam_port *port, bool in) +{ + unsigned long flags; + int ret = 0; + struct usb_ep *ep; + struct list_head *idle; + unsigned queue_size; + spinlock_t *spinlock; + void (*ep_complete)(struct usb_ep *, struct usb_request *); + + if (in) + spinlock = &port->port_lock_dl; + else + spinlock = &port->port_lock_ul; + + spin_lock_irqsave(spinlock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(spinlock, flags); + return -EBUSY; + } + + if (in) { + ep = port->port_usb->in; + idle = &port->data_ch.tx_idle; + queue_size = bam_mux_tx_q_size; + ep_complete = gbam_epin_complete; + } else { + ep = port->port_usb->out; + if (!ep) + goto out; + idle = &port->data_ch.rx_idle; + queue_size = bam_mux_rx_q_size; + ep_complete = gbam_epout_complete; + } + + ret = gbam_alloc_requests(ep, idle, queue_size, ep_complete, + GFP_ATOMIC); +out: + spin_unlock_irqrestore(spinlock, flags); + if (ret) + pr_err("%s: allocation failed\n", __func__); + + return ret; +} + +static void gbam_start_io(struct gbam_port *port) +{ + unsigned long flags; + + pr_debug("%s: port:%p\n", __func__, port); + + if (_gbam_start_io(port, true)) + return; + + if (_gbam_start_io(port, false)) { + spin_lock_irqsave(&port->port_lock_dl, flags); + if (port->port_usb) + gbam_free_requests(port->port_usb->in, + &port->data_ch.tx_idle); + spin_unlock_irqrestore(&port->port_lock_dl, flags); + return; + } + + /* queue out requests */ + gbam_start_rx(port); +} + +static void gbam_notify(void *p, int event, unsigned long data) +{ + struct gbam_port *port = p; + struct bam_ch_info *d; + struct sk_buff *skb; + + if (port == NULL) + pr_err("BAM DMUX notifying after channel close\n"); + + switch (event) { + case BAM_DMUX_RECEIVE: + skb = (struct sk_buff *)data; + if (port) + gbam_data_recv_cb(p, skb); + else + dev_kfree_skb_any(skb); + break; + case BAM_DMUX_WRITE_DONE: + skb = (struct sk_buff *)data; + if (port) + gbam_data_write_done(p, skb); + else + dev_kfree_skb_any(skb); + break; + case BAM_DMUX_TRANSMIT_SIZE: + d = &port->data_ch; + if (test_bit(BAM_CH_OPENED, &d->flags)) + pr_warn("%s, BAM channel opened already", __func__); + bam_mux_rx_req_size = data; + pr_debug("%s rx_req_size: %lu", __func__, bam_mux_rx_req_size); + break; + } +} + +static void gbam_free_rx_buffers(struct gbam_port *port) +{ + struct sk_buff *skb; + unsigned long flags; + struct bam_ch_info *d; + + spin_lock_irqsave(&port->port_lock_ul, flags); + + if (!port->port_usb || !port->port_usb->out) + goto free_rx_buf_out; + + d = &port->data_ch; + gbam_free_requests(port->port_usb->out, &d->rx_idle); + + while ((skb = __skb_dequeue(&d->rx_skb_q))) + dev_kfree_skb_any(skb); + + gbam_free_rx_skb_idle_list(port); + +free_rx_buf_out: + spin_unlock_irqrestore(&port->port_lock_ul, flags); +} + +static void gbam_free_tx_buffers(struct gbam_port *port) +{ + struct sk_buff *skb; + unsigned long flags; + struct bam_ch_info *d; + + spin_lock_irqsave(&port->port_lock_dl, flags); + + if (!port->port_usb) + goto free_tx_buf_out; + + d = &port->data_ch; + gbam_free_requests(port->port_usb->in, &d->tx_idle); + + while ((skb = __skb_dequeue(&d->tx_skb_q))) + dev_kfree_skb_any(skb); + +free_tx_buf_out: + spin_unlock_irqrestore(&port->port_lock_dl, flags); +} + +static void gbam_free_buffers(struct gbam_port *port) +{ + gbam_free_rx_buffers(port); + gbam_free_tx_buffers(port); +} + +static void gbam_disconnect_work(struct work_struct *w) +{ + struct gbam_port *port = + container_of(w, struct gbam_port, disconnect_w); + struct bam_ch_info *d = &port->data_ch; + + if (!test_bit(BAM_CH_OPENED, &d->flags)) { + pr_err("%s: Bam channel is not opened\n", __func__); + goto exit; + } + + msm_bam_dmux_close(d->id); + clear_bit(BAM_CH_OPENED, &d->flags); +exit: + return; +} + +static void gbam2bam_disconnect_work(struct work_struct *w) +{ + struct gbam_port *port = + container_of(w, struct gbam_port, disconnect_w); + struct bam_ch_info *d; + int ret; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + + if (!port->is_connected) { + pr_debug("%s: Port already disconnected. Bailing out.\n", + __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + port->is_connected = false; + d = &port->data_ch; + + /* + * Unlock the port here and not at the end of this work, + * because we do not want to activate usb_bam, ipa and + * tethe bridge logic in atomic context and wait uneeded time. + * Either way other works will not fire until end of this work + * and event functions (as bam_data_connect) will not influance + * while lower layers connect pipes, etc. + */ + spin_unlock_irqrestore(&port->port_lock, flags); + + if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + ret = usb_bam_disconnect_ipa(d->usb_bam_type, &d->ipa_params); + if (ret) + pr_err("%s: usb_bam_disconnect_ipa failed: err:%d\n", + __func__, ret); + usb_bam_free_fifos(d->usb_bam_type, d->src_connection_idx); + usb_bam_free_fifos(d->usb_bam_type, d->dst_connection_idx); + teth_bridge_disconnect(d->ipa_params.src_client); + /* + * Decrement usage count which was incremented upon cable + * connect or cable disconnect in suspended state + */ + usb_gadget_autopm_put_async(port->gadget); + } +} + +static void gbam_connect_work(struct work_struct *w) +{ + struct gbam_port *port = container_of(w, struct gbam_port, connect_w); + struct bam_ch_info *d = &port->data_ch; + int ret; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + if (!port->port_usb) { + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + return; + } + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + if (!test_bit(BAM_CH_READY, &d->flags)) { + pr_err("%s: Bam channel is not ready\n", __func__); + return; + } + + ret = msm_bam_dmux_open(d->id, port, gbam_notify); + if (ret) { + pr_err("%s: unable open bam ch:%d err:%d\n", + __func__, d->id, ret); + return; + } + + set_bit(BAM_CH_OPENED, &d->flags); + + gbam_start_io(port); + + pr_debug("%s: done\n", __func__); +} + +static void gbam2bam_connect_work(struct work_struct *w) +{ + struct gbam_port *port = container_of(w, struct gbam_port, connect_w); + struct usb_gadget *gadget = NULL; + struct teth_bridge_connect_params connect_params; + struct teth_bridge_init_params teth_bridge_params; + struct bam_ch_info *d; + u32 sps_params; + int ret; + unsigned long flags, flags_ul; + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->last_event == U_BAM_DISCONNECT_E) { + pr_debug("%s: Port is about to disconnected. Bailing out.\n", + __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + port->is_connected = true; + + spin_lock_irqsave(&port->port_lock_ul, flags_ul); + spin_lock(&port->port_lock_dl); + if (!port->port_usb) { + pr_debug("%s: usb cable is disconnected, exiting\n", __func__); + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + gadget = port->port_usb->gadget; + if (!gadget) { + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: port_usb.gadget is NULL, exiting\n", __func__); + return; + } + d = &port->data_ch; + + /* + * Unlock the port here and not at the end of this work, + * because we do not want to activate usb_bam, ipa and + * tethe bridge logic in atomic context and wait uneeded time. + * Either way other works will not fire until end of this work + * and event functions (as bam_data_connect) will not influance + * while lower layers connect pipes, etc. + */ + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + spin_unlock_irqrestore(&port->port_lock, flags); + + d->ipa_params.usb_connection_speed = gadget->speed; + + /* + * Invalidate prod and cons client handles from previous + * disconnect. + */ + d->ipa_params.cons_clnt_hdl = -1; + d->ipa_params.prod_clnt_hdl = -1; + + if (usb_bam_get_pipe_type(d->usb_bam_type, d->ipa_params.src_idx, + &d->src_pipe_type) || + usb_bam_get_pipe_type(d->usb_bam_type, d->ipa_params.dst_idx, + &d->dst_pipe_type)) { + pr_err("%s:usb_bam_get_pipe_type() failed\n", __func__); + return; + } + if (d->dst_pipe_type != USB_BAM_PIPE_BAM2BAM) { + pr_err("%s: no software preparation for DL not using bam2bam\n", + __func__); + return; + } + + usb_bam_alloc_fifos(d->usb_bam_type, d->src_connection_idx); + usb_bam_alloc_fifos(d->usb_bam_type, d->dst_connection_idx); + gadget->bam2bam_func_enabled = true; + + spin_lock_irqsave(&port->port_lock, flags); + /* check if USB cable is disconnected or not */ + if (!port || !port->port_usb) { + pr_debug("%s: cable is disconnected.\n", + __func__); + spin_unlock_irqrestore(&port->port_lock, + flags); + goto free_fifos; + } + if (gadget_is_dwc3(gadget)) { + /* Configure for RX */ + configure_data_fifo(d->usb_bam_type, d->src_connection_idx, + port->port_usb->out, d->src_pipe_type); + sps_params = MSM_SPS_MODE | MSM_DISABLE_WB | MSM_PRODUCER | + d->src_pipe_idx; + d->rx_req->length = 32*1024; + d->rx_req->udc_priv = sps_params; + msm_ep_config(port->port_usb->out, d->rx_req, GFP_ATOMIC); + + /* Configure for TX */ + configure_data_fifo(d->usb_bam_type, d->dst_connection_idx, + port->port_usb->in, d->dst_pipe_type); + sps_params = MSM_SPS_MODE | MSM_DISABLE_WB | d->dst_pipe_idx; + d->tx_req->length = 32*1024; + d->tx_req->udc_priv = sps_params; + msm_ep_config(port->port_usb->in, d->tx_req, GFP_ATOMIC); + + } else { + /* Configure for RX */ + sps_params = (MSM_SPS_MODE | d->src_pipe_idx | + MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER; + d->rx_req->udc_priv = sps_params; + + /* Configure for TX */ + sps_params = (MSM_SPS_MODE | d->dst_pipe_idx | + MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER; + d->tx_req->length = 32*1024; + d->tx_req->udc_priv = sps_params; + + } + spin_unlock_irqrestore(&port->port_lock, flags); + + teth_bridge_params.client = d->ipa_params.src_client; + ret = teth_bridge_init(&teth_bridge_params); + if (ret) { + pr_err("%s:teth_bridge_init() failed\n", __func__); + goto ep_unconfig; + } + + /* Support for UL using system-to-IPA */ + if (d->src_pipe_type == USB_BAM_PIPE_SYS2BAM) { + d->ul_params.teth_priv = + teth_bridge_params.private_data; + d->ul_params.teth_cb = + teth_bridge_params.usb_notify_cb; + d->ipa_params.notify = gbam_ipa_sys2bam_notify_cb; + d->ipa_params.priv = &d->ul_params; + d->ipa_params.reset_pipe_after_lpm = false; + + } else { + d->ipa_params.notify = + teth_bridge_params.usb_notify_cb; + d->ipa_params.priv = + teth_bridge_params.private_data; + d->ipa_params.reset_pipe_after_lpm = + (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)); + } + d->ipa_params.ipa_ep_cfg.mode.mode = IPA_BASIC; + d->ipa_params.skip_ep_cfg = teth_bridge_params.skip_ep_cfg; + d->ipa_params.dir = USB_TO_PEER_PERIPHERAL; + ret = usb_bam_connect_ipa(d->usb_bam_type, &d->ipa_params); + if (ret) { + pr_err("%s: usb_bam_connect_ipa failed: err:%d\n", + __func__, ret); + goto ep_unconfig; + } + + /* Remove support for UL using system-to-IPA towards DL */ + if (d->src_pipe_type == USB_BAM_PIPE_SYS2BAM) { + d->ipa_params.notify = d->ul_params.teth_cb; + d->ipa_params.priv = d->ul_params.teth_priv; + } + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) + d->ipa_params.reset_pipe_after_lpm = + (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)); + else + d->ipa_params.reset_pipe_after_lpm = false; + d->ipa_params.dir = PEER_PERIPHERAL_TO_USB; + ret = usb_bam_connect_ipa(d->usb_bam_type, &d->ipa_params); + if (ret) { + pr_err("%s: usb_bam_connect_ipa failed: err:%d\n", + __func__, ret); + goto ep_unconfig; + } + + gqti_ctrl_update_ipa_pipes(port->port_usb, port->port_num, + d->ipa_params.ipa_prod_ep_idx , + d->ipa_params.ipa_cons_ep_idx); + + connect_params.ipa_usb_pipe_hdl = d->ipa_params.prod_clnt_hdl; + connect_params.usb_ipa_pipe_hdl = d->ipa_params.cons_clnt_hdl; + connect_params.tethering_mode = TETH_TETHERING_MODE_RMNET; + connect_params.client_type = d->ipa_params.src_client; + ret = teth_bridge_connect(&connect_params); + if (ret) { + pr_err("%s:teth_bridge_connect() failed\n", __func__); + goto ep_unconfig; + } + + /* queue in & out requests */ + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM) { + gbam_start_endless_rx(port); + } else { + /* The use-case of UL (OUT) ports using sys2bam is based on + * partial reuse of the system-to-bam_demux code. The following + * lines perform the branching out of the standard bam2bam flow + * on the USB side of the UL channel + */ + if (_gbam_start_io(port, false)) { + pr_err("%s: _gbam_start_io failed\n", __func__); + return; + } + gbam_start_rx(port); + } + gbam_start_endless_tx(port); + + pr_debug("%s: done\n", __func__); + return; + +ep_unconfig: + if (gadget_is_dwc3(gadget)) { + msm_ep_unconfig(port->port_usb->in); + msm_ep_unconfig(port->port_usb->out); + } +free_fifos: + usb_bam_free_fifos(d->usb_bam_type, d->src_connection_idx); + usb_bam_free_fifos(d->usb_bam_type, d->dst_connection_idx); + +} + +static int gbam_wake_cb(void *param) +{ + struct gbam_port *port = (struct gbam_port *)param; + struct usb_gadget *gadget; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + pr_debug("%s: usb cable is disconnected, exiting\n", + __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return -ENODEV; + } + + gadget = port->port_usb->gadget; + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_debug("%s: woken up by peer\n", __func__); + + return usb_gadget_wakeup(gadget); +} + +static void gbam2bam_suspend_work(struct work_struct *w) +{ + struct gbam_port *port = container_of(w, struct gbam_port, suspend_w); + struct bam_ch_info *d; + int ret; + unsigned long flags; + + pr_debug("%s: suspend work started\n", __func__); + + spin_lock_irqsave(&port->port_lock, flags); + + if ((port->last_event == U_BAM_DISCONNECT_E) || + (port->last_event == U_BAM_RESUME_E)) { + pr_debug("%s: Port is about to disconnect/resume. Bail out\n", + __func__); + goto exit; + } + + d = &port->data_ch; + + ret = usb_bam_register_wake_cb(d->usb_bam_type, d->dst_connection_idx, + gbam_wake_cb, port); + if (ret) { + pr_err("%s(): Failed to register BAM wake callback.\n", + __func__); + goto exit; + } + + if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + usb_bam_register_start_stop_cbs(d->usb_bam_type, + d->dst_connection_idx, gbam_start, gbam_stop, port); + + /* + * release lock here because gbam_start() or + * gbam_stop() called from usb_bam_suspend() + * re-acquires port lock. + */ + spin_unlock_irqrestore(&port->port_lock, flags); + usb_bam_suspend(d->usb_bam_type, &d->ipa_params); + spin_lock_irqsave(&port->port_lock, flags); + } + +exit: + /* + * Decrement usage count after IPA handshake is done to allow gadget + * parent to go to lpm. This counter was incremented upon cable connect + */ + usb_gadget_autopm_put_async(port->gadget); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void gbam2bam_resume_work(struct work_struct *w) +{ + struct gbam_port *port = container_of(w, struct gbam_port, resume_w); + struct bam_ch_info *d; + struct usb_gadget *gadget = NULL; + int ret; + unsigned long flags; + + pr_debug("%s: resume work started\n", __func__); + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->last_event == U_BAM_DISCONNECT_E || !port->port_usb) { + pr_debug("%s: usb cable is disconnected, exiting\n", + __func__); + goto exit; + } + + d = &port->data_ch; + gadget = port->port_usb->gadget; + + ret = usb_bam_register_wake_cb(d->usb_bam_type, d->dst_connection_idx, + NULL, NULL); + if (ret) { + pr_err("%s(): Failed to register BAM wake callback.\n", + __func__); + goto exit; + } + + if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + if (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)) { + configure_data_fifo(d->usb_bam_type, + d->src_connection_idx, + port->port_usb->out, d->src_pipe_type); + configure_data_fifo(d->usb_bam_type, + d->dst_connection_idx, + port->port_usb->in, d->dst_pipe_type); + spin_unlock_irqrestore(&port->port_lock, flags); + msm_dwc3_reset_dbm_ep(port->port_usb->in); + spin_lock_irqsave(&port->port_lock, flags); + } + usb_bam_resume(d->usb_bam_type, &d->ipa_params); + } + +exit: + spin_unlock_irqrestore(&port->port_lock, flags); +} + +/* BAM data channel ready, allow attempt to open */ +static int gbam_data_ch_probe(struct platform_device *pdev) +{ + struct gbam_port *port; + struct bam_ch_info *d; + int i; + unsigned long flags; + bool do_work = false; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_bam_ports; i++) { + port = bam_ports[i].port; + d = &port->data_ch; + + if (!strncmp(bam_ch_names[i], pdev->name, + BAM_DMUX_CH_NAME_MAX_LEN)) { + set_bit(BAM_CH_READY, &d->flags); + + /* if usb is online, try opening bam_ch */ + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + if (port->port_usb) + do_work = true; + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + if (do_work) + queue_work(gbam_wq, &port->connect_w); + break; + } + } + + return 0; +} + +/* BAM data channel went inactive, so close it */ +static int gbam_data_ch_remove(struct platform_device *pdev) +{ + struct gbam_port *port; + struct bam_ch_info *d; + struct usb_ep *ep_in = NULL; + struct usb_ep *ep_out = NULL; + unsigned long flags; + int i; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_bam_ports; i++) { + if (!strncmp(bam_ch_names[i], pdev->name, + BAM_DMUX_CH_NAME_MAX_LEN)) { + port = bam_ports[i].port; + d = &port->data_ch; + + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + if (port->port_usb) { + ep_in = port->port_usb->in; + ep_out = port->port_usb->out; + } + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + + if (ep_in) + usb_ep_fifo_flush(ep_in); + if (ep_out) + usb_ep_fifo_flush(ep_out); + + gbam_free_buffers(port); + + msm_bam_dmux_close(d->id); + + /* bam dmux will free all pending skbs */ + d->pending_pkts_with_bam = 0; + d->pending_bytes_with_bam = 0; + + clear_bit(BAM_CH_READY, &d->flags); + clear_bit(BAM_CH_OPENED, &d->flags); + } + } + + return 0; +} + +static void gbam_port_free(int portno) +{ + struct gbam_port *port = bam_ports[portno].port; + struct platform_driver *pdrv = &bam_ports[portno].pdrv; + + if (port) { + kfree(port); + platform_driver_unregister(pdrv); + } +} + +static void gbam2bam_port_free(int portno) +{ + struct gbam_port *port = bam2bam_ports[portno]; + + kfree(port); +} + +static int gbam_port_alloc(int portno) +{ + struct gbam_port *port; + struct bam_ch_info *d; + struct platform_driver *pdrv; + + port = kzalloc(sizeof(struct gbam_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->port_num = portno; + + /* port initialization */ + port->is_connected = false; + spin_lock_init(&port->port_lock_ul); + spin_lock_init(&port->port_lock_dl); + spin_lock_init(&port->port_lock); + INIT_WORK(&port->connect_w, gbam_connect_work); + INIT_WORK(&port->disconnect_w, gbam_disconnect_work); + + /* data ch */ + d = &port->data_ch; + d->port = port; + INIT_LIST_HEAD(&d->tx_idle); + INIT_LIST_HEAD(&d->rx_idle); + INIT_WORK(&d->write_tobam_w, gbam_data_write_tobam); + INIT_WORK(&d->write_tohost_w, gbam_write_data_tohost_w); + skb_queue_head_init(&d->tx_skb_q); + skb_queue_head_init(&d->rx_skb_q); + skb_queue_head_init(&d->rx_skb_idle); + d->id = bam_ch_ids[portno]; + + bam_ports[portno].port = port; + + scnprintf(bam_ch_names[portno], BAM_DMUX_CH_NAME_MAX_LEN, + "bam_dmux_ch_%d", bam_ch_ids[portno]); + pdrv = &bam_ports[portno].pdrv; + pdrv->probe = gbam_data_ch_probe; + pdrv->remove = gbam_data_ch_remove; + pdrv->driver.name = bam_ch_names[portno]; + pdrv->driver.owner = THIS_MODULE; + + platform_driver_register(pdrv); + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +static int gbam2bam_port_alloc(int portno) +{ + struct gbam_port *port; + struct bam_ch_info *d; + + port = kzalloc(sizeof(struct gbam_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->port_num = portno; + + /* port initialization */ + port->is_connected = false; + spin_lock_init(&port->port_lock_ul); + spin_lock_init(&port->port_lock_dl); + spin_lock_init(&port->port_lock); + + INIT_WORK(&port->connect_w, gbam2bam_connect_work); + INIT_WORK(&port->disconnect_w, gbam2bam_disconnect_work); + INIT_WORK(&port->suspend_w, gbam2bam_suspend_work); + INIT_WORK(&port->resume_w, gbam2bam_resume_work); + + /* data ch */ + d = &port->data_ch; + d->port = port; + d->ipa_params.src_client = usb_prod[portno]; + d->ipa_params.dst_client = usb_cons[portno]; + bam2bam_ports[portno] = port; + + /* UL workaround requirements */ + skb_queue_head_init(&d->rx_skb_q); + skb_queue_head_init(&d->rx_skb_idle); + INIT_LIST_HEAD(&d->rx_idle); + INIT_WORK(&d->write_tobam_w, gbam_data_write_tobam); + + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t gbam_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct gbam_port *port; + struct bam_ch_info *d; + char *buf; + unsigned long flags; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < n_bam_ports; i++) { + port = bam_ports[i].port; + if (!port) + continue; + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + + d = &port->data_ch; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "#PORT:%d port:%p data_ch:%p#\n" + "dpkts_to_usbhost: %lu\n" + "dpkts_to_modem: %lu\n" + "dpkts_pwith_bam: %u\n" + "dbytes_pwith_bam: %u\n" + "to_usbhost_dcnt: %u\n" + "tomodem__dcnt: %u\n" + "rx_flow_control_disable_count: %u\n" + "rx_flow_control_enable_count: %u\n" + "rx_flow_control_triggered: %u\n" + "max_num_pkts_pending_with_bam: %u\n" + "max_bytes_pending_with_bam: %u\n" + "delayed_bam_mux_write_done: %u\n" + "tx_buf_len: %u\n" + "rx_buf_len: %u\n" + "data_ch_open: %d\n" + "data_ch_ready: %d\n" + "skb_expand_cnt: %lu\n", + i, port, &port->data_ch, + d->to_host, d->to_modem, + d->pending_pkts_with_bam, + d->pending_bytes_with_bam, + d->tohost_drp_cnt, d->tomodem_drp_cnt, + d->rx_flow_control_disable, + d->rx_flow_control_enable, + d->rx_flow_control_triggered, + d->max_num_pkts_pending_with_bam, + d->max_bytes_pending_with_bam, + d->delayed_bam_mux_write_done, + d->tx_skb_q.qlen, d->rx_skb_q.qlen, + test_bit(BAM_CH_OPENED, &d->flags), + test_bit(BAM_CH_READY, &d->flags), + d->skb_expand_cnt); + + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t gbam_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct gbam_port *port; + struct bam_ch_info *d; + int i; + unsigned long flags; + + for (i = 0; i < n_bam_ports; i++) { + port = bam_ports[i].port; + if (!port) + continue; + + spin_lock_irqsave(&port->port_lock_ul, flags); + spin_lock(&port->port_lock_dl); + + d = &port->data_ch; + + d->to_host = 0; + d->to_modem = 0; + d->pending_pkts_with_bam = 0; + d->pending_bytes_with_bam = 0; + d->tohost_drp_cnt = 0; + d->tomodem_drp_cnt = 0; + d->rx_flow_control_disable = 0; + d->rx_flow_control_enable = 0; + d->rx_flow_control_triggered = 0; + d->max_num_pkts_pending_with_bam = 0; + d->max_bytes_pending_with_bam = 0; + d->delayed_bam_mux_write_done = 0; + d->skb_expand_cnt = 0; + + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags); + } + return count; +} + +const struct file_operations gbam_stats_ops = { + .read = gbam_read_stats, + .write = gbam_reset_stats, +}; + +struct dentry *gbam_dent; +static void gbam_debugfs_init(void) +{ + struct dentry *dfile; + + if (gbam_dent) + return; + + gbam_dent = debugfs_create_dir("usb_rmnet", 0); + if (!gbam_dent || IS_ERR(gbam_dent)) + return; + + dfile = debugfs_create_file("status", 0444, gbam_dent, 0, + &gbam_stats_ops); + if (!dfile || IS_ERR(dfile)) { + debugfs_remove(gbam_dent); + gbam_dent = NULL; + return; + } +} +static void gbam_debugfs_remove(void) +{ + debugfs_remove_recursive(gbam_dent); +} +#else +static inline void gbam_debugfs_init(void) {} +static inline void gbam_debugfs_remove(void) {} +#endif + +void gbam_disconnect(struct grmnet *gr, u8 port_num, enum transport_type trans) +{ + struct gbam_port *port; + unsigned long flags, flags_ul, flags_dl; + struct bam_ch_info *d; + + pr_debug("%s: grmnet:%p port#%d\n", __func__, gr, port_num); + + if (trans == USB_GADGET_XPORT_BAM2BAM) { + pr_err("%s: invalid xport#%d\n", __func__, trans); + return; + } + if (trans == USB_GADGET_XPORT_BAM_DMUX && + port_num >= n_bam_ports) { + pr_err("%s: invalid bam portno#%d\n", + __func__, port_num); + return; + } + + if ((trans == USB_GADGET_XPORT_BAM2BAM_IPA) && + port_num >= n_bam2bam_ports) { + pr_err("%s: invalid bam2bam portno#%d\n", + __func__, port_num); + return; + } + + if (!gr) { + pr_err("%s: grmnet port is null\n", __func__); + return; + } + if (trans == USB_GADGET_XPORT_BAM_DMUX) + port = bam_ports[port_num].port; + else + port = bam2bam_ports[port_num]; + + if (!port) { + pr_err("%s: NULL port", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + /* Already disconnected due to suspend with remote wake disabled */ + if (port->last_event == U_BAM_DISCONNECT_E) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + /* + * Suspend with remote wakeup enabled. Increment usage + * count when disconnect happens in suspended state. + * Corresponding decrement happens in the end of this + * function if IPA handshake is already done or it is done + * in disconnect work after finishing IPA handshake. + */ + if (port->last_event == U_BAM_SUSPEND_E) + usb_gadget_autopm_get_noresume(port->gadget); + + port->port_usb = gr; + + if (trans == USB_GADGET_XPORT_BAM_DMUX) + gbam_free_buffers(port); + else if (trans == USB_GADGET_XPORT_BAM2BAM_IPA) + gbam_free_rx_buffers(port); + + spin_lock_irqsave(&port->port_lock_ul, flags_ul); + spin_lock(&port->port_lock_dl); + port->port_usb = 0; + n_tx_req_queued = 0; + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + + /* disable endpoints */ + if (gr->out) { + usb_ep_disable(gr->out); + if (trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + spin_lock_irqsave(&port->port_lock_ul, flags_ul); + if (d->rx_req) { + usb_ep_free_request(gr->out, d->rx_req); + d->rx_req = NULL; + } + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + } + } + usb_ep_disable(gr->in); + if (trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + spin_lock_irqsave(&port->port_lock_dl, flags_dl); + if (d->tx_req) { + usb_ep_free_request(gr->in, d->tx_req); + d->tx_req = NULL; + } + spin_unlock_irqrestore(&port->port_lock_dl, flags_dl); + } + + /* + * Set endless flag to false as USB Endpoint is already + * disable. + */ + if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) + gr->in->endless = false; + + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM && gr->out) + gr->out->endless = false; + } + + gr->in->driver_data = NULL; + if (gr->out) + gr->out->driver_data = NULL; + + port->last_event = U_BAM_DISCONNECT_E; + /* Disable usb irq for CI gadget. It will be enabled in + * usb_bam_disconnect_pipe() after disconnecting all pipes + * and USB BAM reset is done. + */ + if (!gadget_is_dwc3(port->gadget) && + (trans == USB_GADGET_XPORT_BAM2BAM_IPA)) + msm_usb_irq_disable(true); + + queue_work(gbam_wq, &port->disconnect_w); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +int gbam_connect(struct grmnet *gr, u8 port_num, + enum transport_type trans, u8 src_connection_idx, + u8 dst_connection_idx) +{ + struct gbam_port *port; + struct bam_ch_info *d; + int ret; + unsigned long flags, flags_ul; + + pr_debug("%s: grmnet:%p port#%d\n", __func__, gr, port_num); + + if (!gr) { + pr_err("%s: grmnet port is null\n", __func__); + return -ENODEV; + } + + if (!gr->gadget) { + pr_err("%s: gadget handle not passed\n", __func__); + return -EINVAL; + } + + if (trans == USB_GADGET_XPORT_BAM2BAM) { + pr_err("%s: invalid xport#%d\n", __func__, trans); + return -EINVAL; + } + + if (trans == USB_GADGET_XPORT_BAM_DMUX && port_num >= n_bam_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + if ((trans == USB_GADGET_XPORT_BAM2BAM_IPA) + && port_num >= n_bam2bam_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + if (trans == USB_GADGET_XPORT_BAM_DMUX) + port = bam_ports[port_num].port; + else + port = bam2bam_ports[port_num]; + + if (!port) { + pr_err("%s: NULL port", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + d->trans = trans; + + spin_lock_irqsave(&port->port_lock_ul, flags_ul); + spin_lock(&port->port_lock_dl); + port->port_usb = gr; + port->gadget = port->port_usb->gadget; + + if (trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + d->rx_req = usb_ep_alloc_request(port->port_usb->out, + GFP_ATOMIC); + if (!d->rx_req) { + pr_err("%s: RX request allocation failed\n", __func__); + d->rx_req = NULL; + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + spin_unlock_irqrestore(&port->port_lock, flags); + return -ENOMEM; + } + + d->rx_req->context = port; + d->rx_req->complete = gbam_endless_rx_complete; + d->rx_req->length = 0; + d->rx_req->no_interrupt = 1; + + d->tx_req = usb_ep_alloc_request(port->port_usb->in, + GFP_ATOMIC); + if (!d->tx_req) { + pr_err("%s: TX request allocation failed\n", __func__); + d->tx_req = NULL; + usb_ep_free_request(port->port_usb->out, d->rx_req); + d->rx_req = NULL; + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + spin_unlock_irqrestore(&port->port_lock, flags); + return -ENOMEM; + } + + d->tx_req->context = port; + d->tx_req->complete = gbam_endless_tx_complete; + d->tx_req->length = 0; + d->tx_req->no_interrupt = 1; + } + + if (d->trans == USB_GADGET_XPORT_BAM_DMUX) { + d->to_host = 0; + d->to_modem = 0; + d->pending_pkts_with_bam = 0; + d->pending_bytes_with_bam = 0; + d->tohost_drp_cnt = 0; + d->tomodem_drp_cnt = 0; + d->rx_flow_control_disable = 0; + d->rx_flow_control_enable = 0; + d->rx_flow_control_triggered = 0; + d->max_num_pkts_pending_with_bam = 0; + d->max_bytes_pending_with_bam = 0; + d->delayed_bam_mux_write_done = 0; + } + + spin_unlock(&port->port_lock_dl); + spin_unlock_irqrestore(&port->port_lock_ul, flags_ul); + + if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) { + d->src_connection_idx = src_connection_idx; + d->dst_connection_idx = dst_connection_idx; + d->usb_bam_type = usb_bam_get_bam_type(gr->gadget->name); + d->ipa_params.src_pipe = &(d->src_pipe_idx); + d->ipa_params.dst_pipe = &(d->dst_pipe_idx); + d->ipa_params.src_idx = src_connection_idx; + d->ipa_params.dst_idx = dst_connection_idx; + + /* + * Query pipe type using IPA src/dst index with + * usbbam driver. It is being set either as + * BAM2BAM or SYS2BAM. + */ + if (usb_bam_get_pipe_type(d->usb_bam_type, + d->ipa_params.src_idx, &d->src_pipe_type) || + usb_bam_get_pipe_type(d->usb_bam_type, + d->ipa_params.dst_idx, &d->dst_pipe_type)) { + pr_err("%s:usb_bam_get_pipe_type() failed\n", + __func__); + ret = -EINVAL; + usb_ep_free_request(port->port_usb->out, d->rx_req); + d->rx_req = NULL; + usb_ep_free_request(port->port_usb->in, d->tx_req); + d->tx_req = NULL; + goto exit; + } + /* + * Check for pipe_type. If it is BAM2BAM, then it is required + * to disable Xfer complete and Xfer not ready interrupts for + * that particular endpoint. Hence it set endless flag based + * it which is considered into UDC driver while enabling + * USB Endpoint. + */ + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->in->endless = true; + + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->out->endless = true; + } + + ret = usb_ep_enable(gr->in); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:IN ep:%p", + __func__, gr->in); + usb_ep_free_request(port->port_usb->out, d->rx_req); + d->rx_req = NULL; + usb_ep_free_request(port->port_usb->in, d->tx_req); + d->tx_req = NULL; + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->in->endless = false; + + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->out->endless = false; + goto exit; + } + gr->in->driver_data = port; + + /* + * DPL traffic is routed through BAM-DMUX on some targets. + * DPL function has only 1 IN endpoint. Add out endpoint + * checks for BAM-DMUX transport. + */ + if (gr->out) { + ret = usb_ep_enable(gr->out); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p", + __func__, gr->out); + gr->in->driver_data = 0; + usb_ep_disable(gr->in); + usb_ep_free_request(port->port_usb->out, d->rx_req); + d->rx_req = NULL; + usb_ep_free_request(port->port_usb->in, d->tx_req); + d->tx_req = NULL; + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->in->endless = false; + + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->out->endless = false; + goto exit; + } + gr->out->driver_data = port; + } + + port->last_event = U_BAM_CONNECT_E; + /* + * Increment usage count upon cable connect. Decrement after IPA + * handshake is done in disconnect work (due to cable disconnect) + * or in suspend work. + */ + if (trans == USB_GADGET_XPORT_BAM2BAM_IPA) + usb_gadget_autopm_get_noresume(port->gadget); + queue_work(gbam_wq, &port->connect_w); + + ret = 0; +exit: + spin_unlock_irqrestore(&port->port_lock, flags); + return ret; +} + +int gbam_setup(unsigned int no_bam_port) +{ + int i; + int ret; + int bam_port_start = n_bam_ports; + int total_bam_ports = bam_port_start + no_bam_port; + + pr_debug("%s: requested BAM ports:%d\n", __func__, no_bam_port); + + if (!no_bam_port || total_bam_ports > BAM_N_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, no_bam_port); + return -EINVAL; + } + + if (!gbam_wq) { + gbam_wq = alloc_workqueue("k_gbam", WQ_UNBOUND | + WQ_MEM_RECLAIM, 1); + if (!gbam_wq) { + pr_err("%s: Unable to create workqueue gbam_wq\n", + __func__); + return -ENOMEM; + } + } + + for (i = bam_port_start; i < (bam_port_start + no_bam_port); i++) { + n_bam_ports++; + pr_debug("gbam_port_alloc called for %d\n", i); + ret = gbam_port_alloc(i); + if (ret) { + n_bam_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_bam_ports; + } + } + + gbam_debugfs_init(); + + return bam_port_start; + +free_bam_ports: + for (i = 0; i < n_bam_ports; i++) + gbam_port_free(i); + destroy_workqueue(gbam_wq); + + return ret; +} + +int gbam2bam_setup(unsigned int no_bam2bam_port) +{ + int i; + int ret; + int bam2bam_port_start = n_bam2bam_ports; + int total_bam2bam_ports = bam2bam_port_start + no_bam2bam_port; + + pr_debug("%s: requested BAM2BAM ports:%d\n", __func__, no_bam2bam_port); + + if (!no_bam2bam_port || total_bam2bam_ports > BAM2BAM_N_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, no_bam2bam_port); + return -EINVAL; + } + + if (!gbam_wq) { + gbam_wq = alloc_workqueue("k_gbam", WQ_UNBOUND | + WQ_MEM_RECLAIM, 1); + if (!gbam_wq) { + pr_err("%s: Unable to create workqueue gbam_wq\n", + __func__); + return -ENOMEM; + } + } + + for (i = bam2bam_port_start; i < (bam2bam_port_start + + no_bam2bam_port); i++) { + n_bam2bam_ports++; + ret = gbam2bam_port_alloc(i); + if (ret) { + n_bam2bam_ports--; + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_bam2bam_ports; + } + } + + gbam_debugfs_init(); + + return bam2bam_port_start; + +free_bam2bam_ports: + for (i = 0; i < n_bam2bam_ports; i++) + gbam2bam_port_free(i); + destroy_workqueue(gbam_wq); + + return ret; +} + +void gbam_cleanup(void) +{ + gbam_debugfs_remove(); +} + +void gbam_suspend(struct grmnet *gr, u8 port_num, enum transport_type trans) +{ + struct gbam_port *port; + struct bam_ch_info *d; + unsigned long flags; + + if (trans != USB_GADGET_XPORT_BAM2BAM_IPA) + return; + + port = bam2bam_ports[port_num]; + + if (!port) { + pr_err("%s: NULL port", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + + pr_debug("%s: suspended port %d\n", __func__, port_num); + + port->last_event = U_BAM_SUSPEND_E; + queue_work(gbam_wq, &port->suspend_w); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +void gbam_resume(struct grmnet *gr, u8 port_num, enum transport_type trans) +{ + struct gbam_port *port; + struct bam_ch_info *d; + unsigned long flags; + + if (trans != USB_GADGET_XPORT_BAM2BAM_IPA) + return; + + port = bam2bam_ports[port_num]; + + if (!port) { + pr_err("%s: NULL port", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + + pr_debug("%s: resumed port %d\n", __func__, port_num); + + port->last_event = U_BAM_RESUME_E; + /* + * 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 bam_disconnect + * in suspended state. + */ + usb_gadget_autopm_get_noresume(port->gadget); + queue_work(gbam_wq, &port->resume_w); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +int gbam_mbim_connect(struct usb_gadget *g, struct usb_ep *in, + struct usb_ep *out) +{ + struct grmnet *gr; + + gr = kzalloc(sizeof(*gr), GFP_ATOMIC); + if (!gr) + return -ENOMEM; + gr->in = in; + gr->out = out; + gr->gadget = g; + + return gbam_connect(gr, 0, USB_GADGET_XPORT_BAM_DMUX, 0, 0); +} + +void gbam_mbim_disconnect(void) +{ + struct gbam_port *port = bam_ports[0].port; + struct grmnet *gr = port->port_usb; + + if (!gr) { + pr_err("%s: port_usb is NULL\n", __func__); + return; + } + + gbam_disconnect(gr, 0, USB_GADGET_XPORT_BAM_DMUX); + kfree(gr); +} + +int gbam_mbim_setup(void) +{ + int ret = 0; + + /* + * MBIM requires only 1 USB_GADGET_XPORT_BAM_DMUX + * port. The port is always 0 and is shared + * between RMNET and MBIM. + */ + if (!n_bam_ports) + ret = gbam_setup(1); + + return ret; +} diff --git a/drivers/usb/gadget/function/u_bam_data.c b/drivers/usb/gadget/function/u_bam_data.c new file mode 100644 index 000000000000..ff32b391a3e9 --- /dev/null +++ b/drivers/usb/gadget/function/u_bam_data.c @@ -0,0 +1,2113 @@ +/* Copyright (c) 2012-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. + */ + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include "u_bam_data.h" + +#define BAM_DATA_RX_Q_SIZE 128 +#define BAM_DATA_MUX_RX_REQ_SIZE 2048 /* Must be 1KB aligned */ +#define BAM_DATA_PENDING_LIMIT 220 + +#define SYS_BAM_RX_PKT_FLOW_CTRL_SUPPORT 1 +#define SYS_BAM_RX_PKT_FCTRL_EN_TSHOLD 500 +#define SYS_BAM_RX_PKT_FCTRL_DIS_TSHOLD 300 + +static unsigned int bam_ipa_rx_fctrl_support = SYS_BAM_RX_PKT_FLOW_CTRL_SUPPORT; +module_param(bam_ipa_rx_fctrl_support, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_ipa_rx_fctrl_en_thld = SYS_BAM_RX_PKT_FCTRL_EN_TSHOLD; +module_param(bam_ipa_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_ipa_rx_fctrl_dis_thld = SYS_BAM_RX_PKT_FCTRL_DIS_TSHOLD; +module_param(bam_ipa_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +static struct workqueue_struct *bam_data_wq; +static int n_bam2bam_data_ports; + +unsigned int bam_data_rx_q_size = BAM_DATA_RX_Q_SIZE; +module_param(bam_data_rx_q_size, uint, S_IRUGO | S_IWUSR); + +static unsigned int bam_data_mux_rx_req_size = BAM_DATA_MUX_RX_REQ_SIZE; +module_param(bam_data_mux_rx_req_size, uint, S_IRUGO | S_IWUSR); + +#define SPS_PARAMS_SPS_MODE BIT(5) +#define SPS_PARAMS_TBE BIT(6) +#define MSM_VENDOR_ID BIT(16) + +struct rndis_data_ch_info { + /* this provides downlink (device->host i.e host) side configuration*/ + u32 dl_max_transfer_size; + /* this provides uplink (host->device i.e device) side configuration */ + u32 ul_max_transfer_size; + u32 ul_max_packets_number; + bool ul_aggregation_enable; + u32 prod_clnt_hdl; + u32 cons_clnt_hdl; + void *priv; +}; + +struct sys2ipa_sw_data { + void *teth_priv; + ipa_notify_cb teth_cb; +}; + +struct bam_data_ch_info { + unsigned long flags; + unsigned id; + + struct bam_data_port *port; + struct work_struct write_tobam_w; + + struct usb_request *rx_req; + struct usb_request *tx_req; + + u32 src_pipe_idx; + u32 dst_pipe_idx; + u8 src_connection_idx; + u8 dst_connection_idx; + enum usb_ctrl usb_bam_type; + + enum function_type func_type; + enum transport_type trans; + struct usb_bam_connect_ipa_params ipa_params; + + /* UL workaround parameters */ + struct sys2ipa_sw_data ul_params; + struct list_head rx_idle; + struct sk_buff_head rx_skb_q; + int total_skb; + int freed_skb; + int freed_rx_reqs; + int alloc_rx_reqs; + struct sk_buff_head rx_skb_idle; + enum usb_bam_pipe_type src_pipe_type; + enum usb_bam_pipe_type dst_pipe_type; + unsigned int pending_with_bam; + int rx_buffer_size; + + unsigned int rx_flow_control_disable; + unsigned int rx_flow_control_enable; + unsigned int rx_flow_control_triggered; + /* + * used for RNDIS/ECM network interface based design + * to indicate ecm/rndis pipe connect notifiaction is sent + * to ecm_ipa/rndis_ipa. + */ + atomic_t pipe_connect_notified; + bool tx_req_dequeued; + bool rx_req_dequeued; +}; + +enum u_bam_data_event_type { + U_BAM_DATA_DISCONNECT_E = 0, + U_BAM_DATA_CONNECT_E, + U_BAM_DATA_SUSPEND_E, + U_BAM_DATA_RESUME_E +}; + +struct bam_data_port { + bool is_ipa_connected; + enum u_bam_data_event_type last_event; + unsigned port_num; + spinlock_t port_lock; + unsigned int ref_count; + struct data_port *port_usb; + struct usb_gadget *gadget; + struct bam_data_ch_info data_ch; + + struct work_struct connect_w; + struct work_struct disconnect_w; + struct work_struct suspend_w; + struct work_struct resume_w; +}; +struct usb_bam_data_connect_info { + u32 usb_bam_pipe_idx; + u32 peer_pipe_idx; + u32 usb_bam_handle; +}; + +struct bam_data_port *bam2bam_data_ports[BAM2BAM_DATA_N_PORTS]; +static struct rndis_data_ch_info rndis_data; + +static void bam2bam_data_suspend_work(struct work_struct *w); +static void bam2bam_data_resume_work(struct work_struct *w); +static void bam_data_free_reqs(struct bam_data_port *port); + +/*----- sys2bam towards the IPA (UL workaround) --------------- */ + +static int bam_data_alloc_requests(struct usb_ep *ep, struct list_head *head, + int num, + void (*cb)(struct usb_ep *ep, struct usb_request *), + gfp_t flags) +{ + int i; + struct bam_data_port *port = ep->driver_data; + struct bam_data_ch_info *d = &port->data_ch; + struct usb_request *req; + + pr_debug("%s: ep:%p head:%p num:%d cb:%p", __func__, + ep, head, num, cb); + + if (d->alloc_rx_reqs) { + pr_err("%s(): reqs are already allocated.\n", __func__); + WARN_ON(1); + return -EINVAL; + } + + for (i = 0; i < num; i++) { + req = usb_ep_alloc_request(ep, flags); + if (!req) { + pr_err("%s: req allocated:%d\n", __func__, i); + return list_empty(head) ? -ENOMEM : 0; + } + d->alloc_rx_reqs++; + req->complete = cb; + list_add_tail(&req->list, head); + } + + return 0; +} + +static inline dma_addr_t bam_data_get_dma_from_skb(struct sk_buff *skb) +{ + return *((dma_addr_t *)(skb->cb)); +} + +/* This function should be called with port_lock lock taken */ +static struct sk_buff *bam_data_alloc_skb_from_pool( + struct bam_data_port *port) +{ + struct bam_data_ch_info *d; + struct sk_buff *skb = NULL; + dma_addr_t skb_buf_dma_addr; + struct data_port *data_port; + struct usb_gadget *gadget; + + if (!port) + return NULL; + d = &port->data_ch; + if (!d) + return NULL; + + if (d->rx_skb_idle.qlen == 0) { + /* + * In case skb idle pool is empty, we allow to allocate more + * skbs so we dynamically enlarge the pool size when needed. + * Therefore, in steady state this dynamic allocation will + * stop when the pool will arrive to its optimal size. + */ + pr_debug("%s: allocate skb\n", __func__); + skb = alloc_skb(d->rx_buffer_size + BAM_MUX_HDR, GFP_ATOMIC); + if (!skb) { + pr_err("%s: alloc skb failed\n", __func__); + goto alloc_exit; + } + + d->total_skb++; + skb_reserve(skb, BAM_MUX_HDR); + + data_port = port->port_usb; + if (data_port && data_port->cdev && data_port->cdev->gadget) { + gadget = data_port->cdev->gadget; + + skb_buf_dma_addr = + dma_map_single(&gadget->dev, skb->data, + d->rx_buffer_size, DMA_BIDIRECTIONAL); + + if (dma_mapping_error(&gadget->dev, skb_buf_dma_addr)) { + pr_err("%s: Could not DMA map SKB buffer\n", + __func__); + skb_buf_dma_addr = DMA_ERROR_CODE; + } + } else { + pr_err("%s: Could not DMA map SKB buffer\n", __func__); + skb_buf_dma_addr = DMA_ERROR_CODE; + } + + memcpy(skb->cb, &skb_buf_dma_addr, + sizeof(skb_buf_dma_addr)); + + } else { + pr_debug("%s: pull skb from pool\n", __func__); + skb = __skb_dequeue(&d->rx_skb_idle); + } + +alloc_exit: + return skb; +} + +static void bam_data_free_skb_to_pool( + struct bam_data_port *port, + struct sk_buff *skb) +{ + struct bam_data_ch_info *d; + + if (!port) { + dev_kfree_skb_any(skb); + return; + } + d = &port->data_ch; + if (!d) { + dev_kfree_skb_any(skb); + return; + } + + skb->len = 0; + skb_reset_tail_pointer(skb); + __skb_queue_tail(&d->rx_skb_idle, skb); +} + +static void bam_data_write_done(void *p, struct sk_buff *skb) +{ + struct bam_data_port *port = p; + struct bam_data_ch_info *d = &port->data_ch; + unsigned long flags; + + if (!skb) + return; + + spin_lock_irqsave(&port->port_lock, flags); + bam_data_free_skb_to_pool(port, skb); + + d->pending_with_bam--; + + pr_debug("%s: port:%p d:%p pbam:%u, pno:%d\n", __func__, + port, d, d->pending_with_bam, port->port_num); + + spin_unlock_irqrestore(&port->port_lock, flags); + + queue_work(bam_data_wq, &d->write_tobam_w); +} + +static void bam_data_ipa_sys2bam_notify_cb(void *priv, + enum ipa_dp_evt_type event, unsigned long data) +{ + struct sys2ipa_sw_data *ul = (struct sys2ipa_sw_data *)priv; + struct bam_data_port *port; + struct bam_data_ch_info *d; + + switch (event) { + case IPA_WRITE_DONE: + d = container_of(ul, struct bam_data_ch_info, ul_params); + port = container_of(d, struct bam_data_port, data_ch); + /* call into bam_demux functionality that'll recycle the data */ + bam_data_write_done(port, (struct sk_buff *)(data)); + break; + case IPA_RECEIVE: + /* call the callback given by tethering driver init function + * (and was given to ipa_connect) + */ + if (ul->teth_cb) + ul->teth_cb(ul->teth_priv, event, data); + break; + default: + /* unexpected event */ + pr_err("%s: unexpected event %d\n", __func__, event); + break; + } +} + + +static void bam_data_start_rx(struct bam_data_port *port) +{ + struct usb_request *req; + struct bam_data_ch_info *d; + struct usb_ep *ep; + int ret; + struct sk_buff *skb; + unsigned long flags; + + if (!port->port_usb) { + return; + } + + d = &port->data_ch; + ep = port->port_usb->out; + + spin_lock_irqsave(&port->port_lock, flags); + while (port->port_usb && !list_empty(&d->rx_idle)) { + + if (bam_ipa_rx_fctrl_support && + d->rx_skb_q.qlen >= bam_ipa_rx_fctrl_en_thld) + break; + + req = list_first_entry(&d->rx_idle, struct usb_request, list); + skb = bam_data_alloc_skb_from_pool(port); + if (!skb) + break; + list_del(&req->list); + req->buf = skb->data; + req->dma = bam_data_get_dma_from_skb(skb); + req->length = d->rx_buffer_size; + + if (req->dma != DMA_ERROR_CODE) + req->dma_pre_mapped = true; + else + req->dma_pre_mapped = false; + + req->context = skb; + spin_unlock_irqrestore(&port->port_lock, flags); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock_irqsave(&port->port_lock, flags); + if (ret) { + bam_data_free_skb_to_pool(port, skb); + + pr_err("%s: rx queue failed %d\n", __func__, ret); + + if (port->port_usb) + list_add(&req->list, &d->rx_idle); + else + usb_ep_free_request(ep, req); + break; + } + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void bam_data_epout_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct bam_data_port *port = ep->driver_data; + struct bam_data_ch_info *d = &port->data_ch; + struct sk_buff *skb = req->context; + int status = req->status; + int queue = 0; + unsigned long flags; + + switch (status) { + case 0: + skb_put(skb, req->actual); + queue = 1; + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* cable disconnection */ + spin_lock_irqsave(&port->port_lock, flags); + bam_data_free_skb_to_pool(port, skb); + d->freed_rx_reqs++; + spin_unlock_irqrestore(&port->port_lock, flags); + req->buf = 0; + usb_ep_free_request(ep, req); + return; + default: + pr_err("%s: %s response error %d, %d/%d\n", __func__, + ep->name, status, req->actual, req->length); + spin_lock_irqsave(&port->port_lock, flags); + bam_data_free_skb_to_pool(port, skb); + spin_unlock_irqrestore(&port->port_lock, flags); + break; + } + + spin_lock(&port->port_lock); + if (queue) { + __skb_queue_tail(&d->rx_skb_q, skb); + if (!usb_bam_get_prod_granted(d->usb_bam_type, + d->dst_connection_idx)) { + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock); + pr_err_ratelimited("usb bam prod is not granted.\n"); + return; + } else + queue_work(bam_data_wq, &d->write_tobam_w); + } + + if (bam_mux_rx_fctrl_support && + d->rx_skb_q.qlen >= bam_ipa_rx_fctrl_en_thld) { + if (!d->rx_flow_control_triggered) { + d->rx_flow_control_triggered = 1; + d->rx_flow_control_enable++; + } + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock); + return; + } + + skb = bam_data_alloc_skb_from_pool(port); + if (!skb) { + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock); + return; + } + spin_unlock(&port->port_lock); + + req->buf = skb->data; + req->dma = bam_data_get_dma_from_skb(skb); + req->length = d->rx_buffer_size; + + if (req->dma != DMA_ERROR_CODE) + req->dma_pre_mapped = true; + else + req->dma_pre_mapped = false; + + req->context = skb; + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status) { + pr_err_ratelimited("%s: data rx enqueue err %d\n", + __func__, status); + spin_lock(&port->port_lock); + bam_data_free_skb_to_pool(port, skb); + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock); + } +} +/* It should be called with port_lock acquire. */ +static int bam_data_sys2bam_alloc_req(struct bam_data_port *port, bool in) +{ + int ret; + struct usb_ep *ep; + struct list_head *idle; + unsigned queue_size; + void (*ep_complete)(struct usb_ep *, struct usb_request *); + + if (!port->port_usb) + return -EBUSY; + if (in) + return -ENODEV; + + ep = port->port_usb->out; + idle = &port->data_ch.rx_idle; + queue_size = bam_data_rx_q_size; + ep_complete = bam_data_epout_complete; + + ret = bam_data_alloc_requests(ep, idle, queue_size, ep_complete, + GFP_ATOMIC); + if (ret) { + pr_err("%s: allocation failed\n", __func__); + } + + return ret; +} + +static void bam_data_write_toipa(struct work_struct *w) +{ + struct bam_data_port *port; + struct bam_data_ch_info *d; + struct sk_buff *skb; + int ret; + int qlen; + unsigned long flags; + dma_addr_t skb_dma_addr; + struct ipa_tx_meta ipa_meta = {0x0}; + + d = container_of(w, struct bam_data_ch_info, write_tobam_w); + port = d->port; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + while (d->pending_with_bam < BAM_PENDING_PKTS_LIMIT && + usb_bam_get_prod_granted(d->usb_bam_type, + d->dst_connection_idx)) { + skb = __skb_dequeue(&d->rx_skb_q); + if (!skb) + break; + + d->pending_with_bam++; + + pr_debug("%s: port:%p d:%p pbam:%u pno:%d\n", __func__, + port, d, d->pending_with_bam, port->port_num); + + spin_unlock_irqrestore(&port->port_lock, flags); + + skb_dma_addr = bam_data_get_dma_from_skb(skb); + if (skb_dma_addr != DMA_ERROR_CODE) { + ipa_meta.dma_address = skb_dma_addr; + ipa_meta.dma_address_valid = true; + } + + ret = ipa_tx_dp(IPA_CLIENT_USB_PROD, skb, &ipa_meta); + + spin_lock_irqsave(&port->port_lock, flags); + if (ret) { + pr_debug_ratelimited("%s: write error:%d\n", + __func__, ret); + d->pending_with_bam--; + bam_data_free_skb_to_pool(port, skb); + break; + } + } + + qlen = d->rx_skb_q.qlen; + spin_unlock_irqrestore(&port->port_lock, flags); + + if (qlen < bam_ipa_rx_fctrl_dis_thld) { + if (d->rx_flow_control_triggered) { + d->rx_flow_control_disable++; + d->rx_flow_control_triggered = 0; + } + bam_data_start_rx(port); + } + +} + +/*------------data_path----------------------------*/ + +static void bam_data_endless_rx_complete(struct usb_ep *ep, + struct usb_request *req) +{ + int status = req->status; + + pr_debug("%s: status: %d\n", __func__, status); +} + +static void bam_data_endless_tx_complete(struct usb_ep *ep, + struct usb_request *req) +{ + int status = req->status; + + pr_debug("%s: status: %d\n", __func__, status); +} + +static void bam_data_start_endless_rx(struct bam_data_port *port) +{ + struct bam_data_ch_info *d = &port->data_ch; + struct usb_ep *ep; + unsigned long flags; + int status; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb || !d->rx_req) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + ep = port->port_usb->out; + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_debug("%s: enqueue\n", __func__); + status = usb_ep_queue(ep, d->rx_req, GFP_ATOMIC); + if (status) + pr_err("error enqueuing transfer, %d\n", status); +} + +static void bam_data_start_endless_tx(struct bam_data_port *port) +{ + struct bam_data_ch_info *d = &port->data_ch; + struct usb_ep *ep; + unsigned long flags; + int status; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb || !d->tx_req) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + ep = port->port_usb->in; + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_debug("%s: enqueue\n", __func__); + status = usb_ep_queue(ep, d->tx_req, GFP_ATOMIC); + if (status) + pr_err("error enqueuing transfer, %d\n", status); +} + +static void bam_data_stop_endless_rx(struct bam_data_port *port) +{ + struct bam_data_ch_info *d = &port->data_ch; + unsigned long flags; + int status; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + d->rx_req_dequeued = true; + + pr_debug("%s: dequeue\n", __func__); + status = usb_ep_dequeue(port->port_usb->out, d->rx_req); + if (status) + pr_err("%s: error dequeuing transfer, %d\n", __func__, status); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void bam_data_stop_endless_tx(struct bam_data_port *port) +{ + struct bam_data_ch_info *d = &port->data_ch; + struct usb_ep *ep; + unsigned long flags; + int status; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + ep = port->port_usb->in; + d->tx_req_dequeued = true; + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_debug("%s: dequeue\n", __func__); + status = usb_ep_dequeue(ep, d->tx_req); + if (status) + pr_err("%s: error dequeuing transfer, %d\n", __func__, status); +} + +static void bam2bam_free_rx_skb_idle_list(struct bam_data_port *port) +{ + struct bam_data_ch_info *d; + struct sk_buff *skb; + dma_addr_t dma_addr; + struct usb_gadget *gadget = NULL; + + if (!port) { + pr_err("%s(): Port is NULL.\n", __func__); + return; + } + + d = &port->data_ch; + if (!d) { + pr_err("%s(): port->data_ch is NULL.\n", __func__); + return; + } + + if (!port->port_usb) { + pr_err("%s(): port->port_usb is NULL.\n", __func__); + return; + } + + if (!port->port_usb->cdev) { + pr_err("port->port_usb->cdev is NULL"); + return; + } + + gadget = port->port_usb->cdev->gadget; + if (!gadget) { + pr_err("%s(): gadget is NULL.\n", __func__); + return; + } + + while (d->rx_skb_idle.qlen > 0) { + skb = __skb_dequeue(&d->rx_skb_idle); + dma_addr = gbam_get_dma_from_skb(skb); + + if (gadget && dma_addr != DMA_ERROR_CODE) { + dma_unmap_single(&gadget->dev, dma_addr, + bam_mux_rx_req_size, DMA_BIDIRECTIONAL); + dma_addr = DMA_ERROR_CODE; + memcpy(skb->cb, &dma_addr, sizeof(dma_addr)); + } + dev_kfree_skb_any(skb); + d->freed_skb++; + } + + pr_debug("%s(): Freed %d SKBs from rx_skb_idle queue\n", __func__, + d->freed_skb); +} + +/* + * bam_data_ipa_disconnect()- Perform USB IPA function level disconnect + * struct bam_data_ch_info - Per USB IPA port data structure + * + * Make sure to call IPA rndis/ecm/mbim related disconnect APIs() only + * if those APIs init counterpart is already performed. + * MBIM: teth_bridge_connect() is NO_OPS and teth_bridge_init() is + * being called with atomic context on cable connect, hence there is no + * need to consider for this check. pipe_connect_notified is being used + * for RNDIS/ECM driver due to its different design with usage of + * network interface created by IPA driver. + */ +static void bam_data_ipa_disconnect(struct bam_data_ch_info *d) +{ + pr_debug("%s(): pipe_connect_notified:%d\n", + __func__, atomic_read(&d->pipe_connect_notified)); + /* + * Check if pipe_connect_notified is set to 1, then perform disconnect + * part and set pipe_connect_notified to zero. + */ + if (atomic_xchg(&d->pipe_connect_notified, 0) == 1) { + void *priv; + + if (d->func_type == USB_FUNC_ECM) { + priv = ecm_qc_get_ipa_priv(); + ecm_ipa_disconnect(priv); + } else if (d->func_type == USB_FUNC_RNDIS) { + priv = rndis_qc_get_ipa_priv(); + rndis_ipa_pipe_disconnect_notify(priv); + } + pr_debug("%s(): net interface is disconnected.\n", __func__); + } + + if (d->func_type == USB_FUNC_MBIM) { + pr_debug("%s(): teth_bridge() disconnected\n", __func__); + teth_bridge_disconnect(d->ipa_params.src_client); + } +} + +static void bam2bam_data_disconnect_work(struct work_struct *w) +{ + struct bam_data_port *port = + container_of(w, struct bam_data_port, disconnect_w); + struct bam_data_ch_info *d; + unsigned long flags; + int ret; + + spin_lock_irqsave(&port->port_lock, flags); + + if (!port->is_ipa_connected) { + pr_debug("%s: Already disconnected. Bailing out.\n", __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + d = &port->data_ch; + + /* + * Unlock the port here and not at the end of this work, + * because we do not want to activate usb_bam, ipa and + * tethe bridge logic in atomic context and wait uneeded time. + * Either way other works will not fire until end of this work + * and event functions (as bam_data_connect) will not influance + * while lower layers connect pipes, etc. + */ + spin_unlock_irqrestore(&port->port_lock, flags); + + ret = usb_bam_disconnect_ipa(d->usb_bam_type, &d->ipa_params); + if (ret) + pr_err("usb_bam_disconnect_ipa failed: err:%d\n", ret); + usb_bam_free_fifos(d->usb_bam_type, d->src_connection_idx); + usb_bam_free_fifos(d->usb_bam_type, d->dst_connection_idx); + + /* + * NOTE: it is required to disconnect USB and IPA BAM related pipes + * before calling IPA tethered function related disconnect API. IPA + * tethered function related disconnect API delete depedency graph + * with IPA RM which would results into IPA not pulling data although + * there is pending data on USB BAM producer pipe. + */ + bam_data_ipa_disconnect(d); + spin_lock_irqsave(&port->port_lock, flags); + port->is_ipa_connected = false; + + /* + * Decrement usage count which was incremented + * upon cable connect or cable disconnect in suspended state. + */ + usb_gadget_autopm_put_async(port->gadget); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_debug("Disconnect workqueue done (port %p)\n", port); +} +/* + * This function configured data fifo based on index passed to get bam2bam + * configuration. + */ +static void configure_usb_data_fifo(enum usb_ctrl bam_type, + u8 idx, struct usb_ep *ep, enum usb_bam_pipe_type pipe_type) +{ + struct u_bam_data_connect_info bam_info; + struct sps_mem_buffer data_fifo = {0}; + + if (pipe_type == USB_BAM_PIPE_BAM2BAM) { + get_bam2bam_connection_info(bam_type, idx, + &bam_info.usb_bam_pipe_idx, + NULL, &data_fifo, NULL); + + msm_data_fifo_config(ep, + data_fifo.phys_base, + data_fifo.size, + bam_info.usb_bam_pipe_idx); + } +} + +/* Start RX transfers according to pipe_type */ +static inline void bam_data_start_rx_transfers(struct bam_data_ch_info *d, + struct bam_data_port *port) +{ + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM) + bam_data_start_endless_rx(port); + else + bam_data_start_rx(port); +} + +static void bam2bam_data_connect_work(struct work_struct *w) +{ + struct bam_data_port *port = container_of(w, struct bam_data_port, + connect_w); + struct teth_bridge_connect_params connect_params; + struct teth_bridge_init_params teth_bridge_params; + struct bam_data_ch_info *d; + struct data_port *d_port; + struct usb_gadget *gadget = NULL; + u32 sps_params; + int ret; + unsigned long flags; + + pr_debug("%s: Connect workqueue started", __func__); + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + d_port = port->port_usb; + + if (port->last_event == U_BAM_DATA_DISCONNECT_E) { + pr_debug("%s: Port is about to disconnect. Bail out.\n", + __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + if (d_port && d_port->cdev) + gadget = d_port->cdev->gadget; + + if (!gadget) { + pr_err("%s: NULL gadget\n", __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + if (!port->port_usb) { + pr_err("port_usb is NULL"); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + if (!port->port_usb->out) { + pr_err("port_usb->out (bulk out ep) is NULL"); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + /* + * check if connect_w got called two times during RNDIS resume as + * explicit flow control is called to start data transfers after + * bam_data_connect() + */ + if (port->is_ipa_connected) { + pr_debug("IPA connect is already done & Transfers started\n"); + spin_unlock_irqrestore(&port->port_lock, flags); + usb_gadget_autopm_put_async(port->gadget); + return; + } + + d->ipa_params.usb_connection_speed = gadget->speed; + d->ipa_params.cons_clnt_hdl = -1; + d->ipa_params.prod_clnt_hdl = -1; + + if (d->dst_pipe_type != USB_BAM_PIPE_BAM2BAM) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: no software preparation for DL not using bam2bam\n", + __func__); + return; + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + usb_bam_alloc_fifos(d->usb_bam_type, d->src_connection_idx); + usb_bam_alloc_fifos(d->usb_bam_type, d->dst_connection_idx); + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + pr_err("Disconnected.port_usb is NULL\n"); + spin_unlock_irqrestore(&port->port_lock, flags); + goto free_fifos; + } + + if (gadget_is_dwc3(gadget)) { + /* Configure RX */ + configure_usb_data_fifo(d->usb_bam_type, + d->src_connection_idx, + port->port_usb->out, d->src_pipe_type); + sps_params = MSM_SPS_MODE | MSM_DISABLE_WB + | MSM_PRODUCER | d->src_pipe_idx; + d->rx_req->length = 32*1024; + d->rx_req->udc_priv = sps_params; + msm_ep_config(port->port_usb->out, d->rx_req, GFP_ATOMIC); + + /* Configure TX */ + configure_usb_data_fifo(d->usb_bam_type, + d->dst_connection_idx, + port->port_usb->in, d->dst_pipe_type); + sps_params = MSM_SPS_MODE | MSM_DISABLE_WB + | d->dst_pipe_idx; + d->tx_req->length = 32*1024; + d->tx_req->udc_priv = sps_params; + msm_ep_config(port->port_usb->in, d->tx_req, GFP_ATOMIC); + + } else { + /* Configure RX */ + sps_params = (SPS_PARAMS_SPS_MODE | d->src_pipe_idx | + MSM_VENDOR_ID) & ~SPS_PARAMS_TBE; + d->rx_req->udc_priv = sps_params; + + /* Configure TX */ + sps_params = (SPS_PARAMS_SPS_MODE | d->dst_pipe_idx | + MSM_VENDOR_ID) & ~SPS_PARAMS_TBE; + d->tx_req->udc_priv = sps_params; + } + + if (d->func_type == USB_FUNC_MBIM) { + teth_bridge_params.client = d->ipa_params.src_client; + ret = teth_bridge_init(&teth_bridge_params); + if (ret) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s:teth_bridge_init() failed\n", + __func__); + goto free_fifos; + } + d->ipa_params.notify = + teth_bridge_params.usb_notify_cb; + d->ipa_params.priv = + teth_bridge_params.private_data; + d->ipa_params.ipa_ep_cfg.mode.mode = IPA_BASIC; + d->ipa_params.skip_ep_cfg = + teth_bridge_params.skip_ep_cfg; + } + d->ipa_params.dir = USB_TO_PEER_PERIPHERAL; + if (d->func_type == USB_FUNC_ECM) { + d->ipa_params.notify = ecm_qc_get_ipa_rx_cb(); + d->ipa_params.priv = ecm_qc_get_ipa_priv(); + d->ipa_params.skip_ep_cfg = ecm_qc_get_skip_ep_config(); + } + + if (d->func_type == USB_FUNC_RNDIS) { + d->ipa_params.notify = rndis_qc_get_ipa_rx_cb(); + d->ipa_params.priv = rndis_qc_get_ipa_priv(); + d->ipa_params.skip_ep_cfg = + rndis_qc_get_skip_ep_config(); + } + + /* Support for UL using system-to-IPA */ + if (d->src_pipe_type == USB_BAM_PIPE_SYS2BAM) { + d->ul_params.teth_cb = d->ipa_params.notify; + d->ipa_params.notify = + bam_data_ipa_sys2bam_notify_cb; + d->ul_params.teth_priv = d->ipa_params.priv; + d->ipa_params.priv = &d->ul_params; + d->ipa_params.reset_pipe_after_lpm = false; + } else { + d->ipa_params.reset_pipe_after_lpm = + (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)); + } + + spin_unlock_irqrestore(&port->port_lock, flags); + ret = usb_bam_connect_ipa(d->usb_bam_type, &d->ipa_params); + if (ret) { + pr_err("%s: usb_bam_connect_ipa failed: err:%d\n", + __func__, ret); + goto free_fifos; + } + gadget->bam2bam_func_enabled = true; + + spin_lock_irqsave(&port->port_lock, flags); + if (port->last_event == U_BAM_DATA_DISCONNECT_E) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s:%d: Port is being disconnected.\n", + __func__, __LINE__); + goto disconnect_ipa; + } + + d_port->ipa_consumer_ep = d->ipa_params.ipa_cons_ep_idx; + + /* Remove support for UL using system-to-IPA towards DL */ + if (d->src_pipe_type == USB_BAM_PIPE_SYS2BAM) { + d->ipa_params.notify = d->ul_params.teth_cb; + d->ipa_params.priv = d->ul_params.teth_priv; + } + + d->ipa_params.dir = PEER_PERIPHERAL_TO_USB; + if (d->func_type == USB_FUNC_ECM) { + d->ipa_params.notify = ecm_qc_get_ipa_tx_cb(); + d->ipa_params.priv = ecm_qc_get_ipa_priv(); + d->ipa_params.skip_ep_cfg = ecm_qc_get_skip_ep_config(); + } + if (d->func_type == USB_FUNC_RNDIS) { + d->ipa_params.notify = rndis_qc_get_ipa_tx_cb(); + d->ipa_params.priv = rndis_qc_get_ipa_priv(); + d->ipa_params.skip_ep_cfg = + rndis_qc_get_skip_ep_config(); + } + + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) { + d->ipa_params.reset_pipe_after_lpm = + (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)); + } else { + d->ipa_params.reset_pipe_after_lpm = false; + } + spin_unlock_irqrestore(&port->port_lock, flags); + ret = usb_bam_connect_ipa(d->usb_bam_type, &d->ipa_params); + if (ret) { + pr_err("%s: usb_bam_connect_ipa failed: err:%d\n", + __func__, ret); + goto disconnect_ipa; + } + + /* + * Cable might have been disconnected after releasing the + * spinlock and re-enabling IRQs. Hence check again. + */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->last_event == U_BAM_DATA_DISCONNECT_E) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s:%d: port is beind disconnected.\n", + __func__, __LINE__); + goto disconnect_ipa; + } + + port->is_ipa_connected = true; + + d_port->ipa_producer_ep = d->ipa_params.ipa_prod_ep_idx; + pr_debug("%s(): ipa_producer_ep:%d ipa_consumer_ep:%d\n", + __func__, d_port->ipa_producer_ep, + d_port->ipa_consumer_ep); + spin_unlock_irqrestore(&port->port_lock, flags); + + if (d->func_type == USB_FUNC_MBIM) { + connect_params.ipa_usb_pipe_hdl = + d->ipa_params.prod_clnt_hdl; + connect_params.usb_ipa_pipe_hdl = + d->ipa_params.cons_clnt_hdl; + connect_params.tethering_mode = + TETH_TETHERING_MODE_MBIM; + connect_params.client_type = d->ipa_params.src_client; + ret = teth_bridge_connect(&connect_params); + if (ret) { + pr_err("%s:teth_bridge_connect() failed\n", + __func__); + return; + } + } + + if (d->func_type == USB_FUNC_ECM) { + ret = ecm_ipa_connect(d->ipa_params.cons_clnt_hdl, + d->ipa_params.prod_clnt_hdl, + d->ipa_params.priv); + if (ret) { + pr_err("%s: failed to connect IPA: err:%d\n", + __func__, ret); + return; + } + } + + if (d->func_type == USB_FUNC_RNDIS) { + rndis_data.prod_clnt_hdl = + d->ipa_params.prod_clnt_hdl; + rndis_data.cons_clnt_hdl = + d->ipa_params.cons_clnt_hdl; + rndis_data.priv = d->ipa_params.priv; + + pr_debug("ul_max_transfer_size:%d\n", + rndis_data.ul_max_transfer_size); + pr_debug("ul_max_packets_number:%d\n", + rndis_data.ul_max_packets_number); + pr_debug("dl_max_transfer_size:%d\n", + rndis_data.dl_max_transfer_size); + + ret = rndis_ipa_pipe_connect_notify( + rndis_data.cons_clnt_hdl, + rndis_data.prod_clnt_hdl, + rndis_data.ul_max_transfer_size, + rndis_data.ul_max_packets_number, + rndis_data.dl_max_transfer_size, + rndis_data.priv); + if (ret) { + pr_err("%s: failed to connect IPA: err:%d\n", + __func__, ret); + return; + } + } + atomic_set(&d->pipe_connect_notified, 1); + + /* Don't queue the transfers yet, only after network stack is up */ + if (d->func_type == USB_FUNC_RNDIS || d->func_type == USB_FUNC_ECM) { + pr_debug("%s: Not starting now, waiting for network notify", + __func__); + return; + } + + /* queue in & out requests */ + bam_data_start_rx_transfers(d, port); + bam_data_start_endless_tx(port); + + pr_debug("Connect workqueue done (port %p)", port); + return; + +disconnect_ipa: + /* let disconnect work take care of ipa disconnect */ + port->is_ipa_connected = true; + return; + +free_fifos: + usb_bam_free_fifos(d->usb_bam_type, d->src_connection_idx); + usb_bam_free_fifos(d->usb_bam_type, d->dst_connection_idx); +} + +/* + * Called when IPA triggers us that the network interface is up. + * Starts the transfers on bulk endpoints. + * (optimization reasons, the pipes and bam with IPA are already connected) + */ +void bam_data_start_rx_tx(u8 port_num) +{ + struct bam_data_port *port; + struct bam_data_ch_info *d; + unsigned long flags; + + pr_debug("%s: Triggered: starting tx, rx", __func__); + + /* queue in & out requests */ + port = bam2bam_data_ports[port_num]; + if (!port) { + pr_err("%s: port is NULL, can't start tx, rx", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + d = &port->data_ch; + + if (!port->port_usb || !port->port_usb->in->driver_data + || !port->port_usb->out->driver_data) { + pr_err("%s: Can't start tx, rx, ep not enabled", __func__); + goto out; + } + + if (!d->rx_req || !d->tx_req) { + pr_err("%s: No request d->rx_req=%p, d->tx_req=%p", __func__, + d->rx_req, d->tx_req); + goto out; + } + if (!port->is_ipa_connected) { + pr_debug("%s: pipes are disconnected", __func__); + goto out; + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + /* queue in & out requests */ + pr_debug("%s: Starting rx", __func__); + bam_data_start_rx_transfers(d, port); + + pr_debug("%s: Starting tx", __func__); + bam_data_start_endless_tx(port); + + return; +out: + spin_unlock_irqrestore(&port->port_lock, flags); +} + +inline int u_bam_data_func_to_port(enum function_type func, u8 func_port) +{ + if (func >= USB_NUM_FUNCS || func_port >= PORTS_PER_FUNC) { + pr_err("func=%d and func_port=%d are an illegal combination\n", + func, func_port); + return -EINVAL; + } + return (PORTS_PER_FUNC * func) + func_port; +} + +static int bam2bam_data_port_alloc(int portno) +{ + struct bam_data_port *port; + struct bam_data_ch_info *d; + + if (bam2bam_data_ports[portno] != NULL) { + pr_debug("port %d already allocated.\n", portno); + return 0; + } + + port = kzalloc(sizeof(struct bam_data_port), GFP_KERNEL); + if (!port) { + pr_err("no memory to allocate port %d\n", portno); + return -ENOMEM; + } + + bam2bam_data_ports[portno] = port; + d = &port->data_ch; + d->port = port; + + spin_lock_init(&port->port_lock); + + INIT_WORK(&port->connect_w, bam2bam_data_connect_work); + INIT_WORK(&port->disconnect_w, bam2bam_data_disconnect_work); + INIT_WORK(&port->suspend_w, bam2bam_data_suspend_work); + INIT_WORK(&port->resume_w, bam2bam_data_resume_work); + INIT_WORK(&d->write_tobam_w, bam_data_write_toipa); + return 0; +} + +void u_bam_data_start_rndis_ipa(void) +{ + int port_num; + struct bam_data_port *port; + struct bam_data_ch_info *d; + + pr_debug("%s\n", __func__); + + port_num = u_bam_data_func_to_port(USB_FUNC_RNDIS, + RNDIS_QC_ACTIVE_PORT); + port = bam2bam_data_ports[port_num]; + if (!port) { + pr_err("%s: port is NULL", __func__); + return; + } + + d = &port->data_ch; + + if (!atomic_read(&d->pipe_connect_notified)) { + /* + * Increment usage count upon cable connect. Decrement after IPA + * handshake is done in disconnect work due to cable disconnect + * or in suspend work. + */ + usb_gadget_autopm_get_noresume(port->gadget); + queue_work(bam_data_wq, &port->connect_w); + } else { + pr_debug("%s: Transfers already started?\n", __func__); + } +} + +void u_bam_data_stop_rndis_ipa(void) +{ + int port_num; + struct bam_data_port *port; + struct bam_data_ch_info *d; + + pr_debug("%s\n", __func__); + + port_num = u_bam_data_func_to_port(USB_FUNC_RNDIS, + RNDIS_QC_ACTIVE_PORT); + port = bam2bam_data_ports[port_num]; + if (!port) { + pr_err("%s: port is NULL", __func__); + return; + } + + d = &port->data_ch; + + if (atomic_read(&d->pipe_connect_notified)) { + rndis_ipa_reset_trigger(); + bam_data_stop_endless_tx(port); + bam_data_stop_endless_rx(port); + queue_work(bam_data_wq, &port->disconnect_w); + } +} + +void bam_data_flow_control_enable(bool enable) +{ + if (enable) + u_bam_data_stop_rndis_ipa(); + else + u_bam_data_start_rndis_ipa(); +} + +static void bam_data_free_reqs(struct bam_data_port *port) +{ + + struct list_head *head; + struct usb_request *req; + + if (port->data_ch.src_pipe_type != USB_BAM_PIPE_SYS2BAM) + return; + + head = &port->data_ch.rx_idle; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(port->port_usb->out, req); + port->data_ch.freed_rx_reqs++; + } +} + +void bam_data_disconnect(struct data_port *gr, enum function_type func, + u8 dev_port_num) +{ + struct bam_data_port *port; + struct bam_data_ch_info *d; + struct sk_buff *skb = NULL; + unsigned long flags; + int port_num; + + port_num = u_bam_data_func_to_port(func, dev_port_num); + if (port_num < 0) { + pr_err("invalid bam2bam portno#%d\n", port_num); + return; + } + + pr_debug("dev:%p port number:%d\n", gr, port_num); + + if (!gr) { + pr_err("data port is null\n"); + return; + } + + port = bam2bam_data_ports[port_num]; + + if (!port) { + pr_err("port %u is NULL", port_num); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + + /* Already disconnected due to suspend with remote wake disabled */ + if (port->last_event == U_BAM_DATA_DISCONNECT_E) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + /* + * Suspend with remote wakeup enabled. Increment usage + * count when disconnect happens in suspended state. + * Corresponding decrement happens in the end of this + * function if IPA handshake is already done or it is done + * in disconnect work after finishing IPA handshake. + * In case of RNDIS, if connect_w by rndis_flow_control is not triggered + * yet then don't perform pm_runtime_get as suspend_w would have bailed + * w/o runtime_get. + * And restrict check to only RNDIS to handle cases where connect_w is + * already scheduled but execution is pending which must be rare though. + */ + if (port->last_event == U_BAM_DATA_SUSPEND_E && + (d->func_type != USB_FUNC_RNDIS || port->is_ipa_connected)) + usb_gadget_autopm_get_noresume(port->gadget); + + if (port->port_usb) { + port->port_usb->ipa_consumer_ep = -1; + port->port_usb->ipa_producer_ep = -1; + + if (port->port_usb->in && port->port_usb->in->driver_data) { + + /* + * Disable endpoints. + * Unlocking is needed since disabling the eps might + * stop active transfers and therefore the request + * complete function will be called, where we try + * to obtain the spinlock as well. + */ + spin_unlock_irqrestore(&port->port_lock, flags); + usb_ep_disable(port->port_usb->out); + if (d->rx_req) { + usb_ep_free_request(port->port_usb->out, + d->rx_req); + d->rx_req = NULL; + } + + usb_ep_disable(port->port_usb->in); + if (d->tx_req) { + usb_ep_free_request(port->port_usb->in, + d->tx_req); + d->tx_req = NULL; + } + spin_lock_irqsave(&port->port_lock, flags); + + /* Only for SYS2BAM mode related UL workaround */ + if (d->src_pipe_type == USB_BAM_PIPE_SYS2BAM) { + + pr_debug("SKBs_RX_Q: freed:%d\n", + d->rx_skb_q.qlen); + while ((skb = __skb_dequeue(&d->rx_skb_q))) + dev_kfree_skb_any(skb); + + bam2bam_free_rx_skb_idle_list(port); + pr_debug("SKBs: allocated:%d freed:%d\n", + d->total_skb, d->freed_skb); + pr_debug("rx_reqs: allocated:%d freed:%d\n", + d->alloc_rx_reqs, d->freed_rx_reqs); + + /* reset all skb/reqs related statistics */ + d->total_skb = 0; + d->freed_skb = 0; + d->freed_rx_reqs = 0; + d->alloc_rx_reqs = 0; + } + + /* + * Set endless flag to false as USB Endpoint + * is already disable. + */ + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->in->endless = false; + + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->out->endless = false; + + port->port_usb->in->driver_data = NULL; + port->port_usb->out->driver_data = NULL; + + port->port_usb = NULL; + } + } + + port->last_event = U_BAM_DATA_DISCONNECT_E; + /* Disable usb irq for CI gadget. It will be enabled in + * usb_bam_disconnect_pipe() after disconnecting all pipes + * and USB BAM reset is done. + */ + if (!gadget_is_dwc3(port->gadget)) + msm_usb_irq_disable(true); + + queue_work(bam_data_wq, &port->disconnect_w); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +int bam_data_connect(struct data_port *gr, enum transport_type trans, + u8 dev_port_num, enum function_type func) +{ + struct bam_data_port *port; + struct bam_data_ch_info *d; + int ret, port_num; + unsigned long flags; + u8 src_connection_idx, dst_connection_idx; + enum usb_ctrl usb_bam_type; + + if (!gr) { + pr_err("data port is null\n"); + return -ENODEV; + } + + port_num = u_bam_data_func_to_port(func, dev_port_num); + if (port_num < 0) { + pr_err("invalid portno#%d\n", port_num); + return -EINVAL; + } + + if (trans != USB_GADGET_XPORT_BAM2BAM_IPA) { + pr_err("invalid xport#%d\n", trans); + return -EINVAL; + } + + pr_debug("dev:%p port#%d\n", gr, port_num); + + usb_bam_type = usb_bam_get_bam_type(gr->cdev->gadget->name); + + src_connection_idx = usb_bam_get_connection_idx(usb_bam_type, + IPA_P_BAM, USB_TO_PEER_PERIPHERAL, USB_BAM_DEVICE, + dev_port_num); + dst_connection_idx = usb_bam_get_connection_idx(usb_bam_type, + IPA_P_BAM, PEER_PERIPHERAL_TO_USB, USB_BAM_DEVICE, + dev_port_num); + if (src_connection_idx < 0 || dst_connection_idx < 0) { + pr_err("%s: usb_bam_get_connection_idx failed\n", __func__); + return ret; + } + + port = bam2bam_data_ports[port_num]; + + spin_lock_irqsave(&port->port_lock, flags); + + port->port_usb = gr; + port->gadget = gr->cdev->gadget; + d = &port->data_ch; + d->src_connection_idx = src_connection_idx; + d->dst_connection_idx = dst_connection_idx; + d->usb_bam_type = usb_bam_type; + + d->trans = trans; + d->func_type = func; + d->rx_buffer_size = (gr->rx_buffer_size ? gr->rx_buffer_size : + bam_mux_rx_req_size); + + if (usb_bam_type == HSIC_CTRL) { + d->ipa_params.src_client = IPA_CLIENT_HSIC1_PROD; + d->ipa_params.dst_client = IPA_CLIENT_HSIC1_CONS; + } else { + d->ipa_params.src_client = IPA_CLIENT_USB_PROD; + d->ipa_params.dst_client = IPA_CLIENT_USB_CONS; + } + + pr_debug("%s(): rx_buffer_size:%d\n", __func__, d->rx_buffer_size); + d->ipa_params.src_pipe = &(d->src_pipe_idx); + d->ipa_params.dst_pipe = &(d->dst_pipe_idx); + d->ipa_params.src_idx = src_connection_idx; + d->ipa_params.dst_idx = dst_connection_idx; + d->rx_flow_control_disable = 0; + d->rx_flow_control_enable = 0; + d->rx_flow_control_triggered = 0; + + /* + * Query pipe type using IPA src/dst index with + * usbbam driver. It is being set either as + * BAM2BAM or SYS2BAM. + */ + if (usb_bam_get_pipe_type(usb_bam_type, d->ipa_params.src_idx, + &d->src_pipe_type) || + usb_bam_get_pipe_type(usb_bam_type, d->ipa_params.dst_idx, + &d->dst_pipe_type)) { + pr_err("usb_bam_get_pipe_type() failed\n"); + ret = -EINVAL; + goto exit; + } + + /* + * Check for pipe_type. If it is BAM2BAM, then it is required + * to disable Xfer complete and Xfer not ready interrupts for + * that particular endpoint. Hence it set endless flag based + * it which is considered into UDC driver while enabling + * USB Endpoint. + */ + if (d->dst_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->in->endless = true; + + if (d->src_pipe_type == USB_BAM_PIPE_BAM2BAM) + port->port_usb->out->endless = true; + + ret = usb_ep_enable(gr->in); + if (ret) { + pr_err("usb_ep_enable failed eptype:IN ep:%p", gr->in); + goto exit; + } + + gr->in->driver_data = port; + + ret = usb_ep_enable(gr->out); + if (ret) { + pr_err("usb_ep_enable failed eptype:OUT ep:%p", gr->out); + goto disable_in_ep; + } + + gr->out->driver_data = port; + + if (d->src_pipe_type == USB_BAM_PIPE_SYS2BAM) { + + /* UL workaround requirements */ + skb_queue_head_init(&d->rx_skb_q); + skb_queue_head_init(&d->rx_skb_idle); + INIT_LIST_HEAD(&d->rx_idle); + + ret = bam_data_sys2bam_alloc_req(port, false); + if (ret) { + pr_err("%s: sys2bam_alloc_req failed(%d)", + __func__, ret); + goto disable_out_ep; + } + } + + d->rx_req = usb_ep_alloc_request(port->port_usb->out, + GFP_ATOMIC); + if (!d->rx_req) { + pr_err("%s: failed to allocate rx_req\n", __func__); + goto bam_data_free; + } + d->rx_req->context = port; + d->rx_req->complete = bam_data_endless_rx_complete; + d->rx_req->length = 0; + d->rx_req->no_interrupt = 1; + + d->tx_req = usb_ep_alloc_request(port->port_usb->in, + GFP_ATOMIC); + if (!d->tx_req) { + pr_err("%s: failed to allocate tx_req\n", __func__); + goto ep_out_req_free; + } + + d->tx_req->context = port; + d->tx_req->complete = bam_data_endless_tx_complete; + d->tx_req->length = 0; + d->tx_req->no_interrupt = 1; + + gr->out->driver_data = port; + + port->last_event = U_BAM_DATA_CONNECT_E; + + /* Wait for host to enable flow_control */ + if (d->func_type == USB_FUNC_RNDIS) { + ret = 0; + goto exit; + } + + /* + * Increment usage count upon cable connect. Decrement after IPA + * handshake is done in disconnect work (due to cable disconnect) + * or in suspend work. + */ + usb_gadget_autopm_get_noresume(port->gadget); + + queue_work(bam_data_wq, &port->connect_w); + spin_unlock_irqrestore(&port->port_lock, flags); + return 0; + +ep_out_req_free: + usb_ep_free_request(port->port_usb->out, d->rx_req); +bam_data_free: + bam_data_free_reqs(port); +disable_out_ep: + gr->out->driver_data = 0; + usb_ep_disable(gr->out); +disable_in_ep: + gr->in->driver_data = 0; + usb_ep_disable(gr->in); +exit: + spin_unlock_irqrestore(&port->port_lock, flags); + return ret; +} + +int bam_data_setup(enum function_type func, unsigned int no_bam2bam_port) +{ + int i; + int ret; + + pr_debug("requested %d BAM2BAM ports", no_bam2bam_port); + + if (!no_bam2bam_port || no_bam2bam_port > PORTS_PER_FUNC || + func >= USB_NUM_FUNCS) { + pr_err("Invalid num of ports count:%d or function type:%d\n", + no_bam2bam_port, func); + return -EINVAL; + } + + for (i = 0; i < no_bam2bam_port; i++) { + n_bam2bam_data_ports++; + ret = bam2bam_data_port_alloc(u_bam_data_func_to_port(func, i)); + if (ret) { + n_bam2bam_data_ports--; + pr_err("Failed to alloc port:%d\n", i); + goto free_bam_ports; + } + } + + pr_debug("n_bam2bam_data_ports:%d\n", n_bam2bam_data_ports); + + if (bam_data_wq) { + pr_debug("bam_data is already setup."); + return 0; + } + + bam_data_wq = alloc_workqueue("k_bam_data", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!bam_data_wq) { + pr_err("Failed to create workqueue\n"); + ret = -ENOMEM; + goto free_bam_ports; + } + + return 0; + +free_bam_ports: + for (i = 0; i < n_bam2bam_data_ports; i++) { + kfree(bam2bam_data_ports[i]); + bam2bam_data_ports[i] = NULL; + if (bam_data_wq) { + destroy_workqueue(bam_data_wq); + bam_data_wq = NULL; + } + } + + return ret; +} + +static int bam_data_wake_cb(void *param) +{ + int ret; + struct bam_data_port *port = (struct bam_data_port *)param; + struct data_port *d_port = port->port_usb; + struct usb_gadget *gadget; + struct usb_function *func; + + pr_debug("%s: woken up by peer\n", __func__); + + if (!d_port) { + pr_err("FAILED: d_port == NULL"); + return -ENODEV; + } + + if (!d_port->cdev) { + pr_err("FAILED: d_port->cdev == NULL"); + return -ENODEV; + } + + gadget = d_port->cdev->gadget; + if (!gadget) { + pr_err("FAILED: d_port->cdev->gadget == NULL"); + return -ENODEV; + } + + func = d_port->func; + + /* + * In Super-Speed mode, remote wakeup is not allowed for suspended + * functions which have been disallowed by the host to issue Funtion + * 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)) + ret = usb_func_wakeup(func); + else + ret = usb_gadget_wakeup(gadget); + + if ((ret == -EBUSY) || (ret == -EAGAIN)) + pr_debug("Remote wakeup is delayed due to LPM exit.\n"); + else if (ret) + pr_err("Failed to wake up the USB core. ret=%d.\n", ret); + + return ret; +} + +static void bam_data_start(void *param, enum usb_bam_pipe_dir dir) +{ + struct bam_data_port *port = param; + struct data_port *d_port = port->port_usb; + struct bam_data_ch_info *d = &port->data_ch; + struct usb_gadget *gadget; + + if (!d_port || !d_port->cdev || !d_port->cdev->gadget) { + pr_err("%s:d_port,cdev or gadget is NULL\n", __func__); + return; + } + if (port->last_event != U_BAM_DATA_RESUME_E) { + pr_err("%s: Port state changed since resume. Bail out.\n", + __func__); + return; + } + + gadget = d_port->cdev->gadget; + + if (dir == USB_TO_PEER_PERIPHERAL) { + if (port->data_ch.src_pipe_type == USB_BAM_PIPE_BAM2BAM) + bam_data_start_endless_rx(port); + else { + bam_data_start_rx(port); + queue_work(bam_data_wq, &d->write_tobam_w); + } + } else { + if (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)) { + configure_data_fifo(d->usb_bam_type, + d->dst_connection_idx, + port->port_usb->in, d->dst_pipe_type); + } + bam_data_start_endless_tx(port); + } + +} + +static void bam_data_stop(void *param, enum usb_bam_pipe_dir dir) +{ + struct bam_data_port *port = param; + + if (dir == USB_TO_PEER_PERIPHERAL) { + /* + * Only handling BAM2BAM, as there is no equivelant to + * bam_data_stop_endless_rx() for the SYS2BAM use case + */ + if (port->data_ch.src_pipe_type == USB_BAM_PIPE_BAM2BAM) + bam_data_stop_endless_rx(port); + } else { + bam_data_stop_endless_tx(port); + } +} + +void bam_data_suspend(struct data_port *port_usb, u8 dev_port_num, + enum function_type func, bool remote_wakeup_enabled) +{ + struct bam_data_port *port; + unsigned long flags; + int port_num; + + port_num = u_bam_data_func_to_port(func, dev_port_num); + if (port_num < 0) { + pr_err("invalid bam2bam portno#%d\n", port_num); + return; + } + + pr_debug("%s: suspended port %d\n", __func__, port_num); + + port = bam2bam_data_ports[port_num]; + if (!port) { + pr_err("%s(): Port is NULL.\n", __func__); + return; + } + + /* suspend with remote wakeup disabled */ + if (!remote_wakeup_enabled) { + /* + * When remote wakeup is disabled, IPA BAM 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. + */ + port_usb->in_ep_desc_backup = port_usb->in->desc; + port_usb->out_ep_desc_backup = port_usb->out->desc; + + pr_debug("in_ep_desc_backup = %p, out_ep_desc_backup = %p", + port_usb->in_ep_desc_backup, + port_usb->out_ep_desc_backup); + + bam_data_disconnect(port_usb, func, dev_port_num); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + port->last_event = U_BAM_DATA_SUSPEND_E; + queue_work(bam_data_wq, &port->suspend_w); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +void bam_data_resume(struct data_port *port_usb, u8 dev_port_num, + enum function_type func, bool remote_wakeup_enabled) +{ + struct bam_data_port *port; + unsigned long flags; + int port_num; + + port_num = u_bam_data_func_to_port(func, dev_port_num); + if (port_num < 0) { + pr_err("invalid bam2bam portno#%d\n", port_num); + return; + } + + pr_debug("%s: resumed port %d\n", __func__, port_num); + + port = bam2bam_data_ports[port_num]; + if (!port) { + pr_err("%s(): Port is NULL.\n", __func__); + return; + } + + /* resume with remote wakeup disabled */ + if (!remote_wakeup_enabled) { + /* Restore endpoint descriptors info. */ + port_usb->in->desc = port_usb->in_ep_desc_backup; + port_usb->out->desc = port_usb->out_ep_desc_backup; + + pr_debug("in_ep_desc_backup = %p, out_ep_desc_backup = %p", + port_usb->in_ep_desc_backup, + port_usb->out_ep_desc_backup); + + bam_data_connect(port_usb, port->data_ch.trans, + dev_port_num, func); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + port->last_event = U_BAM_DATA_RESUME_E; + + /* + * 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 bam_data_disconnect + * in suspended state. + */ + usb_gadget_autopm_get_noresume(port->gadget); + queue_work(bam_data_wq, &port->resume_w); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +void bam_data_flush_workqueue(void) +{ + pr_debug("%s(): Flushing workqueue\n", __func__); + flush_workqueue(bam_data_wq); +} + +static void bam2bam_data_suspend_work(struct work_struct *w) +{ + struct bam_data_port *port = + container_of(w, struct bam_data_port, suspend_w); + struct bam_data_ch_info *d; + int ret; + unsigned long flags; + + pr_debug("%s: suspend work started\n", __func__); + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + + /* In case of RNDIS, host enables flow_control invoking connect_w. If it + * is delayed then we may end up having suspend_w run before connect_w. + * In this scenario, connect_w may or may not at all start if cable gets + * disconnected or if host changes configuration e.g. RNDIS --> MBIM + * For these cases don't do runtime_put as there was no _get yet, and + * detect this condition on disconnect to not do extra pm_runtme_get + * for SUSPEND --> DISCONNECT scenario. + */ + if (!port->is_ipa_connected) { + pr_err("%s: Not yet connected. SUSPEND pending.\n", __func__); + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + if ((port->last_event == U_BAM_DATA_DISCONNECT_E) || + (port->last_event == U_BAM_DATA_RESUME_E)) { + pr_debug("%s: Port is about to disconnect/resume. Bail out.\n", + __func__); + goto exit; + } + + ret = usb_bam_register_wake_cb(d->usb_bam_type, d->dst_connection_idx, + bam_data_wake_cb, port); + if (ret) { + pr_err("%s(): Failed to register BAM wake callback.\n", + __func__); + goto exit; + } + + usb_bam_register_start_stop_cbs(d->usb_bam_type, d->dst_connection_idx, + bam_data_start, bam_data_stop, + port); + + /* + * release lock here because bam_data_start() or + * bam_data_stop() called from usb_bam_suspend() + * re-acquires port lock. + */ + spin_unlock_irqrestore(&port->port_lock, flags); + usb_bam_suspend(d->usb_bam_type, &d->ipa_params); + spin_lock_irqsave(&port->port_lock, flags); + +exit: + /* + * Decrement usage count after IPA handshake is done + * to allow gadget parent to go to lpm. This counter was + * incremented upon cable connect. + */ + usb_gadget_autopm_put_async(port->gadget); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void bam2bam_data_resume_work(struct work_struct *w) +{ + struct bam_data_port *port = + container_of(w, struct bam_data_port, resume_w); + struct bam_data_ch_info *d; + struct data_port *d_port; + struct usb_gadget *gadget; + int ret; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + + if (!port->port_usb) { + pr_err("port->port_usb is NULL"); + goto exit; + } + + if (!port->port_usb->cdev) { + pr_err("!port->port_usb->cdev is NULL"); + goto exit; + } + + if (!port->port_usb->cdev->gadget) { + pr_err("!port->port_usb->cdev->gadget is NULL"); + goto exit; + } + + d = &port->data_ch; + d_port = port->port_usb; + gadget = d_port->cdev->gadget; + + pr_debug("%s: resume work started\n", __func__); + + if (port->last_event == U_BAM_DATA_DISCONNECT_E) { + pr_debug("%s: Port is about to disconnect. Bail out.\n", + __func__); + goto exit; + } + + ret = usb_bam_register_wake_cb(d->usb_bam_type, d->dst_connection_idx, + NULL, NULL); + if (ret) { + pr_err("%s(): Failed to un-register BAM wake callback.\n", + __func__); + goto exit; + } + + /* + * If usb_req was dequeued as part of bus suspend then + * corresponding DBM IN and OUT EPs should also be reset. + * There is a possbility that usb_bam may not have dequeued the + * request in case of quick back to back usb bus suspend resume. + */ + if (gadget_is_dwc3(gadget) && + msm_dwc3_reset_ep_after_lpm(gadget)) { + if (d->tx_req_dequeued) { + configure_usb_data_fifo(d->usb_bam_type, + d->dst_connection_idx, + port->port_usb->in, d->dst_pipe_type); + spin_unlock_irqrestore(&port->port_lock, flags); + msm_dwc3_reset_dbm_ep(port->port_usb->in); + spin_lock_irqsave(&port->port_lock, flags); + } + if (d->rx_req_dequeued) { + configure_usb_data_fifo(d->usb_bam_type, + d->src_connection_idx, + port->port_usb->out, d->src_pipe_type); + spin_unlock_irqrestore(&port->port_lock, flags); + msm_dwc3_reset_dbm_ep(port->port_usb->out); + spin_lock_irqsave(&port->port_lock, flags); + } + } + d->tx_req_dequeued = false; + d->rx_req_dequeued = false; + usb_bam_resume(d->usb_bam_type, &d->ipa_params); +exit: + spin_unlock_irqrestore(&port->port_lock, flags); +} + +void u_bam_data_set_dl_max_xfer_size(u32 max_transfer_size) +{ + + if (!max_transfer_size) { + pr_err("%s: invalid parameters\n", __func__); + return; + } + rndis_data.dl_max_transfer_size = max_transfer_size; + pr_debug("%s(): dl_max_xfer_size:%d\n", __func__, max_transfer_size); +} + +void u_bam_data_set_ul_max_pkt_num(u8 max_packets_number) + +{ + if (!max_packets_number) { + pr_err("%s: invalid parameters\n", __func__); + return; + } + + rndis_data.ul_max_packets_number = max_packets_number; + + if (max_packets_number > 1) + rndis_data.ul_aggregation_enable = true; + else + rndis_data.ul_aggregation_enable = false; + + pr_debug("%s(): ul_aggregation enable:%d\n", __func__, + rndis_data.ul_aggregation_enable); + pr_debug("%s(): ul_max_packets_number:%d\n", __func__, + max_packets_number); +} + +void u_bam_data_set_ul_max_xfer_size(u32 max_transfer_size) +{ + if (!max_transfer_size) { + pr_err("%s: invalid parameters\n", __func__); + return; + } + rndis_data.ul_max_transfer_size = max_transfer_size; + pr_debug("%s(): ul_max_xfer_size:%d\n", __func__, max_transfer_size); +} diff --git a/drivers/usb/gadget/function/u_bam_data.h b/drivers/usb/gadget/function/u_bam_data.h new file mode 100644 index 000000000000..e3acbd0c56a0 --- /dev/null +++ b/drivers/usb/gadget/function/u_bam_data.h @@ -0,0 +1,71 @@ +/* Copyright (c) 2013-2015, 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 __U_BAM_DATA_H +#define __U_BAM_DATA_H + +#include "usb_gadget_xport.h" + +enum function_type { + USB_FUNC_ECM, + USB_FUNC_MBIM, + USB_FUNC_RNDIS, + USB_NUM_FUNCS, +}; + +#define PORTS_PER_FUNC 1 +#define BAM2BAM_DATA_N_PORTS (USB_NUM_FUNCS * PORTS_PER_FUNC) + +struct data_port { + struct usb_composite_dev *cdev; + struct usb_function *func; + struct usb_ep *in; + int rx_buffer_size; + struct usb_ep *out; + int ipa_consumer_ep; + int ipa_producer_ep; + const struct usb_endpoint_descriptor *in_ep_desc_backup; + const struct usb_endpoint_descriptor *out_ep_desc_backup; +}; + +void bam_data_disconnect(struct data_port *gr, enum function_type func, + u8 dev_port_num); + +int bam_data_connect(struct data_port *gr, enum transport_type trans, + u8 dev_port_num, enum function_type func); + +int bam_data_setup(enum function_type func, unsigned int no_bam2bam_port); + +void bam_data_flush_workqueue(void); + +void bam_data_suspend(struct data_port *port_usb, u8 dev_port_num, + enum function_type func, bool remote_wakeup_enabled); + +void bam_data_resume(struct data_port *port_usb, u8 dev_port_num, + enum function_type func, bool remote_wakeup_enabled); + +void bam_data_flow_control_enable(bool enable); + +void u_bam_data_set_dl_max_xfer_size(u32 dl_max_transfer_size); + +void u_bam_data_set_ul_max_pkt_num(u8 ul_max_packets_number); + +void u_bam_data_set_ul_max_xfer_size(u32 ul_max_xfer_size); + +void u_bam_data_start_rndis_ipa(void); + +void u_bam_data_stop_rndis_ipa(void); + +void bam_data_start_rx_tx(u8 port_num); + +int u_bam_data_func_to_port(enum function_type func, u8 func_port); +#endif /* __U_BAM_DATA_H */ diff --git a/drivers/usb/gadget/function/u_ctrl_qti.c b/drivers/usb/gadget/function/u_ctrl_qti.c new file mode 100644 index 000000000000..a9a8ab4618e8 --- /dev/null +++ b/drivers/usb/gadget/function/u_ctrl_qti.c @@ -0,0 +1,837 @@ +/* + * Copyright (c) 2013-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. + */ + +#include +#include +#include + +#include + +#include "u_rmnet.h" +#include "usb_gadget_xport.h" + +#define RMNET_CTRL_QTI_NAME "rmnet_ctrl" +#define DPL_CTRL_QTI_NAME "dpl_ctrl" +/* + * Use size of gadget's qti control name. Here currently RMNET and DPL + * gadget is using QTI as control transport. Hence using RMNET ctrl name + * (as it is bigger in size) for QTI_CTRL_NAME_LEN. + */ +#define QTI_CTRL_NAME_LEN (sizeof(RMNET_CTRL_QTI_NAME)+2) + +struct qti_ctrl_port { + void *port_usb; + char name[QTI_CTRL_NAME_LEN]; + struct miscdevice ctrl_device; + + bool is_open; + int index; + unsigned intf; + int ipa_prod_idx; + int ipa_cons_idx; + enum peripheral_ep_type ep_type; + + atomic_t connected; + atomic_t line_state; + + atomic_t open_excl; + atomic_t read_excl; + atomic_t write_excl; + atomic_t ioctl_excl; + + wait_queue_head_t read_wq; + + struct list_head cpkt_req_q; + + spinlock_t lock; + enum gadget_type gtype; + unsigned host_to_modem; + unsigned copied_to_modem; + unsigned copied_from_modem; + unsigned modem_to_host; + unsigned drp_cpkt_cnt; +}; +static struct qti_ctrl_port *ctrl_port[NR_QTI_PORTS]; + +static inline int qti_ctrl_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -EBUSY; + } +} + +static inline void qti_ctrl_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +static void qti_ctrl_queue_notify(struct qti_ctrl_port *port) +{ + unsigned long flags; + struct rmnet_ctrl_pkt *cpkt = NULL; + + pr_debug("%s: Queue empty packet for QTI for port%d", + __func__, port->index); + + spin_lock_irqsave(&port->lock, flags); + if (!port->is_open) { + pr_err("%s: rmnet ctrl file handler %p is not open", + __func__, port); + spin_unlock_irqrestore(&port->lock, flags); + return; + } + + cpkt = alloc_rmnet_ctrl_pkt(0, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + pr_err("%s: Unable to allocate reset function pkt\n", __func__); + spin_unlock_irqrestore(&port->lock, flags); + return; + } + + list_add_tail(&cpkt->list, &port->cpkt_req_q); + spin_unlock_irqrestore(&port->lock, flags); + + pr_debug("%s: Wake up read queue", __func__); + wake_up(&port->read_wq); +} + +static int gqti_ctrl_send_cpkt_tomodem(u8 portno, void *buf, size_t len) +{ + unsigned long flags; + struct qti_ctrl_port *port; + struct rmnet_ctrl_pkt *cpkt; + + if (len > MAX_QTI_PKT_SIZE) { + pr_err("given pkt size too big:%zu > max_pkt_size:%d\n", + len, MAX_QTI_PKT_SIZE); + return -EINVAL; + } + + if (portno >= NR_QTI_PORTS) { + pr_err("%s: Invalid QTI port %d\n", __func__, portno); + return -ENODEV; + } + port = ctrl_port[portno]; + + cpkt = alloc_rmnet_ctrl_pkt(len, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + pr_err("%s: Unable to allocate ctrl pkt\n", __func__); + return -ENOMEM; + } + + memcpy(cpkt->buf, buf, len); + cpkt->len = len; + + pr_debug("%s: gtype:%d: Add to cpkt_req_q packet with len = %zu\n", + __func__, port->gtype, len); + spin_lock_irqsave(&port->lock, flags); + + /* drop cpkt if port is not open */ + if (!port->is_open) { + pr_debug("rmnet file handler %p(index=%d) is not open", + port, port->index); + port->drp_cpkt_cnt++; + spin_unlock_irqrestore(&port->lock, flags); + free_rmnet_ctrl_pkt(cpkt); + return 0; + } + + list_add_tail(&cpkt->list, &port->cpkt_req_q); + port->host_to_modem++; + spin_unlock_irqrestore(&port->lock, flags); + + /* wakeup read thread */ + pr_debug("%s: Wake up read queue", __func__); + wake_up(&port->read_wq); + + return 0; +} + +static void +gqti_ctrl_notify_modem(void *gptr, u8 portno, int val) +{ + struct qti_ctrl_port *port; + + if (portno >= NR_QTI_PORTS) { + pr_err("%s: Invalid QTI port %d\n", __func__, portno); + return; + } + port = ctrl_port[portno]; + + atomic_set(&port->line_state, val); + + /* send 0 len pkt to qti to notify state change */ + qti_ctrl_queue_notify(port); +} + +int gqti_ctrl_connect(void *gr, u8 port_num, unsigned intf, + enum transport_type dxport, enum gadget_type gtype) +{ + struct qti_ctrl_port *port; + struct grmnet *g_rmnet = NULL; + struct gqdss *g_dpl = NULL; + unsigned long flags; + + pr_debug("%s: gtype:%d gadget:%p\n", __func__, gtype, gr); + if (port_num >= NR_QTI_PORTS) { + pr_err("%s: Invalid QTI port %d\n", __func__, port_num); + return -ENODEV; + } + + port = ctrl_port[port_num]; + if (!port) { + pr_err("%s: gadget port is null\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&port->lock, flags); + port->gtype = gtype; + if (dxport == USB_GADGET_XPORT_BAM_DMUX) { + /* + * BAM-DMUX data transport is used for RMNET and DPL + * on some targets where IPA is not available. + * Set endpoint type as BAM-DMUX and interface + * id as channel number. This information is + * sent to user space via EP_LOOKUP ioctl. + * + */ + + port->ep_type = DATA_EP_TYPE_BAM_DMUX; + port->intf = (gtype == USB_GADGET_RMNET) ? + BAM_DMUX_USB_RMNET_0 : + BAM_DMUX_USB_DPL; + port->ipa_prod_idx = 0; + port->ipa_cons_idx = 0; + } else { + port->ep_type = DATA_EP_TYPE_HSUSB; + port->intf = intf; + } + + if (gr && port->gtype == USB_GADGET_RMNET) { + port->port_usb = gr; + g_rmnet = (struct grmnet *)gr; + g_rmnet->send_encap_cmd = gqti_ctrl_send_cpkt_tomodem; + g_rmnet->notify_modem = gqti_ctrl_notify_modem; + } else if (gr && port->gtype == USB_GADGET_DPL) { + port->port_usb = gr; + g_dpl = (struct gqdss *)gr; + g_dpl->send_encap_cmd = gqti_ctrl_send_cpkt_tomodem; + g_dpl->notify_modem = gqti_ctrl_notify_modem; + atomic_set(&port->line_state, 1); + } else { + spin_unlock_irqrestore(&port->lock, flags); + pr_err("%s(): Port is used without gtype.\n", __func__); + return -ENODEV; + } + + port->host_to_modem = 0; + port->copied_to_modem = 0; + port->copied_from_modem = 0; + port->modem_to_host = 0; + port->drp_cpkt_cnt = 0; + + spin_unlock_irqrestore(&port->lock, flags); + + atomic_set(&port->connected, 1); + wake_up(&port->read_wq); + if (port->port_usb && g_rmnet && g_rmnet->connect) + g_rmnet->connect(port->port_usb); + + return 0; +} + +void gqti_ctrl_disconnect(void *gr, u8 port_num) +{ + struct qti_ctrl_port *port; + unsigned long flags; + struct rmnet_ctrl_pkt *cpkt; + struct grmnet *g_rmnet = NULL; + struct gqdss *g_dpl = NULL; + + pr_debug("%s: gadget:%p\n", __func__, gr); + + if (port_num >= NR_QTI_PORTS) { + pr_err("%s: Invalid QTI port %d\n", __func__, port_num); + return; + } + + port = ctrl_port[port_num]; + + if (!port) { + pr_err("%s: gadget port is null\n", __func__); + return; + } + + atomic_set(&port->connected, 0); + atomic_set(&port->line_state, 0); + spin_lock_irqsave(&port->lock, flags); + + /* reset ipa eps to -1 */ + port->ipa_prod_idx = -1; + port->ipa_cons_idx = -1; + port->port_usb = NULL; + + if (gr && port->gtype == USB_GADGET_RMNET) { + g_rmnet = (struct grmnet *)gr; + g_rmnet->send_encap_cmd = NULL; + g_rmnet->notify_modem = NULL; + } else if (gr && port->gtype == USB_GADGET_DPL) { + g_dpl = (struct gqdss *)gr; + g_dpl->send_encap_cmd = NULL; + g_dpl->notify_modem = NULL; + } else { + pr_err("%s(): unrecognized gadget type(%d).\n", + __func__, port->gtype); + } + + while (!list_empty(&port->cpkt_req_q)) { + cpkt = list_first_entry(&port->cpkt_req_q, + struct rmnet_ctrl_pkt, list); + + list_del(&cpkt->list); + free_rmnet_ctrl_pkt(cpkt); + } + + spin_unlock_irqrestore(&port->lock, flags); + + /* send 0 len pkt to qti to notify state change */ + qti_ctrl_queue_notify(port); +} + +void gqti_ctrl_update_ipa_pipes(void *gr, u8 port_num, u32 ipa_prod, + u32 ipa_cons) +{ + struct qti_ctrl_port *port; + + if (port_num >= NR_QTI_PORTS) { + pr_err("%s: Invalid QTI port %d\n", __func__, port_num); + return; + } + + port = ctrl_port[port_num]; + + port->ipa_prod_idx = ipa_prod; + port->ipa_cons_idx = ipa_cons; + +} + + +static int qti_ctrl_open(struct inode *ip, struct file *fp) +{ + unsigned long flags; + struct qti_ctrl_port *port = container_of(fp->private_data, + struct qti_ctrl_port, + ctrl_device); + + pr_debug("Open rmnet_ctrl_qti device file name=%s(index=%d)\n", + port->name, port->index); + + if (qti_ctrl_lock(&port->open_excl)) { + pr_err("Already opened\n"); + return -EBUSY; + } + + spin_lock_irqsave(&port->lock, flags); + port->is_open = true; + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static int qti_ctrl_release(struct inode *ip, struct file *fp) +{ + unsigned long flags; + struct qti_ctrl_port *port = container_of(fp->private_data, + struct qti_ctrl_port, + ctrl_device); + + pr_debug("Close rmnet control file"); + + spin_lock_irqsave(&port->lock, flags); + port->is_open = false; + spin_unlock_irqrestore(&port->lock, flags); + + qti_ctrl_unlock(&port->open_excl); + + return 0; +} + +static ssize_t +qti_ctrl_read(struct file *fp, char __user *buf, size_t count, loff_t *pos) +{ + struct qti_ctrl_port *port = container_of(fp->private_data, + struct qti_ctrl_port, + ctrl_device); + struct rmnet_ctrl_pkt *cpkt = NULL; + unsigned long flags; + int ret = 0; + + pr_debug("%s: Enter(%zu)\n", __func__, count); + + if (count > MAX_QTI_PKT_SIZE) { + pr_err("Buffer size is too big %zu, should be at most %d\n", + count, MAX_QTI_PKT_SIZE); + return -EINVAL; + } + + if (qti_ctrl_lock(&port->read_excl)) { + pr_err("Previous reading is not finished yet\n"); + return -EBUSY; + } + + /* block until a new packet is available */ + do { + spin_lock_irqsave(&port->lock, flags); + if (!list_empty(&port->cpkt_req_q)) + break; + spin_unlock_irqrestore(&port->lock, flags); + + pr_debug("%s: Requests list is empty. Wait.\n", __func__); + ret = wait_event_interruptible(port->read_wq, + !list_empty(&port->cpkt_req_q)); + if (ret < 0) { + pr_debug("Waiting failed\n"); + qti_ctrl_unlock(&port->read_excl); + return -ERESTARTSYS; + } + } while (1); + + cpkt = list_first_entry(&port->cpkt_req_q, struct rmnet_ctrl_pkt, + list); + list_del(&cpkt->list); + spin_unlock_irqrestore(&port->lock, flags); + + if (cpkt->len > count) { + pr_err("cpkt size too big:%d > buf size:%zu\n", + cpkt->len, count); + qti_ctrl_unlock(&port->read_excl); + free_rmnet_ctrl_pkt(cpkt); + return -ENOMEM; + } + + pr_debug("%s: cpkt size:%d\n", __func__, cpkt->len); + + + qti_ctrl_unlock(&port->read_excl); + + ret = copy_to_user(buf, cpkt->buf, cpkt->len); + if (ret) { + pr_err("copy_to_user failed: err %d\n", ret); + ret = -EFAULT; + } else { + pr_debug("%s: copied %d bytes to user\n", __func__, cpkt->len); + ret = cpkt->len; + port->copied_to_modem++; + } + + free_rmnet_ctrl_pkt(cpkt); + + return ret; +} + +static ssize_t +qti_ctrl_write(struct file *fp, const char __user *buf, size_t count, + loff_t *pos) +{ + struct qti_ctrl_port *port = container_of(fp->private_data, + struct qti_ctrl_port, + ctrl_device); + void *kbuf; + unsigned long flags; + int ret = 0; + struct grmnet *g_rmnet = NULL; + + pr_debug("%s: Enter(%zu) port_index=%d", __func__, count, port->index); + + if (!count) { + pr_debug("zero length ctrl pkt\n"); + return -EINVAL; + } + + if (count > MAX_QTI_PKT_SIZE) { + pr_debug("given pkt size too big:%zu > max_pkt_size:%d\n", + count, MAX_QTI_PKT_SIZE); + return -EINVAL; + } + + if (qti_ctrl_lock(&port->write_excl)) { + pr_err("Previous writing not finished yet\n"); + return -EBUSY; + } + + if (!atomic_read(&port->connected)) { + pr_debug("USB cable not connected\n"); + qti_ctrl_unlock(&port->write_excl); + return -EPIPE; + } + + kbuf = kmalloc(count, GFP_KERNEL); + if (!kbuf) { + pr_err("failed to allocate ctrl pkt\n"); + qti_ctrl_unlock(&port->write_excl); + return -ENOMEM; + } + ret = copy_from_user(kbuf, buf, count); + if (ret) { + pr_err("copy_from_user failed err:%d\n", ret); + kfree(kbuf); + qti_ctrl_unlock(&port->write_excl); + return -EFAULT; + } + port->copied_from_modem++; + + spin_lock_irqsave(&port->lock, flags); + if (port && port->port_usb) { + if (port->gtype == USB_GADGET_RMNET) { + g_rmnet = (struct grmnet *)port->port_usb; + } else { + spin_unlock_irqrestore(&port->lock, flags); + pr_err("%s(): unrecognized gadget type(%d).\n", + __func__, port->gtype); + return -EINVAL; + } + + if (g_rmnet && g_rmnet->send_cpkt_response) { + ret = g_rmnet->send_cpkt_response(port->port_usb, + kbuf, count); + if (ret) + pr_err("%d failed to send ctrl packet.\n", ret); + port->modem_to_host++; + } else { + pr_err("send_cpkt_response callback is NULL\n"); + ret = -EINVAL; + } + } + + spin_unlock_irqrestore(&port->lock, flags); + kfree(kbuf); + qti_ctrl_unlock(&port->write_excl); + + pr_debug("%s: Exit(%zu)", __func__, count); + return (ret) ? ret : count; +} + +static long qti_ctrl_ioctl(struct file *fp, unsigned cmd, unsigned long arg) +{ + struct qti_ctrl_port *port = container_of(fp->private_data, + struct qti_ctrl_port, + ctrl_device); + struct grmnet *gr = NULL; + struct ep_info info; + int val, ret = 0; + + pr_debug("%s: Received command %d for gtype:%d\n", + __func__, cmd, port->gtype); + + if (qti_ctrl_lock(&port->ioctl_excl)) + return -EBUSY; + + switch (cmd) { + case QTI_CTRL_MODEM_OFFLINE: + if (port && (port->gtype == USB_GADGET_DPL)) { + pr_err("%s(): Modem Offline not handled\n", __func__); + goto exit_ioctl; + } + + if (port && port->port_usb) + gr = port->port_usb; + + if (gr && gr->disconnect) + gr->disconnect(gr); + break; + case QTI_CTRL_MODEM_ONLINE: + if (port && (port->gtype == USB_GADGET_DPL)) { + pr_err("%s(): Modem Online not handled\n", __func__); + goto exit_ioctl; + } + + if (port && port->port_usb) + gr = port->port_usb; + + if (gr && gr->connect) + gr->connect(gr); + break; + case QTI_CTRL_GET_LINE_STATE: + val = atomic_read(&port->line_state); + ret = copy_to_user((void __user *)arg, &val, sizeof(val)); + if (ret) { + pr_err("copying to user space failed"); + ret = -EFAULT; + } + pr_debug("%s: Sent line_state: %d for gtype:%d\n", __func__, + atomic_read(&port->line_state), port->gtype); + break; + case QTI_CTRL_EP_LOOKUP: + + pr_debug("%s(): EP_LOOKUP for gtype:%d\n", __func__, + port->gtype); + val = atomic_read(&port->connected); + if (!val) { + pr_err_ratelimited("EP_LOOKUP failed: not connected\n"); + ret = -EAGAIN; + break; + } + + if (port->ipa_prod_idx == -1 && port->ipa_cons_idx == -1) { + pr_err_ratelimited("EP_LOOKUP ipa pipes not updated\n"); + ret = -EAGAIN; + break; + } + + info.ph_ep_info.ep_type = port->ep_type; + info.ph_ep_info.peripheral_iface_id = port->intf; + info.ipa_ep_pair.cons_pipe_num = port->ipa_cons_idx; + info.ipa_ep_pair.prod_pipe_num = port->ipa_prod_idx; + + pr_debug("%s(): gtype:%d ep_type:%d intf:%d\n", + __func__, port->gtype, info.ph_ep_info.ep_type, + info.ph_ep_info.peripheral_iface_id); + + pr_debug("%s(): ipa_cons_idx:%d ipa_prod_idx:%d\n", + __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) { + pr_err("copying to user space failed"); + ret = -EFAULT; + } + break; + default: + pr_err("wrong parameter"); + ret = -EINVAL; + } + +exit_ioctl: + qti_ctrl_unlock(&port->ioctl_excl); + + return ret; +} + +static unsigned int qti_ctrl_poll(struct file *file, poll_table *wait) +{ + struct qti_ctrl_port *port = container_of(file->private_data, + struct qti_ctrl_port, + ctrl_device); + unsigned long flags; + unsigned int mask = 0; + + if (!port) { + pr_err("%s on a NULL device\n", __func__); + return POLLERR; + } + + poll_wait(file, &port->read_wq, wait); + + spin_lock_irqsave(&port->lock, flags); + if (!list_empty(&port->cpkt_req_q)) { + mask |= POLLIN | POLLRDNORM; + pr_debug("%s sets POLLIN for rmnet_ctrl_qti_port\n", __func__); + } + spin_unlock_irqrestore(&port->lock, flags); + + return mask; +} + +static int qti_ctrl_read_stats(struct seq_file *s, void *unused) +{ + struct qti_ctrl_port *port = s->private; + unsigned long flags; + int i; + + for (i = 0; i < NR_QTI_PORTS; i++) { + port = ctrl_port[i]; + if (!port) + continue; + spin_lock_irqsave(&port->lock, flags); + + seq_printf(s, "\n#PORT:%d port: %p\n", i, port); + seq_printf(s, "name: %s\n", port->name); + seq_printf(s, "host_to_modem: %d\n", + port->host_to_modem); + seq_printf(s, "copied_to_modem: %d\n", + port->copied_to_modem); + seq_printf(s, "copied_from_modem: %d\n", + port->copied_from_modem); + seq_printf(s, "modem_to_host: %d\n", + port->modem_to_host); + seq_printf(s, "cpkt_drp_cnt: %d\n", + port->drp_cpkt_cnt); + spin_unlock_irqrestore(&port->lock, flags); + } + + return 0; +} + +static int qti_ctrl_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, qti_ctrl_read_stats, inode->i_private); +} + +static ssize_t qti_ctrl_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct qti_ctrl_port *port = s->private; + int i; + unsigned long flags; + + for (i = 0; i < NR_QTI_PORTS; i++) { + port = ctrl_port[i]; + if (!port) + continue; + + spin_lock_irqsave(&port->lock, flags); + port->host_to_modem = 0; + port->copied_to_modem = 0; + port->copied_from_modem = 0; + port->modem_to_host = 0; + port->drp_cpkt_cnt = 0; + spin_unlock_irqrestore(&port->lock, flags); + } + return count; +} + +const struct file_operations qti_ctrl_stats_ops = { + .open = qti_ctrl_stats_open, + .read = seq_read, + .write = qti_ctrl_reset_stats, +}; + +static struct dentry *qti_ctrl_dent; +static void qti_ctrl_debugfs_init(void) +{ + struct dentry *qti_ctrl_dfile; + + qti_ctrl_dent = debugfs_create_dir("usb_qti", 0); + if (IS_ERR(qti_ctrl_dent)) + return; + + qti_ctrl_dfile = + debugfs_create_file("status", 0444, qti_ctrl_dent, 0, + &qti_ctrl_stats_ops); + if (!qti_ctrl_dfile || IS_ERR(qti_ctrl_dfile)) + debugfs_remove(qti_ctrl_dent); +} + +static void qti_ctrl_debugfs_exit(void) +{ + debugfs_remove_recursive(qti_ctrl_dent); +} + +/* file operations for rmnet device /dev/rmnet_ctrl */ +static const struct file_operations qti_ctrl_fops = { + .owner = THIS_MODULE, + .open = qti_ctrl_open, + .release = qti_ctrl_release, + .read = qti_ctrl_read, + .write = qti_ctrl_write, + .unlocked_ioctl = qti_ctrl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = qti_ctrl_ioctl, +#endif + .poll = qti_ctrl_poll, +}; +/* file operations for DPL device /dev/dpl_ctrl */ +static const struct file_operations dpl_qti_ctrl_fops = { + .owner = THIS_MODULE, + .open = qti_ctrl_open, + .release = qti_ctrl_release, + .read = qti_ctrl_read, + .write = NULL, + .unlocked_ioctl = qti_ctrl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = qti_ctrl_ioctl, +#endif + .poll = qti_ctrl_poll, +}; + +int gqti_ctrl_init(void) +{ + int ret, i, sz = QTI_CTRL_NAME_LEN; + struct qti_ctrl_port *port = NULL; + + for (i = 0; i < NR_QTI_PORTS; i++) { + port = kzalloc(sizeof(struct qti_ctrl_port), GFP_KERNEL); + if (!port) { + pr_err("Failed to allocate rmnet control device\n"); + ret = -ENOMEM; + goto fail_init; + } + + INIT_LIST_HEAD(&port->cpkt_req_q); + spin_lock_init(&port->lock); + + atomic_set(&port->open_excl, 0); + atomic_set(&port->read_excl, 0); + atomic_set(&port->write_excl, 0); + atomic_set(&port->ioctl_excl, 0); + atomic_set(&port->connected, 0); + atomic_set(&port->line_state, 0); + + init_waitqueue_head(&port->read_wq); + + ctrl_port[i] = port; + port->index = i; + port->ipa_prod_idx = -1; + port->ipa_cons_idx = -1; + + if (i == 0) + strlcat(port->name, RMNET_CTRL_QTI_NAME, sz); + else if (i == DPL_QTI_CTRL_PORT_NO) + strlcat(port->name, DPL_CTRL_QTI_NAME, sz); + else + snprintf(port->name, sz, "%s%d", + RMNET_CTRL_QTI_NAME, i); + + port->ctrl_device.name = port->name; + if (i == DPL_QTI_CTRL_PORT_NO) + port->ctrl_device.fops = &dpl_qti_ctrl_fops; + else + port->ctrl_device.fops = &qti_ctrl_fops; + port->ctrl_device.minor = MISC_DYNAMIC_MINOR; + + ret = misc_register(&port->ctrl_device); + if (ret) { + pr_err("rmnet control driver failed to register"); + goto fail_init; + } + } + qti_ctrl_debugfs_init(); + + return ret; + +fail_init: + for (i--; i >= 0; i--) { + misc_deregister(&ctrl_port[i]->ctrl_device); + kfree(ctrl_port[i]); + ctrl_port[i] = NULL; + } + return ret; +} + +void gqti_ctrl_cleanup(void) +{ + int i; + + for (i = 0; i < NR_QTI_PORTS; i++) { + misc_deregister(&ctrl_port[i]->ctrl_device); + kfree(ctrl_port[i]); + ctrl_port[i] = NULL; + } + qti_ctrl_debugfs_exit(); +} diff --git a/drivers/usb/gadget/function/u_data_ipa.c b/drivers/usb/gadget/function/u_data_ipa.c new file mode 100644 index 000000000000..1be03f83249b --- /dev/null +++ b/drivers/usb/gadget/function/u_data_ipa.c @@ -0,0 +1,877 @@ +/* Copyright (c) 2014-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb_gadget_xport.h" + +#define IPA_N_PORTS 4 +struct ipa_data_ch_info { + struct usb_request *rx_req; + struct usb_request *tx_req; + unsigned long flags; + unsigned id; + enum transport_type trans; + enum gadget_type gtype; + bool is_connected; + unsigned port_num; + spinlock_t port_lock; + + struct work_struct connect_w; + struct work_struct disconnect_w; + struct work_struct suspend_w; + struct work_struct resume_w; + + u32 src_pipe_idx; + u32 dst_pipe_idx; + u8 src_connection_idx; + u8 dst_connection_idx; + enum usb_ctrl usb_bam_type; + struct gadget_ipa_port *port_usb; + struct usb_bam_connect_ipa_params ipa_params; +}; + +static int n_ipa_ports; +static struct workqueue_struct *ipa_data_wq; +struct ipa_data_ch_info *ipa_data_ports[IPA_N_PORTS]; +/** + * ipa_data_endless_complete() - completion callback for endless TX/RX request + * @ep: USB endpoint for which this completion happen + * @req: USB endless request + * + * This completion is being called when endless (TX/RX) transfer is terminated + * i.e. disconnect or suspend case. + */ +static void ipa_data_endless_complete(struct usb_ep *ep, + struct usb_request *req) +{ + pr_debug("%s: endless complete for(%s) with status: %d\n", + __func__, ep->name, req->status); +} + +/** + * ipa_data_start_endless_xfer() - configure USB endpoint and + * queue endless TX/RX request + * @port: USB IPA data channel information + * @in: USB endpoint direction i.e. true: IN(Device TX), false: OUT(Device RX) + * + * It is being used to queue endless TX/RX request with UDC driver. + * It does set required DBM endpoint configuration before queueing endless + * TX/RX request. + */ +static void ipa_data_start_endless_xfer(struct ipa_data_ch_info *port, bool in) +{ + int status; + + if (!port->port_usb) { + pr_err("%s(): port_usb is NULL.\n", __func__); + return; + } + + if (in) { + pr_debug("%s: enqueue endless TX_REQ(IN)\n", __func__); + status = usb_ep_queue(port->port_usb->in, + port->tx_req, GFP_ATOMIC); + if (status) + pr_err("error enqueuing endless TX_REQ, %d\n", status); + } else { + pr_debug("%s: enqueue endless RX_REQ(OUT)\n", __func__); + status = usb_ep_queue(port->port_usb->out, + port->rx_req, GFP_ATOMIC); + if (status) + pr_err("error enqueuing endless RX_REQ, %d\n", status); + } +} + +/** + * ipa_data_stop_endless_xfer() - terminate and dequeue endless TX/RX request + * @port: USB IPA data channel information + * @in: USB endpoint direction i.e. IN - Device TX, OUT - Device RX + * + * It is being used to terminate and dequeue endless TX/RX request with UDC + * driver. + */ +static void ipa_data_stop_endless_xfer(struct ipa_data_ch_info *port, bool in) +{ + int status; + + if (!port->port_usb) { + pr_err("%s(): port_usb is NULL.\n", __func__); + return; + } + + if (in) { + pr_debug("%s: dequeue endless TX_REQ(IN)\n", __func__); + status = usb_ep_dequeue(port->port_usb->in, port->tx_req); + if (status) + pr_err("error dequeueing endless TX_REQ, %d\n", status); + } else { + pr_debug("%s: dequeue endless RX_REQ(OUT)\n", __func__); + status = usb_ep_dequeue(port->port_usb->out, port->rx_req); + if (status) + pr_err("error dequeueing endless RX_REQ, %d\n", status); + } +} + +/** + * ipa_data_disconnect_work() - Perform USB IPA BAM disconnect + * @w: disconnect work + * + * It is being schedule from ipa_data_disconnect() API when particular function + * is being disable due to USB disconnect or USB composition switch is being + * trigger . This API performs disconnect of USB BAM pipe, IPA BAM pipe and also + * initiate USB IPA BAM pipe handshake for USB Disconnect sequence. Due to + * handshake operation and involvement of SPS related APIs, this functioality + * can't be used from atomic context. + */ +static void ipa_data_disconnect_work(struct work_struct *w) +{ + struct ipa_data_ch_info *port = container_of(w, struct ipa_data_ch_info, + disconnect_w); + unsigned long flags; + int ret; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->is_connected) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_debug("Already disconnected.\n"); + return; + } + port->is_connected = false; + pr_debug("%s(): prod_clnt_hdl:%d cons_clnt_hdl:%d\n", __func__, + port->ipa_params.prod_clnt_hdl, + port->ipa_params.cons_clnt_hdl); + + spin_unlock_irqrestore(&port->port_lock, flags); + ret = usb_bam_disconnect_ipa(port->usb_bam_type, &port->ipa_params); + if (ret) + pr_err("usb_bam_disconnect_ipa failed: err:%d\n", ret); + + if (port->ipa_params.prod_clnt_hdl) + usb_bam_free_fifos(port->usb_bam_type, + port->dst_connection_idx); + if (port->ipa_params.cons_clnt_hdl) + usb_bam_free_fifos(port->usb_bam_type, + port->src_connection_idx); + + pr_debug("%s(): disconnect work completed.\n", __func__); +} + +/** + * ipa_data_disconnect() - Restore USB ep operation and disable USB endpoint + * @gp: USB gadget IPA Port + * @port_num: Port num used by function driver which need to be disable + * + * It is being called from atomic context from gadget driver when particular + * function is being disable due to USB cable disconnect or USB composition + * switch is being trigger. This API performs restoring USB endpoint operation + * and disable USB endpoint used for accelerated path. + */ +void ipa_data_disconnect(struct gadget_ipa_port *gp, u8 port_num) +{ + struct ipa_data_ch_info *port; + unsigned long flags; + struct usb_gadget *gadget = NULL; + + pr_debug("dev:%p port number:%d\n", gp, port_num); + if (port_num >= n_ipa_ports) { + pr_err("invalid ipa portno#%d\n", port_num); + return; + } + + if (!gp) { + pr_err("data port is null\n"); + return; + } + + port = ipa_data_ports[port_num]; + if (!port) { + pr_err("port %u is NULL", port_num); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) { + gadget = port->port_usb->cdev->gadget; + port->port_usb->ipa_consumer_ep = -1; + port->port_usb->ipa_producer_ep = -1; + + if (port->port_usb->in) { + /* + * Disable endpoints. + * Unlocking is needed since disabling the eps might + * stop active transfers and therefore the request + * complete function will be called, where we try + * to obtain the spinlock as well. + */ + if (gadget_is_dwc3(gadget)) + msm_ep_unconfig(port->port_usb->in); + spin_unlock_irqrestore(&port->port_lock, flags); + usb_ep_disable(port->port_usb->in); + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb->in->endless = false; + } + + if (port->port_usb->out) { + if (gadget_is_dwc3(gadget)) + msm_ep_unconfig(port->port_usb->out); + spin_unlock_irqrestore(&port->port_lock, flags); + usb_ep_disable(port->port_usb->out); + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb->out->endless = false; + } + + port->port_usb = NULL; + } + spin_unlock_irqrestore(&port->port_lock, flags); + queue_work(ipa_data_wq, &port->disconnect_w); +} + +/** + * configure_fifo() - Configure USB BAM Pipe's data FIFO + * @idx: USB BAM Pipe index + * @ep: USB endpoint + * + * This function configures USB BAM data fifo using fetched pipe configuraion + * using provided index value. This function needs to used before starting + * endless transfer. + */ +static void configure_fifo(enum usb_ctrl bam_type, u8 idx, struct usb_ep *ep) +{ + struct u_bam_data_connect_info bam_info; + struct sps_mem_buffer data_fifo = {0}; + + get_bam2bam_connection_info(bam_type, idx, + &bam_info.usb_bam_pipe_idx, + NULL, &data_fifo, NULL); + msm_data_fifo_config(ep, data_fifo.phys_base, data_fifo.size, + bam_info.usb_bam_pipe_idx); +} + +/** + * ipa_data_connect_work() - Perform USB IPA BAM connect + * @w: connect work + * + * It is being schedule from ipa_data_connect() API when particular function + * which is using USB IPA accelerated path. This API performs allocating request + * for USB endpoint (tx/rx) for endless purpose, configure USB endpoint to be + * used in accelerated path, connect of USB BAM pipe, IPA BAM pipe and also + * initiate USB IPA BAM pipe handshake for connect sequence. + */ +static void ipa_data_connect_work(struct work_struct *w) +{ + struct ipa_data_ch_info *port = container_of(w, struct ipa_data_ch_info, + connect_w); + struct gadget_ipa_port *gport; + struct usb_gadget *gadget = NULL; + u32 sps_params; + int ret; + unsigned long flags; + bool is_ipa_disconnected = true; + + pr_debug("%s: Connect workqueue started", __func__); + + spin_lock_irqsave(&port->port_lock, flags); + + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s(): port_usb is NULL.\n", __func__); + return; + } + + gport = port->port_usb; + if (gport && gport->cdev) + gadget = gport->cdev->gadget; + + if (!gadget) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: gport is NULL.\n", __func__); + return; + } + + gport->ipa_consumer_ep = -1; + gport->ipa_producer_ep = -1; + if (gport->out) { + port->rx_req = usb_ep_alloc_request(gport->out, GFP_ATOMIC); + if (!port->rx_req) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: failed to allocate rx_req\n", __func__); + return; + } + port->rx_req->context = port; + port->rx_req->complete = ipa_data_endless_complete; + port->rx_req->length = 0; + port->rx_req->no_interrupt = 1; + } + + if (gport->in) { + port->tx_req = usb_ep_alloc_request(gport->in, GFP_ATOMIC); + if (!port->tx_req) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: failed to allocate tx_req\n", __func__); + goto free_rx_req; + } + port->tx_req->context = port; + port->tx_req->complete = ipa_data_endless_complete; + port->tx_req->length = 0; + port->tx_req->no_interrupt = 1; + } + + port->is_connected = true; + spin_unlock_irqrestore(&port->port_lock, flags); + + /* update IPA Parameteres here. */ + port->ipa_params.usb_connection_speed = gadget->speed; + if (gadget_is_dwc3(gadget)) + port->ipa_params.reset_pipe_after_lpm = + msm_dwc3_reset_ep_after_lpm(gadget); + port->ipa_params.skip_ep_cfg = true; + port->ipa_params.keep_ipa_awake = true; + port->ipa_params.cons_clnt_hdl = -1; + port->ipa_params.prod_clnt_hdl = -1; + + + if (gport->out) { + usb_bam_alloc_fifos(port->usb_bam_type, + port->src_connection_idx); + + if (gadget_is_dwc3(gadget)) { + sps_params = MSM_SPS_MODE | MSM_DISABLE_WB + | MSM_PRODUCER | port->src_pipe_idx; + port->rx_req->length = 32*1024; + port->rx_req->udc_priv = sps_params; + configure_fifo(port->usb_bam_type, + port->src_connection_idx, + port->port_usb->out); + ret = msm_ep_config(gport->out, port->rx_req, + GFP_ATOMIC); + if (ret) { + pr_err("msm_ep_config() failed for OUT EP\n"); + usb_bam_free_fifos(port->usb_bam_type, + port->src_connection_idx); + goto free_rx_tx_req; + } + } else { + sps_params = (MSM_SPS_MODE | port->src_pipe_idx | + MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER; + port->rx_req->udc_priv = sps_params; + } + } + + if (gport->in) { + usb_bam_alloc_fifos(port->usb_bam_type, + port->dst_connection_idx); + if (gadget_is_dwc3(gadget)) { + sps_params = MSM_SPS_MODE | MSM_DISABLE_WB | + port->dst_pipe_idx; + port->tx_req->length = 32*1024; + port->tx_req->udc_priv = sps_params; + configure_fifo(port->usb_bam_type, + port->dst_connection_idx, gport->in); + ret = msm_ep_config(gport->in, port->tx_req, + GFP_ATOMIC); + if (ret) { + pr_err("msm_ep_config() failed for IN EP\n"); + goto unconfig_msm_ep_out; + } + } else { + sps_params = (MSM_SPS_MODE | port->dst_pipe_idx | + MSM_VENDOR_ID) & ~MSM_IS_FINITE_TRANSFER; + port->tx_req->udc_priv = sps_params; + } + } + + /* + * Perform below operations for Tx from Device (OUT transfer) + * 1. Connect with pipe of USB BAM with IPA BAM pipe + * 2. Update USB Endpoint related information using SPS Param. + * 3. Configure USB Endpoint/DBM for the same. + * 4. Override USB ep queue functionality for endless transfer. + */ + if (gport->out) { + pr_debug("configure bam ipa connect for USB OUT\n"); + port->ipa_params.dir = USB_TO_PEER_PERIPHERAL; + ret = usb_bam_connect_ipa(port->usb_bam_type, + &port->ipa_params); + if (ret) { + pr_err("usb_bam_connect_ipa out failed err:%d\n", ret); + goto unconfig_msm_ep_in; + } + gadget->bam2bam_func_enabled = true; + + gport->ipa_consumer_ep = port->ipa_params.ipa_cons_ep_idx; + is_ipa_disconnected = false; + } + + if (gport->in) { + pr_debug("configure bam ipa connect for USB IN\n"); + port->ipa_params.dir = PEER_PERIPHERAL_TO_USB; + port->ipa_params.dst_client = IPA_CLIENT_USB_DPL_CONS; + ret = usb_bam_connect_ipa(port->usb_bam_type, + &port->ipa_params); + if (ret) { + pr_err("usb_bam_connect_ipa IN failed err:%d\n", ret); + goto disconnect_usb_bam_ipa_out; + } + gadget->bam2bam_func_enabled = true; + + gport->ipa_producer_ep = port->ipa_params.ipa_prod_ep_idx; + is_ipa_disconnected = false; + } + + pr_debug("ipa_producer_ep:%d ipa_consumer_ep:%d\n", + gport->ipa_producer_ep, + gport->ipa_consumer_ep); + + gqti_ctrl_update_ipa_pipes(NULL, DPL_QTI_CTRL_PORT_NO, + gport->ipa_producer_ep, + gport->ipa_consumer_ep); + + pr_debug("src_bam_idx:%d dst_bam_idx:%d\n", + port->src_connection_idx, port->dst_connection_idx); + + if (gport->out) + ipa_data_start_endless_xfer(port, false); + if (gport->in) + ipa_data_start_endless_xfer(port, true); + + pr_debug("Connect workqueue done (port %p)", port); + return; + +disconnect_usb_bam_ipa_out: + if (!is_ipa_disconnected) { + usb_bam_disconnect_ipa(port->usb_bam_type, &port->ipa_params); + is_ipa_disconnected = true; + } +unconfig_msm_ep_in: + if (gport->in) + msm_ep_unconfig(port->port_usb->in); +unconfig_msm_ep_out: + if (gport->in) + usb_bam_free_fifos(port->usb_bam_type, + port->dst_connection_idx); + if (gport->out) { + msm_ep_unconfig(port->port_usb->out); + usb_bam_free_fifos(port->usb_bam_type, + port->src_connection_idx); + } +free_rx_tx_req: + spin_lock_irqsave(&port->port_lock, flags); + port->is_connected = false; + spin_unlock_irqrestore(&port->port_lock, flags); + if (gport->in && port->tx_req) + usb_ep_free_request(gport->in, port->tx_req); +free_rx_req: + if (gport->out && port->rx_req) + usb_ep_free_request(gport->out, port->rx_req); +} + +/** + * ipa_data_connect() - Prepare IPA params and enable USB endpoints + * @gp: USB IPA gadget port + * @port_num: port number used by accelerated function + * @src_connection_idx: USB BAM pipe index used as producer + * @dst_connection_idx: USB BAM pipe index used as consumer + * + * It is being called from accelerated function driver (from set_alt()) to + * initiate USB BAM IPA connection. This API is enabling accelerated endpoints + * and schedule connect_work() which establishes USB IPA BAM communication. + */ +int ipa_data_connect(struct gadget_ipa_port *gp, u8 port_num, + u8 src_connection_idx, u8 dst_connection_idx) +{ + struct ipa_data_ch_info *port; + unsigned long flags; + int ret; + + pr_debug("dev:%p port#%d src_connection_idx:%d dst_connection_idx:%d\n", + gp, port_num, src_connection_idx, dst_connection_idx); + + if (port_num >= n_ipa_ports) { + pr_err("invalid portno#%d\n", port_num); + ret = -ENODEV; + goto err; + } + + if (!gp) { + pr_err("gadget port is null\n"); + ret = -ENODEV; + goto err; + } + + port = ipa_data_ports[port_num]; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = gp; + port->src_connection_idx = src_connection_idx; + port->dst_connection_idx = dst_connection_idx; + port->usb_bam_type = usb_bam_get_bam_type(gp->cdev->gadget->name); + + port->ipa_params.src_pipe = &(port->src_pipe_idx); + port->ipa_params.dst_pipe = &(port->dst_pipe_idx); + port->ipa_params.src_idx = src_connection_idx; + port->ipa_params.dst_idx = dst_connection_idx; + + /* + * Disable Xfer complete and Xfer not ready interrupts by + * marking endless flag which is used in UDC driver to enable + * these interrupts. with this set, these interrupts for selected + * endpoints won't be enabled. + */ + if (port->port_usb->in) { + port->port_usb->in->endless = true; + ret = usb_ep_enable(port->port_usb->in); + if (ret) { + pr_err("usb_ep_enable failed eptype:IN ep:%p", + port->port_usb->in); + port->port_usb->in->endless = false; + goto err_usb_in; + } + } + + if (port->port_usb->out) { + port->port_usb->out->endless = true; + ret = usb_ep_enable(port->port_usb->out); + if (ret) { + pr_err("usb_ep_enable failed eptype:OUT ep:%p", + port->port_usb->out); + port->port_usb->out->endless = false; + goto err_usb_out; + } + } + + if (!port->port_usb->out && !port->port_usb->in) { + pr_err("%s(): No USB endpoint enabled.\n", __func__); + ret = -EINVAL; + goto err_usb_in; + } + + queue_work(ipa_data_wq, &port->connect_w); + spin_unlock_irqrestore(&port->port_lock, flags); + + return ret; + +err_usb_out: + if (port->port_usb->in) + port->port_usb->in->endless = false; +err_usb_in: + spin_unlock_irqrestore(&port->port_lock, flags); +err: + pr_debug("%s(): failed with error:%d\n", __func__, ret); + return ret; +} + +/** + * ipa_data_start() - Restart USB endless transfer + * @param: IPA data channel information + * @dir: USB BAM pipe direction + * + * It is being used to restart USB endless transfer for USB bus resume. + * For USB consumer case, it restarts USB endless RX transfer, whereas + * for USB producer case, it resets DBM endpoint and restart USB endless + * TX transfer. + */ +static void ipa_data_start(void *param, enum usb_bam_pipe_dir dir) +{ + struct ipa_data_ch_info *port = param; + struct usb_gadget *gadget = NULL; + + if (!port || !port->port_usb || !port->port_usb->cdev->gadget) { + pr_err("%s:port,cdev or gadget is NULL\n", __func__); + return; + } + + gadget = port->port_usb->cdev->gadget; + if (dir == USB_TO_PEER_PERIPHERAL) { + pr_debug("%s(): start endless RX\n", __func__); + ipa_data_start_endless_xfer(port, false); + } else { + pr_debug("%s(): start endless TX\n", __func__); + if (msm_dwc3_reset_ep_after_lpm(gadget)) { + configure_fifo(port->usb_bam_type, + port->dst_connection_idx, port->port_usb->in); + } + ipa_data_start_endless_xfer(port, true); + } +} + +/** + * ipa_data_stop() - Stop endless Tx/Rx transfers + * @param: IPA data channel information + * @dir: USB BAM pipe direction + * + * It is being used to stop endless Tx/Rx transfers. It is being used + * for USB bus suspend functionality. + */ +static void ipa_data_stop(void *param, enum usb_bam_pipe_dir dir) +{ + struct ipa_data_ch_info *port = param; + struct usb_gadget *gadget = NULL; + + if (!port || !port->port_usb || !port->port_usb->cdev->gadget) { + pr_err("%s:port,cdev or gadget is NULL\n", __func__); + return; + } + + gadget = port->port_usb->cdev->gadget; + if (dir == USB_TO_PEER_PERIPHERAL) { + pr_debug("%s(): stop endless RX transfer\n", __func__); + ipa_data_stop_endless_xfer(port, false); + } else { + pr_debug("%s(): stop endless TX transfer\n", __func__); + ipa_data_stop_endless_xfer(port, true); + } +} + +/** + * ipa_data_suspend() - Initiate USB BAM IPA suspend functionality + * @gp: Gadget IPA port + * @port_num: port number used by function + * + * It is being used to initiate USB BAM IPA suspend functionality + * for USB bus suspend functionality. + */ +void ipa_data_suspend(struct gadget_ipa_port *gp, u8 port_num) +{ + struct ipa_data_ch_info *port; + int ret; + + pr_debug("dev:%p port number:%d\n", gp, port_num); + + if (port_num >= n_ipa_ports) { + pr_err("invalid ipa portno#%d\n", port_num); + return; + } + + if (!gp) { + pr_err("data port is null\n"); + return; + } + + port = ipa_data_ports[port_num]; + if (!port) { + pr_err("port %u is NULL", port_num); + return; + } + + pr_debug("%s: suspend started\n", __func__); + ret = usb_bam_register_wake_cb(port->usb_bam_type, + port->dst_connection_idx, NULL, port); + if (ret) { + pr_err("%s(): Failed to register BAM wake callback.\n", + __func__); + return; + } + + usb_bam_register_start_stop_cbs(port->usb_bam_type, + port->dst_connection_idx, ipa_data_start, + ipa_data_stop, port); + usb_bam_suspend(port->usb_bam_type, &port->ipa_params); +} + +/** + * ipa_data_resume() - Initiate USB resume functionality + * @gp: Gadget IPA port + * @port_num: port number used by function + * + * It is being used to initiate USB resume functionality + * for USB bus resume case. + */ +void ipa_data_resume(struct gadget_ipa_port *gp, u8 port_num) +{ + struct ipa_data_ch_info *port; + unsigned long flags; + struct usb_gadget *gadget = NULL; + int ret; + + pr_debug("dev:%p port number:%d\n", gp, port_num); + + if (port_num >= n_ipa_ports) { + pr_err("invalid ipa portno#%d\n", port_num); + return; + } + + if (!gp) { + pr_err("data port is null\n"); + return; + } + + port = ipa_data_ports[port_num]; + if (!port) { + pr_err("port %u is NULL", port_num); + return; + } + + pr_debug("%s: resume started\n", __func__); + spin_lock_irqsave(&port->port_lock, flags); + gadget = port->port_usb->cdev->gadget; + if (!gadget) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s(): Gadget is NULL.\n", __func__); + return; + } + + ret = usb_bam_register_wake_cb(port->usb_bam_type, + port->dst_connection_idx, NULL, NULL); + if (ret) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s(): Failed to register BAM wake callback.\n", + __func__); + return; + } + + if (msm_dwc3_reset_ep_after_lpm(gadget)) { + configure_fifo(port->usb_bam_type, port->src_connection_idx, + port->port_usb->out); + configure_fifo(port->usb_bam_type, port->dst_connection_idx, + port->port_usb->in); + spin_unlock_irqrestore(&port->port_lock, flags); + msm_dwc3_reset_dbm_ep(port->port_usb->in); + spin_lock_irqsave(&port->port_lock, flags); + usb_bam_resume(port->usb_bam_type, &port->ipa_params); + } + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +/** + * ipa_data_port_alloc() - Allocate IPA USB Port structure + * @portno: port number to be used by particular USB function + * + * It is being used by USB function driver to allocate IPA data port + * for USB IPA data accelerated path. + * + * Retrun: 0 in case of success, otherwise errno. + */ +static int ipa_data_port_alloc(int portno) +{ + struct ipa_data_ch_info *port = NULL; + + if (ipa_data_ports[portno] != NULL) { + pr_debug("port %d already allocated.\n", portno); + return 0; + } + + port = kzalloc(sizeof(struct ipa_data_ch_info), GFP_KERNEL); + if (!port) { + pr_err("no memory to allocate port %d\n", portno); + return -ENOMEM; + } + + ipa_data_ports[portno] = port; + + pr_debug("port:%p with portno:%d allocated\n", port, portno); + return 0; +} + +/** + * ipa_data_port_select() - Select particular port for BAM2BAM IPA mode + * @portno: port number to be used by particular USB function + * @gtype: USB gadget function type + * + * It is being used by USB function driver to select which BAM2BAM IPA + * port particular USB function wants to use. + * + */ +void ipa_data_port_select(int portno, enum gadget_type gtype) +{ + struct ipa_data_ch_info *port = NULL; + + pr_debug("portno:%d\n", portno); + + port = ipa_data_ports[portno]; + port->port_num = portno; + port->is_connected = false; + + spin_lock_init(&port->port_lock); + + if (!work_pending(&port->connect_w)) + INIT_WORK(&port->connect_w, ipa_data_connect_work); + + if (!work_pending(&port->disconnect_w)) + INIT_WORK(&port->disconnect_w, ipa_data_disconnect_work); + + port->ipa_params.src_client = IPA_CLIENT_USB_PROD; + port->ipa_params.dst_client = IPA_CLIENT_USB_CONS; + port->gtype = gtype; +}; + +/** + * ipa_data_setup() - setup BAM2BAM IPA port + * @no_ipa_port: total number of BAM2BAM IPA port to support + * + * Each USB function who wants to use BAM2BAM IPA port would + * be counting number of IPA port to use and initialize those + * ports at time of bind_config() in android gadget driver. + * + * Retrun: 0 in case of success, otherwise errno. + */ +int ipa_data_setup(unsigned int no_ipa_port) +{ + int i, ret; + + pr_debug("requested %d IPA BAM ports", no_ipa_port); + + if (!no_ipa_port || no_ipa_port > IPA_N_PORTS) { + pr_err("Invalid num of ports count:%d\n", no_ipa_port); + return -EINVAL; + } + + for (i = 0; i < no_ipa_port; i++) { + n_ipa_ports++; + ret = ipa_data_port_alloc(i); + if (ret) { + n_ipa_ports--; + pr_err("Failed to alloc port:%d\n", i); + goto free_ipa_ports; + } + } + + pr_debug("n_ipa_ports:%d\n", n_ipa_ports); + + if (ipa_data_wq) { + pr_debug("ipa_data_wq is already setup."); + return 0; + } + + ipa_data_wq = alloc_workqueue("k_usb_ipa_data", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!ipa_data_wq) { + pr_err("Failed to create workqueue\n"); + ret = -ENOMEM; + goto free_ipa_ports; + } + + return 0; + +free_ipa_ports: + for (i = 0; i < n_ipa_ports; i++) { + kfree(ipa_data_ports[i]); + ipa_data_ports[i] = NULL; + if (ipa_data_wq) { + destroy_workqueue(ipa_data_wq); + ipa_data_wq = NULL; + } + } + + return ret; +} diff --git a/drivers/usb/gadget/function/u_data_ipa.h b/drivers/usb/gadget/function/u_data_ipa.h new file mode 100644 index 000000000000..b7d47ab1bb04 --- /dev/null +++ b/drivers/usb/gadget/function/u_data_ipa.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2014, 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 __U_DATA_IPA_H +#define __U_DATA_IPA_H + +#include "usb_gadget_xport.h" + +struct gadget_ipa_port { + struct usb_composite_dev *cdev; + struct usb_function *func; + struct usb_ep *in; + struct usb_ep *out; + int ipa_consumer_ep; + int ipa_producer_ep; +}; + +void ipa_data_port_select(int portno, enum gadget_type gtype); +void ipa_data_disconnect(struct gadget_ipa_port *gp, u8 port_num); +int ipa_data_connect(struct gadget_ipa_port *gp, u8 port_num, + u8 src_connection_idx, u8 dst_connection_idx); +int ipa_data_setup(unsigned int no_ipa_port); +void ipa_data_resume(struct gadget_ipa_port *gp, u8 port_num); +void ipa_data_suspend(struct gadget_ipa_port *gp, u8 port_num); + +#endif diff --git a/drivers/usb/gadget/function/u_qc_ether.c b/drivers/usb/gadget/function/u_qc_ether.c new file mode 100644 index 000000000000..118bab4477ce --- /dev/null +++ b/drivers/usb/gadget/function/u_qc_ether.c @@ -0,0 +1,454 @@ +/* + * u_qc_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * Copyright (c) 2012-2014, 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 + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include "u_ether.h" + + +/* + * This component encapsulates the Ethernet link glue needed to provide + * one (!) network link through the USB gadget stack, normally "usb0". + * + * The control and data models are handled by the function driver which + * connects to this code; such as CDC Ethernet (ECM or EEM), + * "CDC Subset", or RNDIS. That includes all descriptor and endpoint + * management. + * + * Link level addressing is handled by this component using module + * parameters; if no such parameters are provided, random link level + * addresses are used. Each end of the link uses one address. The + * host end address is exported in various ways, and is often recorded + * in configuration databases. + * + * The driver which assembles each configuration using such a link is + * responsible for ensuring that each configuration includes at most one + * instance of is network link. (The network layer provides ways for + * this single "physical" link to be used by multiple virtual links.) + * + * This utilities is based on Ethernet-over-USB link layer utilities and + * contains MSM specific implementation. + */ + +#define UETH__VERSION "29-May-2008" + +struct eth_qc_dev { + /* lock is held while accessing port_usb + * or updating its backlink port_usb->ioport + */ + spinlock_t lock; + struct qc_gether *port_usb; + + struct net_device *net; + struct usb_gadget *gadget; + + unsigned header_len; + + bool zlp; + u8 host_mac[ETH_ALEN]; +}; + +/*-------------------------------------------------------------------------*/ + +#undef DBG +#undef VDBG +#undef ERROR +#undef INFO + +#define xprintk(d, level, fmt, args...) \ + printk(level "%s: " fmt , (d)->net->name , ## args) + +#ifdef DEBUG +#undef DEBUG +#define DBG(dev, fmt, args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VDBG DBG +#else +#define VDBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#define ERROR(dev, fmt, args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define INFO(dev, fmt, args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + +/*-------------------------------------------------------------------------*/ + +/* NETWORK DRIVER HOOKUP (to the layer above this driver) */ +static int ueth_qc_change_mtu(struct net_device *net, int new_mtu) +{ + struct eth_qc_dev *dev = netdev_priv(net); + unsigned long flags; + int status = 0; + + /* don't change MTU on "live" link (peer won't know) */ + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + status = -EBUSY; + else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN) + status = -ERANGE; + else + net->mtu = new_mtu; + spin_unlock_irqrestore(&dev->lock, flags); + + return status; +} + +static void eth_qc_get_drvinfo(struct net_device *net, + struct ethtool_drvinfo *p) +{ + struct eth_qc_dev *dev = netdev_priv(net); + + strlcpy(p->driver, "g_qc_ether", sizeof p->driver); + strlcpy(p->version, UETH__VERSION, sizeof p->version); + strlcpy(p->fw_version, dev->gadget->name, sizeof p->fw_version); + strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof p->bus_info); +} + +static const struct ethtool_ops qc_ethtool_ops = { + .get_drvinfo = eth_qc_get_drvinfo, + .get_link = ethtool_op_get_link, +}; + +static netdev_tx_t eth_qc_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + return NETDEV_TX_OK; +} + +static int eth_qc_open(struct net_device *net) +{ + struct eth_qc_dev *dev = netdev_priv(net); + struct qc_gether *link; + + DBG(dev, "%s\n", __func__); + if (netif_carrier_ok(dev->net)) { + /* Force the netif to send the RTM_NEWLINK event + * that in use to notify on the USB cable status. + */ + netif_carrier_off(dev->net); + netif_carrier_on(dev->net); + netif_wake_queue(dev->net); + } + + spin_lock_irq(&dev->lock); + link = dev->port_usb; + if (link && link->open) + link->open(link); + spin_unlock_irq(&dev->lock); + + return 0; +} + +static int eth_qc_stop(struct net_device *net) +{ + struct eth_qc_dev *dev = netdev_priv(net); + unsigned long flags; + struct qc_gether *link = dev->port_usb; + + VDBG(dev, "%s\n", __func__); + netif_stop_queue(net); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb && link->close) + link->close(link); + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */ +static char *qc_dev_addr; +module_param(qc_dev_addr, charp, S_IRUGO); +MODULE_PARM_DESC(qc_dev_addr, "QC Device Ethernet Address"); + +/* this address is invisible to ifconfig */ +static char *qc_host_addr; +module_param(qc_host_addr, charp, S_IRUGO); +MODULE_PARM_DESC(qc_host_addr, "QC Host Ethernet Address"); + +static int get_qc_ether_addr(const char *str, u8 *dev_addr) +{ + if (str) { + unsigned i; + + for (i = 0; i < 6; i++) { + unsigned char num; + + if ((*str == '.') || (*str == ':')) + str++; + num = hex_to_bin(*str++) << 4; + num |= hex_to_bin(*str++); + dev_addr[i] = num; + } + if (is_valid_ether_addr(dev_addr)) + return 0; + } + random_ether_addr(dev_addr); + return 1; +} + +static const struct net_device_ops eth_qc_netdev_ops = { + .ndo_open = eth_qc_open, + .ndo_stop = eth_qc_stop, + .ndo_start_xmit = eth_qc_start_xmit, + .ndo_change_mtu = ueth_qc_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static struct device_type qc_gadget_type = { + .name = "gadget", +}; + +void gether_qc_get_macs(u8 dev_mac[ETH_ALEN], u8 host_mac[ETH_ALEN]) +{ + if (get_qc_ether_addr(qc_dev_addr, dev_mac)) + pr_debug("using random dev_mac ethernet address\n"); + if (get_qc_ether_addr(qc_host_addr, host_mac)) + pr_debug("using random host_mac ethernet address\n"); +} + +/** + * gether_qc_setup - initialize one ethernet-over-usb link + * @g: gadget to associated with these links + * @ethaddr: NULL, or a buffer in which the ethernet address of the + * host side of the link is recorded + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses are + * set up using module parameters. + * + * Returns negative errno, or zero on success + */ +int gether_qc_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) +{ + return gether_qc_setup_name(g, ethaddr, "usb"); +} + +/** + * gether_qc_setup_name - initialize one ethernet-over-usb link + * @g: gadget to associated with these links + * @ethaddr: NULL, or a buffer in which the ethernet address of the + * host side of the link is recorded + * @netname: name for network device (for example, "usb") + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses are + * set up using module parameters. + * + * Returns negative errno, or zero on success + */ +int gether_qc_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname) +{ + struct eth_qc_dev *dev; + struct net_device *net; + int status; + + net = alloc_etherdev(sizeof *dev); + if (!net) + return -ENOMEM; + + dev = netdev_priv(net); + spin_lock_init(&dev->lock); + + /* network device setup */ + dev->net = net; + snprintf(net->name, sizeof(net->name), "%s%%d", netname); + + if (get_qc_ether_addr(qc_dev_addr, net->dev_addr)) + dev_warn(&g->dev, + "using random %s ethernet address\n", "self"); + if (get_qc_ether_addr(qc_host_addr, dev->host_mac)) + dev_warn(&g->dev, + "using random %s ethernet address\n", "host"); + + if (ethaddr) + memcpy(ethaddr, dev->host_mac, ETH_ALEN); + + net->netdev_ops = ð_qc_netdev_ops; + net->ethtool_ops = &qc_ethtool_ops; + + netif_carrier_off(net); + + dev->gadget = g; + SET_NETDEV_DEV(net, &g->dev); + SET_NETDEV_DEVTYPE(net, &qc_gadget_type); + + status = register_netdev(net); + if (status < 0) { + dev_dbg(&g->dev, "register_netdev failed, %d\n", status); + free_netdev(net); + } else { + INFO(dev, "MAC %pM\n", net->dev_addr); + INFO(dev, "HOST MAC %pM\n", dev->host_mac); + + } + + return status; +} + +/** + * gether_qc_cleanup_name - remove Ethernet-over-USB device + * @netname: name for network device (for example, "usb") + * Context: may sleep + * + * This is called to free all resources allocated by @gether_qc_setup(). + */ +void gether_qc_cleanup_name(const char *netname) +{ + struct net_device *net_dev; + + /* Extract the eth_qc_dev from the net device */ + net_dev = dev_get_by_name(&init_net, netname); + + if (net_dev) { + dev_put(net_dev); + unregister_netdev(net_dev); + free_netdev(net_dev); + } +} + +struct net_device *gether_qc_get_net(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; +} +/** + * gether_qc_connect_name - notify network layer that USB link + * is active + * @link: the USB link, set up with endpoints, descriptors matching + * current device speed, and any framing wrapper(s) set up. + * @netname: name for network device (for example, "usb") + * Context: irqs blocked + * @netif_enable: if true, net interface will be turned on + * + * This is called to let the network layer know the connection + * is active ("carrier detect"). + */ +struct net_device *gether_qc_connect_name(struct qc_gether *link, + const char *netname, bool netif_enable) +{ + struct net_device *net_dev; + struct eth_qc_dev *dev; + + /* Extract the eth_qc_dev from the net device */ + net_dev = dev_get_by_name(&init_net, netname); + if (!net_dev) + return ERR_PTR(-EINVAL); + + dev_put(net_dev); + dev = netdev_priv(net_dev); + + if (!dev) + return ERR_PTR(-EINVAL); + + dev->zlp = link->is_zlp_ok; + dev->header_len = link->header_len; + + spin_lock(&dev->lock); + dev->port_usb = link; + link->ioport = dev; + if (netif_running(dev->net)) { + if (link->open) + link->open(link); + } else { + if (link->close) + link->close(link); + } + spin_unlock(&dev->lock); + + if (netif_enable) { + netif_carrier_on(dev->net); + if (netif_running(dev->net)) + netif_wake_queue(dev->net); + } + + return dev->net; +} + +/** + * gether_qc_disconnect_name - notify network layer that USB + * link is inactive + * @link: the USB link, on which gether_connect() was called + * @netname: name for network device (for example, "usb") + * Context: irqs blocked + * + * This is called to let the network layer know the connection + * went inactive ("no carrier"). + * + * On return, the state is as if gether_connect() had never been called. + */ +void gether_qc_disconnect_name(struct qc_gether *link, const char *netname) +{ + struct net_device *net_dev; + struct eth_qc_dev *dev; + + /* Extract the eth_qc_dev from the net device */ + net_dev = dev_get_by_name(&init_net, netname); + if (!net_dev) + return; + + dev_put(net_dev); + dev = netdev_priv(net_dev); + + if (!dev) + return; + + DBG(dev, "%s\n", __func__); + + netif_stop_queue(dev->net); + netif_carrier_off(dev->net); + + spin_lock(&dev->lock); + dev->port_usb = NULL; + link->ioport = NULL; + spin_unlock(&dev->lock); +} diff --git a/drivers/usb/gadget/function/u_qc_ether.h b/drivers/usb/gadget/function/u_qc_ether.h new file mode 100644 index 000000000000..c5706edf8d2f --- /dev/null +++ b/drivers/usb/gadget/function/u_qc_ether.h @@ -0,0 +1,101 @@ +/* + * u_qc_ether.h -- interface to USB gadget "ethernet link" utilities + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * Copyright (c) 2012-2014, 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 + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __U_QC_ETHER_H +#define __U_QC_ETHER_H + +#include +#include +#include +#include + +#include "gadget_chips.h" + + +/* + * This represents the USB side of an "ethernet" link, managed by a USB + * function which provides control and (maybe) framing. Two functions + * in different configurations could share the same ethernet link/netdev, + * using different host interaction models. + * + * There is a current limitation that only one instance of this link may + * be present in any given configuration. When that's a problem, network + * layer facilities can be used to package multiple logical links on this + * single "physical" one. + * + * This function is based on Ethernet-over-USB link layer utilities and + * contains MSM specific implementation. + */ + +struct qc_gether { + struct usb_function func; + + /* updated by gether_{connect,disconnect} */ + struct eth_qc_dev *ioport; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *in_ep; + struct usb_ep *out_ep; + + bool is_zlp_ok; + + u16 cdc_filter; + + /* hooks for added framing, as needed for RNDIS and EEM. */ + u32 header_len; + + struct sk_buff *(*wrap)(struct qc_gether *port, + struct sk_buff *skb); + int (*unwrap)(struct qc_gether *port, + struct sk_buff *skb, + struct sk_buff_head *list); + + /* called on network open/close */ + void (*open)(struct qc_gether *); + void (*close)(struct qc_gether *); +}; + +/* netdev setup/teardown as directed by the gadget driver */ +int gether_qc_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]); +void gether_qc_cleanup_name(const char *netname); +/* variant of gether_setup that allows customizing network device name */ +int gether_qc_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname); + +/* connect/disconnect is handled by individual functions */ +struct net_device *gether_qc_connect_name(struct qc_gether *link, + const char *netname, bool netif_enable); +struct net_device *gether_qc_get_net(const char *netname); +void gether_qc_disconnect_name(struct qc_gether *link, const char *netname); + +/* each configuration may bind one instance of an ethernet link */ +int ecm_qc_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + char *xport_name); + +int +rndis_qc_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer, + u8 maxPktPerXfer, u8 pkt_alignment_factor, + char *xport_name); + +void gether_qc_get_macs(u8 dev_mac[ETH_ALEN], u8 host_mac[ETH_ALEN]); + +#endif /* __U_QC_ETHER_H */ diff --git a/drivers/usb/gadget/function/u_rmnet.h b/drivers/usb/gadget/function/u_rmnet.h new file mode 100644 index 000000000000..4336dbf26274 --- /dev/null +++ b/drivers/usb/gadget/function/u_rmnet.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2011-2015, 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 __U_RMNET_H +#define __U_RMNET_H + +#include +#include +#include +#include + +struct rmnet_ctrl_pkt { + void *buf; + int len; + struct list_head list; +}; + +struct grmnet { + struct usb_function func; + + struct usb_gadget *gadget; + struct usb_ep *in; + struct usb_ep *out; + + /* to usb host, aka laptop, windows pc etc. Will + * be filled by usb driver of rmnet functionality + */ + int (*send_cpkt_response)(void *g, void *buf, size_t len); + + /* to modem, and to be filled by driver implementing + * control function + */ + int (*send_encap_cmd)(u8 port_num, void *buf, size_t len); + + void (*notify_modem)(void *g, u8 port_num, int cbits); + + void (*disconnect)(struct grmnet *g); + void (*connect)(struct grmnet *g); +}; + +#define NR_QTI_PORTS (NR_RMNET_PORTS + NR_DPL_PORTS) +#define NR_RMNET_PORTS 4 +#define NR_DPL_PORTS 1 + +enum ctrl_client { + FRMNET_CTRL_CLIENT, + GPS_CTRL_CLIENT, + + NR_CTRL_CLIENTS +}; + +int gbam_setup(unsigned int no_bam_port); +int gbam2bam_setup(unsigned int no_bam2bam_port); +void gbam_cleanup(void); +int gbam_connect(struct grmnet *gr, u8 port_num, + enum transport_type trans, u8 src_connection_idx, + u8 dst_connection_idx); +void gbam_disconnect(struct grmnet *gr, u8 port_num, + enum transport_type trans); +void gbam_suspend(struct grmnet *gr, u8 port_num, enum transport_type trans); +void gbam_resume(struct grmnet *gr, u8 port_num, enum transport_type trans); +int gbam_mbim_setup(void); +int gbam_mbim_connect(struct usb_gadget *g, struct usb_ep *in, + struct usb_ep *out); +void gbam_mbim_disconnect(void); +int gsmd_ctrl_connect(struct grmnet *gr, int port_num); +void gsmd_ctrl_disconnect(struct grmnet *gr, u8 port_num); +int gsmd_ctrl_setup(enum ctrl_client client_num, unsigned int count, + u8 *first_port_idx); +#endif /* __U_RMNET_H*/