usb: gadget: MBIM function driver for QCOM chipsets
This driver implements Mobile Broadband Interface Model protocol.
Control messages are transferred through QBI module
running in user space and communicating with mbim
using file operations interface android_mbim.
Data is transferred on SPS, Bam-to-Bam.
Change-Id: Ifc4b3f7e21b1a0dac377af272f9ae922ec9c7e2e
Signed-off-by: Anna Perel <aperel@codeaurora.org>
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
index 3e5c6f1..3fb9c83 100644
--- a/drivers/usb/gadget/android.c
+++ b/drivers/usb/gadget/android.c
@@ -70,6 +70,8 @@
#include "f_rndis.c"
#include "rndis.c"
#include "u_ether.c"
+#include "u_bam_data.c"
+#include "f_mbim.c"
MODULE_AUTHOR("Mike Lockwood");
MODULE_DESCRIPTION("Android Composite USB Driver");
@@ -386,6 +388,35 @@
.attributes = rmnet_function_attributes,
};
+
+/* MBIM - used with BAM */
+#define MAX_MBIM_INSTANCES 1
+
+static int mbim_function_init(struct android_usb_function *f,
+ struct usb_composite_dev *cdev)
+{
+ return mbim_init(MAX_MBIM_INSTANCES);
+}
+
+static void mbim_function_cleanup(struct android_usb_function *f)
+{
+ fmbim_cleanup();
+}
+
+static int mbim_function_bind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ return mbim_bind_config(c, 0);
+}
+
+static struct android_usb_function mbim_function = {
+ .name = "usb_mbim",
+ .cleanup = mbim_function_cleanup,
+ .bind_config = mbim_function_bind_config,
+ .init = mbim_function_init,
+};
+
+
/* DIAG */
static char diag_clients[32]; /*enabled DIAG clients- "diag[,diag_mdm]" */
static ssize_t clients_store(
@@ -1004,6 +1035,7 @@
static struct android_usb_function *supported_functions[] = {
+ &mbim_function,
&rmnet_smd_function,
&rmnet_sdio_function,
&rmnet_smd_sdio_function,
diff --git a/drivers/usb/gadget/f_mbim.c b/drivers/usb/gadget/f_mbim.c
new file mode 100644
index 0000000..8288496
--- /dev/null
+++ b/drivers/usb/gadget/f_mbim.c
@@ -0,0 +1,1772 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/kernel.h>
+#include <linux/device.h>
+
+#include <linux/usb/cdc.h>
+
+#include <linux/usb/composite.h>
+#include <linux/usb/android_composite.h>
+#include <linux/platform_device.h>
+
+#include <linux/spinlock.h>
+
+/*
+ * 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 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 NR_MBIM_PORTS 1
+
+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 {
+ NCM_NOTIFY_NONE,
+ NCM_NOTIFY_CONNECT,
+ NCM_NOTIFY_SPEED,
+};
+
+struct f_mbim {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+
+ atomic_t online;
+ bool is_open;
+
+ atomic_t open_excl;
+ atomic_t ioctl_excl;
+ atomic_t read_excl;
+ atomic_t write_excl;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+
+ 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;
+
+ struct ndp_parser_opts *parser_opts;
+
+ spinlock_t lock;
+
+ struct list_head cpkt_req_q;
+ struct list_head cpkt_resp_q;
+
+ u32 ntb_input_size;
+ u16 ntb_max_datagrams;
+
+ pid_t user_pid;
+
+ atomic_t error;
+};
+
+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);
+}
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static inline unsigned mbim_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;
+}
+
+/*-------------------------------------------------------------------------*/
+
+#define NTB_DEFAULT_IN_SIZE (0x4000)
+#define NTB_OUT_SIZE (0x1000)
+#define NDP_IN_DIVISOR (0x4)
+
+#define FORMATS_SUPPORTED USB_CDC_NCM_NTB16_SUPPORTED
+
+static struct usb_cdc_ncm_ntb_parameters ntb_parameters = {
+ .wLength = sizeof ntb_parameters,
+ .bmNtbFormatsSupported = cpu_to_le16(FORMATS_SUPPORTED),
+ .dwNtbInMaxSize = cpu_to_le32(NTB_DEFAULT_IN_SIZE),
+ .wNdpInDivisor = cpu_to_le16(NDP_IN_DIVISOR),
+ .wNdpInPayloadRemainder = cpu_to_le16(0),
+ .wNdpInAlignment = cpu_to_le16(4),
+
+ .dwNtbOutMaxSize = cpu_to_le32(NTB_OUT_SIZE),
+ .wNdpOutDivisor = cpu_to_le16(4),
+ .wNdpOutPayloadRemainder = cpu_to_le16(0),
+ .wNdpOutAlignment = cpu_to_le16(4),
+ .wNtbOutMaxDatagrams = cpu_to_le16(4),
+};
+
+/*
+ * 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_mbb_desc mbb_desc = {
+ .bLength = sizeof mbb_desc,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_MBB_TYPE,
+
+ .bcdMbbVersion = cpu_to_le16(0x0100),
+
+ .wMaxControlMessage = cpu_to_le16(0x1000),
+ .bNumberFilters = 0x10,
+ .bMaxFilterSize = 0x80,
+ .wMaxSegmentSize = cpu_to_le16(0x1000),
+ .bmNetworkCapabilities = 0x20,
+};
+
+/* 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 *) &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 *) &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,
+};
+
+/* 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,
+};
+
+/*
+ * Here are options for the Datagram Pointer table (NDP) parser.
+ * There are 2 different formats: NDP16 and NDP32 in the spec (ch. 3),
+ * in NDP16 offsets and sizes fields are 1 16bit word wide,
+ * in NDP32 -- 2 16bit words wide. Also signatures are different.
+ * To make the parser code the same, put the differences in the structure,
+ * and switch pointers to the structures when the format is changed.
+ */
+
+struct ndp_parser_opts {
+ u32 nth_sign;
+ u32 ndp_sign;
+ unsigned nth_size;
+ unsigned ndp_size;
+ unsigned ndplen_align;
+ /* sizes in u16 units */
+ unsigned dgram_item_len; /* index or length */
+ unsigned block_length;
+ unsigned fp_index;
+ unsigned reserved1;
+ unsigned reserved2;
+ unsigned next_fp_index;
+};
+
+#define INIT_NDP16_OPTS { \
+ .nth_sign = USB_CDC_NCM_NTH16_SIGN, \
+ .ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN, \
+ .nth_size = sizeof(struct usb_cdc_ncm_nth16), \
+ .ndp_size = sizeof(struct usb_cdc_ncm_ndp16), \
+ .ndplen_align = 4, \
+ .dgram_item_len = 1, \
+ .block_length = 1, \
+ .fp_index = 1, \
+ .reserved1 = 0, \
+ .reserved2 = 0, \
+ .next_fp_index = 1, \
+}
+
+#define INIT_NDP32_OPTS { \
+ .nth_sign = USB_CDC_NCM_NTH32_SIGN, \
+ .ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN, \
+ .nth_size = sizeof(struct usb_cdc_ncm_nth32), \
+ .ndp_size = sizeof(struct usb_cdc_ncm_ndp32), \
+ .ndplen_align = 8, \
+ .dgram_item_len = 2, \
+ .block_length = 2, \
+ .fp_index = 2, \
+ .reserved1 = 1, \
+ .reserved2 = 2, \
+ .next_fp_index = 2, \
+}
+
+static struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS;
+static struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS;
+
+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)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ req->buf = kmalloc(buffer_size, 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);
+ }
+}
+
+static void fmbim_ctrl_response_available(struct f_mbim *dev)
+{
+ struct usb_request *req = dev->not_port.notify_req;
+ struct usb_cdc_notification *event = NULL;
+ unsigned long flags;
+ int ret;
+
+ int notif_c = 0;
+
+ pr_info("dev:%p portno#%d\n", dev, dev->port_num);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (!atomic_read(&dev->online)) {
+ pr_info("dev:%p is not online\n", dev);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return;
+ }
+
+ if (!req) {
+ pr_info("dev:%p req is NULL\n", dev);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return;
+ }
+
+ if (!req->buf) {
+ pr_info("dev:%p req->buf is NULL\n", dev);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return;
+ }
+
+ notif_c = atomic_inc_return(&dev->not_port.notify_count);
+ pr_info("atomic_inc_return[notif_c] = %d", notif_c);
+
+ 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->ctrl_id);
+ event->wLength = cpu_to_le16(0);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ pr_info("Call usb_ep_queue");
+
+ ret = usb_ep_queue(dev->not_port.notify,
+ dev->not_port.notify_req, GFP_ATOMIC);
+ if (ret) {
+ atomic_dec(&dev->not_port.notify_count);
+ pr_err("ep enqueue error %d\n", ret);
+ }
+
+ pr_info("Succcessfull Exit");
+}
+
+static int
+fmbim_send_cpkt_response(struct f_mbim *gr, struct ctrl_pkt *cpkt)
+{
+ struct f_mbim *dev = gr;
+ unsigned long flags;
+
+ if (!gr || !cpkt) {
+ pr_err("Invalid cpkt, dev:%p cpkt:%p\n",
+ gr, cpkt);
+ return -ENODEV;
+ }
+
+ pr_info("dev:%p port_num#%d\n", dev, dev->port_num);
+
+ if (!atomic_read(&dev->online)) {
+ pr_info("dev:%p is not connected\n", dev);
+ mbim_free_ctrl_pkt(cpkt);
+ return 0;
+ }
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add(&cpkt->list, &dev->cpkt_resp_q);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ fmbim_ctrl_response_available(dev);
+
+ return 0;
+}
+
+/* ---------------------------- BAM INTERFACE ----------------------------- */
+
+static int mbim_bam_setup(int no_ports)
+{
+ int ret;
+
+ pr_info("no_ports:%d\n", no_ports);
+
+ ret = bam_data_setup(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 int mbim_bam_connect(struct f_mbim *dev)
+{
+ int ret;
+
+ pr_info("dev:%p portno:%d\n", dev, dev->port_num);
+
+ ret = bam_data_connect(&dev->bam_port, dev->port_num, dev->port_num);
+ if (ret) {
+ pr_err("bam_data_setup failed: err:%d\n",
+ ret);
+ return ret;
+ } else {
+ pr_info("mbim bam connected\n");
+ }
+
+ return 0;
+}
+
+static int mbim_bam_disconnect(struct f_mbim *dev)
+{
+ pr_info("dev:%p port:%d. Do nothing.\n",
+ dev, dev->port_num);
+
+ /* bam_data_disconnect(&dev->bam_port, dev->port_num); */
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------------*/
+
+static inline void mbim_reset_values(struct f_mbim *mbim)
+{
+ mbim->parser_opts = &ndp16_opts;
+
+ mbim->ntb_input_size = NTB_DEFAULT_IN_SIZE;
+
+ atomic_set(&mbim->not_port.notify_count, 0);
+ atomic_set(&mbim->online, 0);
+}
+
+/*
+ * 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;
+ struct usb_composite_dev *cdev = mbim->cdev;
+ __le32 *data;
+ int status;
+
+ pr_info("notify_state: %d", mbim->not_port.notify_state);
+
+ if (!req)
+ return;
+
+ event = req->buf;
+
+ switch (mbim->not_port.notify_state) {
+
+ case NCM_NOTIFY_NONE:
+ return;
+
+ case NCM_NOTIFY_CONNECT:
+ event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+ if (mbim->is_open)
+ event->wValue = cpu_to_le16(1);
+ else
+ event->wValue = cpu_to_le16(0);
+ event->wLength = 0;
+ req->length = sizeof *event;
+
+ pr_info("notify connect %s\n",
+ mbim->is_open ? "true" : "false");
+ mbim->not_port.notify_state = NCM_NOTIFY_NONE;
+ break;
+
+ case NCM_NOTIFY_SPEED:
+ event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
+ event->wValue = cpu_to_le16(0);
+ event->wLength = cpu_to_le16(8);
+ req->length = NCM_STATUS_BYTECOUNT;
+
+ /* SPEED_CHANGE data is up/down speeds in bits/sec */
+ data = req->buf + sizeof *event;
+ data[0] = cpu_to_le32(mbim_bitrate(cdev->gadget));
+ data[1] = data[0];
+
+ pr_info("notify speed %d\n",
+ mbim_bitrate(cdev->gadget));
+ mbim->not_port.notify_state = NCM_NOTIFY_CONNECT;
+ break;
+ }
+ event->bmRequestType = 0xA1;
+ event->wIndex = cpu_to_le16(mbim->ctrl_id);
+
+ mbim->not_port.notify_req = NULL;
+ /*
+ * In double buffering if there is a space in FIFO,
+ * completion callback can be called right after the call,
+ * so unlocking
+ */
+ spin_unlock(&mbim->lock);
+ status = usb_ep_queue(mbim->not_port.notify, req, GFP_ATOMIC);
+ spin_lock(&mbim->lock);
+ if (status < 0) {
+ mbim->not_port.notify_req = req;
+ atomic_dec(&mbim->not_port.notify_count);
+ pr_err("usb_ep_queue failed, err: %d", status);
+ }
+}
+
+/*
+ * Context: mbim->lock held
+ */
+static void mbim_notify(struct f_mbim *mbim)
+{
+ /*
+ * If mbim_notify() is called before the second (CONNECT)
+ * notification is sent, then it will reset to send the SPEED
+ * notificaion again (and again, and again), but it's not a problem
+ */
+ pr_info("dev:%p\n", mbim);
+
+ mbim->not_port.notify_state = NCM_NOTIFY_SPEED;
+ mbim_do_notify(mbim);
+}
+
+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;
+
+ int notif_c = 0;
+
+ pr_info("dev:%p\n", mbim);
+
+ spin_lock(&mbim->lock);
+ switch (req->status) {
+ case 0:
+ pr_info("Notification %02x sent\n",
+ event->bNotificationType);
+
+ notif_c = atomic_dec_return(&mbim->not_port.notify_count);
+
+ if (notif_c != 0) {
+ pr_info("Continue to mbim_do_notify()");
+ break;
+ } else {
+ pr_info("notify_count decreased to 0. Do not notify");
+ spin_unlock(&mbim->lock);
+ return;
+ }
+
+ break;
+
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ /* connection gone */
+ mbim->not_port.notify_state = NCM_NOTIFY_NONE;
+ atomic_set(&mbim->not_port.notify_count, 0);
+ pr_info("ESHUTDOWN/ECONNRESET, connection gone");
+ break;
+ default:
+ pr_err("Unknown event %02x --> %d\n",
+ event->bNotificationType, req->status);
+ break;
+ }
+
+ mbim->not_port.notify_req = req;
+ mbim_do_notify(mbim);
+
+ spin_unlock(&mbim->lock);
+
+ pr_info("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_info("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(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(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_info("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;
+
+ if (!dev) {
+ pr_err("mbim dev is null\n");
+ return;
+ }
+
+ if (req->status < 0) {
+ pr_err("mbim command error %d\n", req->status);
+ return;
+ }
+
+ pr_info("dev:%p port#%d\n", dev, dev->port_num);
+
+ spin_lock(&dev->lock);
+ if (!dev->is_open) {
+ pr_err("mbim file handler %p is not open", dev);
+ spin_unlock(&dev->lock);
+ return;
+ }
+
+ cpkt = mbim_alloc_ctrl_pkt(len, GFP_ATOMIC);
+ if (!cpkt) {
+ pr_err("Unable to allocate ctrl pkt\n");
+ spin_unlock(&dev->lock);
+ return;
+ }
+
+ pr_info("Add to cpkt_req_q packet with len = %d\n", len);
+ memcpy(cpkt->buf, req->buf, len);
+ list_add_tail(&cpkt->list, &dev->cpkt_req_q);
+ spin_unlock(&dev->lock);
+
+ /* wakeup read thread */
+ pr_info("Wake up read queue");
+ 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_info("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_info("USB_CDC_RESET_FUNCTION");
+ value = 0;
+ if (!_mbim_dev->user_pid) {
+ pr_err("QBI pid is not set");
+ break;
+ }
+
+ if (!_mbim_dev->is_open) {
+ pr_err("QBI is not up yet");
+ break;
+ }
+
+ send_sig_info(SIGUSR1, SEND_SIG_NOINFO,
+ find_task_by_vpid(_mbim_dev->user_pid));
+
+ pr_info("Sent signal to QBI pid %d",
+ _mbim_dev->user_pid);
+ break;
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SEND_ENCAPSULATED_COMMAND:
+
+ pr_info("USB_CDC_SEND_ENCAPSULATED_COMMAND");
+
+ if (w_length > req->length) {
+ pr_err("w_length > req->length: %d > %d",
+ 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_info("USB_CDC_GET_ENCAPSULATED_RESPONSE");
+
+ if (w_value) {
+ pr_err("w_length > 0: %d", w_length);
+ break;
+ }
+
+ pr_info("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_info("copied encapsulated_response %d bytes",
+ value);
+
+ break;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_NTB_PARAMETERS:
+
+ pr_info("USB_CDC_GET_NTB_PARAMETERS");
+
+ if (w_length == 0 || w_value != 0 || w_index != mbim->ctrl_id)
+ break;
+
+ value = w_length > sizeof ntb_parameters ?
+ sizeof ntb_parameters : w_length;
+ memcpy(req->buf, &ntb_parameters, value);
+ break;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_NTB_INPUT_SIZE:
+
+ pr_info("USB_CDC_GET_NTB_INPUT_SIZE");
+
+ 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_info("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_info("USB_CDC_SET_NTB_INPUT_SIZE");
+
+ if (w_length != 4 && w_length != 8) {
+ pr_err("wrong NTB length %d", 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;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_NTB_FORMAT:
+ {
+ uint16_t format;
+
+ pr_info("USB_CDC_GET_NTB_FORMAT");
+
+ if (w_length < 2 || w_value != 0 || w_index != mbim->ctrl_id)
+ break;
+
+ format = (mbim->parser_opts == &ndp16_opts) ? 0x0000 : 0x0001;
+ put_unaligned_le16(format, req->buf);
+ value = 2;
+ pr_info("NTB FORMAT: sending %d\n", format);
+ break;
+ }
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SET_NTB_FORMAT:
+ {
+ pr_info("USB_CDC_SET_NTB_FORMAT");
+
+ if (w_length != 0 || w_index != mbim->ctrl_id)
+ break;
+ switch (w_value) {
+ case 0x0000:
+ mbim->parser_opts = &ndp16_opts;
+ pr_info("NCM16 selected\n");
+ break;
+ case 0x0001:
+ mbim->parser_opts = &ndp32_opts;
+ pr_info("NCM32 selected\n");
+ break;
+ default:
+ break;
+ }
+ value = 0;
+ 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_info("control request: %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("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;
+}
+
+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;
+
+ /* Control interface has only altsetting 0 */
+ if (intf == mbim->ctrl_id) {
+
+ pr_info("CONTROL_INTERFACE");
+
+ 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");
+
+ if (alt > 1)
+ goto fail;
+
+ if (mbim->bam_port.in->driver_data) {
+ pr_info("reset mbim\n");
+ mbim_reset_values(mbim);
+ mbim_bam_disconnect(mbim);
+ }
+
+ /*
+ * CDC Network only sends data in non-default altsettings.
+ * Changing altsettings resets filters, statistics, etc.
+ */
+ if (alt == 1) {
+ pr_info("Alt set 1, initialize ports");
+
+ if (!mbim->bam_port.in->desc) {
+
+ pr_info("Choose endpoints");
+
+ 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",
+ 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",
+ mbim->bam_port.out->desc);
+ } else {
+ pr_info("PORTS already SET");
+ }
+
+ pr_info("Activate mbim\n");
+ mbim_bam_connect(mbim);
+ }
+
+ spin_lock(&mbim->lock);
+ mbim_notify(mbim);
+ spin_unlock(&mbim->lock);
+ } else {
+ goto fail;
+ }
+
+ atomic_set(&mbim->online, 1);
+
+ pr_info("SET DEVICE ONLINE");
+
+ /* wakeup file threads */
+ wake_up(&mbim->read_wq);
+ wake_up(&mbim->write_wq);
+
+ return 0;
+
+fail:
+ pr_err("ERROR: Illegal Interface");
+ 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;
+ return mbim->bam_port.in->driver_data ? 1 : 0;
+}
+
+static void mbim_disable(struct usb_function *f)
+{
+ struct f_mbim *mbim = func_to_mbim(f);
+
+ pr_info("SET DEVICE OFFLINE");
+ atomic_set(&mbim->online, 0);
+
+ if (_mbim_dev && _mbim_dev->user_pid && _mbim_dev->is_open) {
+ send_sig_info(SIGUSR1, SEND_SIG_NOINFO,
+ find_task_by_vpid(_mbim_dev->user_pid));
+ pr_info("Sending reset signal to QBI pid %d",
+ _mbim_dev->user_pid);
+ }
+
+ mbim_bam_disconnect(mbim);
+
+ if (mbim->not_port.notify->driver_data) {
+ usb_ep_disable(mbim->not_port.notify);
+ mbim->not_port.notify->driver_data = NULL;
+ }
+
+ pr_info("mbim deactivated\n");
+}
+
+/*---------------------- 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;
+
+ pr_info("Enter");
+
+ 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_nop_intf.bInterfaceNumber = status;
+ mbim_data_intf.bInterfaceNumber = status;
+ mbim_union_desc.bSlaveInterface0 = status;
+
+ 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);
+ 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;
+
+ /* copy descriptors, and track endpoint copies */
+ f->descriptors = usb_copy_descriptors(mbim_fs_function);
+ if (!f->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;
+ }
+
+ 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->descriptors)
+ usb_free_descriptors(f->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);
+
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->descriptors);
+
+ kfree(mbim->not_port.notify_req->buf);
+ usb_ep_free_request(mbim->not_port.notify, mbim->not_port.notify_req);
+}
+
+/**
+ * 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)
+{
+ struct f_mbim *mbim = NULL;
+ int status = 0;
+
+ pr_info("port number %u", portno);
+
+ if (portno >= nr_mbim_ports) {
+ pr_err("Can not add port %u. Max ports = %d",
+ portno, nr_mbim_ports);
+ return -ENODEV;
+ }
+
+ status = mbim_bam_setup(nr_mbim_ports);
+ if (status) {
+ pr_err("bam setup failed");
+ 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;
+ }
+
+ /* allocate and initialize one new instance */
+ mbim = mbim_ports[0].port;
+ if (!mbim) {
+ pr_info("mbim struct not allocated");
+ return -ENOMEM;
+ }
+
+ mbim->cdev = c->cdev;
+
+ spin_lock_init(&mbim->lock);
+
+ 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;
+
+ 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", 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;
+ int ret = 0;
+
+ pr_debug("Enter(%d)\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 %d, 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;
+ }
+
+ /* block until mbim online */
+ while (!(atomic_read(&dev->online) || atomic_read(&dev->error))) {
+ pr_err("USB cable not connected. Wait.\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ (atomic_read(&dev->online) ||
+ atomic_read(&dev->error)));
+ if (ret < 0) {
+ mbim_unlock(&dev->read_excl);
+ return 0;
+ }
+ }
+
+ if (atomic_read(&dev->error)) {
+ mbim_unlock(&dev->read_excl);
+ return -EIO;
+ }
+
+ while (list_empty(&dev->cpkt_req_q)) {
+ pr_err("Requests list is empty. Wait.\n");
+ 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 0;
+ }
+ pr_debug("Received request packet\n");
+ }
+
+ cpkt = list_first_entry(&dev->cpkt_req_q, struct ctrl_pkt,
+ list);
+ if (cpkt->len > count) {
+ mbim_unlock(&dev->read_excl);
+ pr_err("cpkt size too big:%d > buf size:%d\n",
+ cpkt->len, count);
+ return -ENOMEM;
+ }
+
+ pr_debug("cpkt size:%d\n", cpkt->len);
+
+ list_del(&cpkt->list);
+ 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 = 0;
+ } 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;
+ int ret = 0;
+
+ pr_debug("Enter(%d)", count);
+
+ if (!dev) {
+ pr_err("Received NULL mbim pointer\n");
+ return -ENODEV;
+ }
+
+ if (!count) {
+ pr_err("zero length ctrl pkt\n");
+ return -ENODEV;
+ }
+
+ if (count > MAX_CTRL_PKT_SIZE) {
+ pr_err("given pkt size too big:%d > max_pkt_size:%d\n",
+ count, MAX_CTRL_PKT_SIZE);
+ return -ENOMEM;
+ }
+
+ 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;
+ }
+
+ 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 0;
+ }
+
+ fmbim_send_cpkt_response(dev, cpkt);
+
+ mbim_unlock(&dev->write_excl);
+
+ pr_debug("Exit(%d)", count);
+
+ return 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");
+
+ pr_info("Set QBI pid %d\n", pid_nr(task_pid(current)));
+ _mbim_dev->user_pid = pid_nr(task_pid(current));
+
+ fp->private_data = _mbim_dev;
+
+ atomic_set(&_mbim_dev->error, 0);
+
+ spin_lock(&_mbim_dev->lock);
+ _mbim_dev->is_open = true;
+ mbim_notify(_mbim_dev);
+ spin_unlock(&_mbim_dev->lock);
+
+ pr_info("Exit, mbim file opened\n");
+
+ return 0;
+}
+
+static int mbim_release(struct inode *ip, struct file *fp)
+{
+ struct f_mbim *mbim = fp->private_data;
+
+ pr_info("Close mbim file");
+
+ spin_lock(&mbim->lock);
+ mbim->is_open = false;
+ mbim_notify(mbim);
+ spin_unlock(&mbim->lock);
+
+ mbim->user_pid = 0;
+
+ mbim_unlock(&_mbim_dev->open_excl);
+
+ return 0;
+}
+
+static long mbim_ioctl(struct file *fp, unsigned cmd, unsigned long arg)
+{
+ struct f_mbim *mbim = fp->private_data;
+ int ret = 0;
+
+ pr_info("Received command %d", cmd);
+
+ 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");
+ ret = -EFAULT;
+ }
+ pr_info("Sent NTB size %d", 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");
+ ret = -EFAULT;
+ }
+ pr_info("Sent NTB datagrams count %d",
+ mbim->ntb_max_datagrams);
+ break;
+ default:
+ pr_err("wrong parameter");
+ 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;
+ 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);
+ init_waitqueue_head(&dev->write_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");
+ goto fail_probe;
+ }
+
+ pr_info("Initialized %d ports\n", nr_mbim_ports);
+
+ return ret;
+
+fail_probe:
+ pr_err("Failed");
+ 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");
+
+ 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/u_bam_data.c b/drivers/usb/gadget/u_bam_data.c
new file mode 100644
index 0000000..73b4e75
--- /dev/null
+++ b/drivers/usb/gadget/u_bam_data.c
@@ -0,0 +1,328 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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 <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/bitops.h>
+#include <linux/usb/gadget.h>
+
+#include <mach/bam_dmux.h>
+#include <mach/usb_gadget_xport.h>
+#include <mach/usb_bam.h>
+
+#define BAM2BAM_DATA_N_PORTS 1
+
+static struct workqueue_struct *bam_data_wq;
+static int n_bam2bam_data_ports;
+
+#define SPS_PARAMS_SPS_MODE BIT(5)
+#define SPS_PARAMS_TBE BIT(6)
+#define MSM_VENDOR_ID BIT(16)
+
+struct data_port {
+ struct usb_function func;
+ struct usb_ep *in;
+ struct usb_ep *out;
+};
+
+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;
+
+ u8 src_pipe_idx;
+ u8 dst_pipe_idx;
+ u8 connection_idx;
+};
+
+struct bam_data_port {
+ unsigned port_num;
+ struct data_port *port_usb;
+ struct bam_data_ch_info data_ch;
+
+ struct work_struct connect_w;
+ struct work_struct disconnect_w;
+};
+
+struct bam_data_port *bam2bam_data_ports[BAM2BAM_DATA_N_PORTS];
+
+/*------------data_path----------------------------*/
+
+static void bam_data_endless_rx_complete(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ int status = req->status;
+
+ pr_info("status: %d\n", status);
+}
+
+static void bam_data_endless_tx_complete(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ int status = req->status;
+
+ pr_info("status: %d\n", status);
+}
+
+static void bam_data_start_endless_rx(struct bam_data_port *port)
+{
+ struct bam_data_ch_info *d = &port->data_ch;
+ int status;
+
+ status = usb_ep_queue(port->port_usb->out, 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;
+ int status;
+
+ status = usb_ep_queue(port->port_usb->in, d->tx_req, GFP_ATOMIC);
+ if (status)
+ pr_err("error enqueuing transfer, %d\n", status);
+}
+
+static void bam2bam_data_disconnect_work(struct work_struct *w)
+{
+ struct bam_data_port *port =
+ container_of(w, struct bam_data_port, disconnect_w);
+
+ pr_info("Enter");
+
+ /* disable endpoints */
+ if (!port->port_usb || !port->port_usb->out || !port->port_usb->in) {
+ pr_err("port_usb->out/in == NULL. Exit");
+ return;
+ }
+ usb_ep_disable(port->port_usb->out);
+ usb_ep_disable(port->port_usb->in);
+
+ port->port_usb->in->driver_data = NULL;
+ port->port_usb->out->driver_data = NULL;
+
+ port->port_usb = 0;
+
+ pr_info("Exit");
+}
+
+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 bam_data_ch_info *d = &port->data_ch;
+ u32 sps_params;
+ int ret;
+
+ pr_info("Enter");
+
+ ret = usb_bam_connect(d->connection_idx, &d->src_pipe_idx,
+ &d->dst_pipe_idx);
+ d->src_pipe_idx = 11;
+ d->dst_pipe_idx = 10;
+
+ if (ret) {
+ pr_err("usb_bam_connect failed: err:%d\n", ret);
+ return;
+ }
+
+ if (!port->port_usb) {
+ pr_err("port_usb is NULL");
+ return;
+ }
+
+ if (!port->port_usb->out) {
+ pr_err("port_usb->out (bulk out ep) is NULL");
+ return;
+ }
+
+ d->rx_req = usb_ep_alloc_request(port->port_usb->out, GFP_KERNEL);
+ if (!d->rx_req)
+ return;
+
+ d->rx_req->context = port;
+ d->rx_req->complete = bam_data_endless_rx_complete;
+ d->rx_req->length = 0;
+ sps_params = (SPS_PARAMS_SPS_MODE | d->src_pipe_idx |
+ MSM_VENDOR_ID) & ~SPS_PARAMS_TBE;
+ d->rx_req->udc_priv = sps_params;
+ d->tx_req = usb_ep_alloc_request(port->port_usb->in, GFP_KERNEL);
+ if (!d->tx_req)
+ return;
+
+ d->tx_req->context = port;
+ d->tx_req->complete = bam_data_endless_tx_complete;
+ d->tx_req->length = 0;
+ sps_params = (SPS_PARAMS_SPS_MODE | d->dst_pipe_idx |
+ MSM_VENDOR_ID) & ~SPS_PARAMS_TBE;
+ d->tx_req->udc_priv = sps_params;
+
+ /* queue in & out requests */
+ bam_data_start_endless_rx(port);
+ bam_data_start_endless_tx(port);
+
+ pr_info("Done\n");
+}
+
+static void bam2bam_data_port_free(int portno)
+{
+ kfree(bam2bam_data_ports[portno]);
+ bam2bam_data_ports[portno] = NULL;
+}
+
+static int bam2bam_data_port_alloc(int portno)
+{
+ struct bam_data_port *port = NULL;
+ struct bam_data_ch_info *d = NULL;
+
+ port = kzalloc(sizeof(struct bam_data_port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->port_num = portno;
+
+ INIT_WORK(&port->connect_w, bam2bam_data_connect_work);
+ INIT_WORK(&port->disconnect_w, bam2bam_data_disconnect_work);
+
+ /* data ch */
+ d = &port->data_ch;
+ d->port = port;
+ bam2bam_data_ports[portno] = port;
+
+ pr_info("port:%p portno:%d\n", port, portno);
+
+ return 0;
+}
+
+void bam_data_disconnect(struct data_port *gr, u8 port_num)
+{
+ struct bam_data_port *port;
+ struct bam_data_ch_info *d;
+
+ pr_info("dev:%p port#%d\n", gr, port_num);
+
+ if (port_num >= n_bam2bam_data_ports) {
+ pr_err("invalid bam2bam portno#%d\n", port_num);
+ return;
+ }
+
+ if (!gr) {
+ pr_err("mbim data port is null\n");
+ return;
+ }
+
+ port = bam2bam_data_ports[port_num];
+
+ d = &port->data_ch;
+ port->port_usb = gr;
+
+ queue_work(bam_data_wq, &port->disconnect_w);
+}
+
+int bam_data_connect(struct data_port *gr, u8 port_num,
+ u8 connection_idx)
+{
+ struct bam_data_port *port;
+ struct bam_data_ch_info *d;
+ int ret;
+
+ pr_info("dev:%p port#%d\n", gr, port_num);
+
+ if (port_num >= n_bam2bam_data_ports) {
+ pr_err("invalid portno#%d\n", port_num);
+ return -ENODEV;
+ }
+
+ if (!gr) {
+ pr_err("mbim data port is null\n");
+ return -ENODEV;
+ }
+
+ port = bam2bam_data_ports[port_num];
+
+ d = &port->data_ch;
+
+ ret = usb_ep_enable(gr->in);
+ if (ret) {
+ pr_err("usb_ep_enable failed eptype:IN ep:%p", gr->in);
+ return ret;
+ }
+ 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);
+ gr->in->driver_data = 0;
+ return ret;
+ }
+ gr->out->driver_data = port;
+
+ port->port_usb = gr;
+
+ d->connection_idx = connection_idx;
+
+ queue_work(bam_data_wq, &port->connect_w);
+
+ return 0;
+}
+
+int bam_data_setup(unsigned int no_bam2bam_port)
+{
+ int i;
+ int ret;
+
+ pr_info("requested %d BAM2BAM ports", no_bam2bam_port);
+
+ if (!no_bam2bam_port || no_bam2bam_port > BAM2BAM_DATA_N_PORTS) {
+ pr_err("Invalid num of ports count:%d\n", no_bam2bam_port);
+ return -EINVAL;
+ }
+
+ 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");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < no_bam2bam_port; i++) {
+ n_bam2bam_data_ports++;
+ ret = bam2bam_data_port_alloc(i);
+ if (ret) {
+ n_bam2bam_data_ports--;
+ pr_err("Failed to alloc port:%d\n", i);
+ goto free_bam_ports;
+ }
+ }
+
+ return 0;
+
+free_bam_ports:
+ for (i = 0; i < n_bam2bam_data_ports; i++)
+ bam2bam_data_port_free(i);
+ destroy_workqueue(bam_data_wq);
+
+ return ret;
+}
+
diff --git a/include/linux/usb/cdc.h b/include/linux/usb/cdc.h
index 81a9279..2b39f69 100644
--- a/include/linux/usb/cdc.h
+++ b/include/linux/usb/cdc.h
@@ -53,6 +53,7 @@
#define USB_CDC_DMM_TYPE 0x14
#define USB_CDC_OBEX_TYPE 0x15
#define USB_CDC_NCM_TYPE 0x1a
+#define USB_CDC_MBB_TYPE 0x1b /* mbb_desc */
/* "Header Functional Descriptor" from CDC spec 5.2.3.1 */
struct usb_cdc_header_desc {
@@ -187,6 +188,21 @@
__le16 bcdNcmVersion;
__u8 bmNetworkCapabilities;
} __attribute__ ((packed));
+
+/* "MBIM Functional Descriptor" */
+struct usb_cdc_mbb_desc {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+
+ __le16 bcdMbbVersion;
+ __le16 wMaxControlMessage;
+ __u8 bNumberFilters;
+ __u8 bMaxFilterSize;
+ __le16 wMaxSegmentSize;
+ __u8 bmNetworkCapabilities;
+} __packed;
+
/*-------------------------------------------------------------------------*/
/*
@@ -201,6 +217,7 @@
#define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00
#define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01
+#define USB_CDC_RESET_FUNCTION 0x05
#define USB_CDC_REQ_SET_LINE_CODING 0x20
#define USB_CDC_REQ_GET_LINE_CODING 0x21
#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22