/* Copyright (c) 2012-2018, 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 <linux/kernel.h>
#include <linux/device.h>

#include <linux/usb/cdc.h>

#include <linux/usb/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 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 delayed_work	rwake_work;
};

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);
}

static void mbim_remote_wakeup_work(struct work_struct *w)
{
	struct f_mbim	*mbim = container_of(w, struct f_mbim, rwake_work.work);
	int		ret = 0;

	if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) &&
			!mbim->function.func_is_suspended){
		pr_debug("%s: resume in progress\n", __func__);
		return;
	}

	if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) &&
			mbim->function.func_is_suspended)
		ret = usb_func_wakeup(&mbim->function);
	else
		ret = usb_gadget_wakeup(mbim->cdev->gadget);

	if (ret == -EBUSY || ret == -EAGAIN)
		pr_debug("%s:RW delayed due to LPM exit %d\n",  __func__, ret);
	else
		pr_info("%s: remote wake-up failed: %d\n", __func__, ret);
}

/*
 * 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) {
			/* ignore if request already queued before bus_resume */
			if (status != -EBUSY)
				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:%pK\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;
	}

	spin_unlock(&mbim->lock);

	pr_debug("dev:%pK 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:%pK\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:%pK 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:%pK 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 void mbim_response_complete(struct usb_ep *ep, struct usb_request *req)
{
	struct f_mbim *mbim = req->context;

	pr_debug("%s: queue notify request if any new response available\n"
			, __func__);
	spin_lock(&mbim->lock);
	mbim_do_notify(mbim);
	spin_unlock(&mbim->lock);
}

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);

		req->complete = mbim_response_complete;
		req->context = mbim;
		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%pK\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%pK\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");

	cancel_delayed_work(&mbim->rwake_work);
	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);

	if (mbim->remote_wakeup_enabled &&
			atomic_read(&mbim->not_port.notify_count) > 0) {
		pr_info("%s: pending notification, wakeup host\n", __func__);
		schedule_delayed_work(&mbim->rwake_work,
				      msecs_to_jiffies(2000));
	}
}

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;

	cancel_delayed_work(&mbim->rwake_work);

	/* 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);
	INIT_DELAYED_WORK(&mbim->rwake_work, mbim_remote_wakeup_work);

	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 %pK req %pK req->buf %pK\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:%pK 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 && ret != -EBUSY)) {
		spin_lock_irqsave(&dev->lock, flags);
		/*
		 * cpkt already freed if device disconnected while we
		 * dropped lock. Nothing to be done in that case.
		 */
		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");
			goto fail;
		}

		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;
	}

fail:
	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;
}

