Merge "defconfig: kona: enable CONFIG_QCOM_SECURE_BUFFER"
diff --git a/Documentation/devicetree/bindings/bus/mhi.txt b/Documentation/devicetree/bindings/bus/mhi.txt
index 837c33c..34dff22 100644
--- a/Documentation/devicetree/bindings/bus/mhi.txt
+++ b/Documentation/devicetree/bindings/bus/mhi.txt
@@ -291,11 +291,6 @@
   Value type: <string>
   Definition: Interface name to be given so clients can identify it
 
-- mhi,recycle-buf
-  Usage: optional
-  Value type: <bool>
-  Definition: Set true if interface support recycling buffers.
-
 - aliases
   Usage: required
   Value type: <string>
diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.txt b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
index b17087e..1a680cf 100644
--- a/Documentation/devicetree/bindings/iommu/arm,smmu.txt
+++ b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
@@ -127,12 +127,26 @@
 		  Some hardware may not have full support for atos debugging
 		  in tandem with other features like power collapse.
 
+-qcom,opt-out-tbu-halting:
+		  Allow certain TBUs to opt-out from being halted for the
+		  ATOS operation to proceed. Halting certain TBUs would cause
+		  considerable impact to the system such as deadlocks on demand.
+		  Such TBUs can be opted out to be halted from software.
+
 - qcom,deferred-regulator-disable-delay : The time delay for deferred regulator
                   disable in ms. In case of unmap call, regulator is
                   enabled/disabled. This may introduce additional delay. For
                   clients who do not detach, it's not possible to keep regulator
                   vote while smmu is attached. Type is <u32>.
 
+- qcom,min-iova-align:
+		  Some hardware revision might have the deep prefetch bug where
+		  invalid entries in the prefetch window would cause improper
+		  permissions to be cached for the valid entries in this window.
+		  Enable the workaround on such hardware by aligning the start
+		  and end of all mapped buffers to prefetch size boundary, which
+		  is defined by ARM_SMMU_MIN_IOVA_ALIGN.
+
 - clocks        : List of clocks to be used during SMMU register access. See
                   Documentation/devicetree/bindings/clock/clock-bindings.txt
                   for information about the format. For each clock specified
diff --git a/arch/arm64/boot/dts/qcom/kona-regulators.dtsi b/arch/arm64/boot/dts/qcom/kona-regulators.dtsi
index e2f931b..18a3235 100644
--- a/arch/arm64/boot/dts/qcom/kona-regulators.dtsi
+++ b/arch/arm64/boot/dts/qcom/kona-regulators.dtsi
@@ -8,6 +8,7 @@
 /* Stub regulators */
 / {
 	/* PM8150 S3 + S2 + S1 = VDD_CX supply */
+	VDD_CX_LEVEL_AO:
 	VDD_CX_LEVEL: S3A_LEVEL: pm8150_s3_level: regulator-pm8150-s3-level {
 		compatible = "qcom,stub-regulator";
 		regulator-name = "pm8150_s3_level";
diff --git a/arch/arm64/boot/dts/qcom/kona.dtsi b/arch/arm64/boot/dts/qcom/kona.dtsi
index e805f90..7362a6b 100644
--- a/arch/arm64/boot/dts/qcom/kona.dtsi
+++ b/arch/arm64/boot/dts/qcom/kona.dtsi
@@ -252,61 +252,73 @@
 		};
 
 		pil_camera_mem: pil_camera_region@86000000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86000000 0x0 0x500000>;
 		};
 
 		pil_wlan_fw_mem: pil_wlan_fw_region@86500000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86500000 0x0 0x100000>;
 		};
 
 		pil_ipa_fw_mem: pil_ipa_fw_region@86600000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86600000 0x0 0x10000>;
 		};
 
 		pil_ipa_gsi_mem: pil_ipa_gsi_region@86610000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86610000 0x0 0x5000>;
 		};
 
 		pil_gpu_mem: pil_gpu_region@86615000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86615000 0x0 0x2000>;
 		};
 
 		pil_npu_mem: pil_npu_region@86680000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86680000 0x0 0x80000>;
 		};
 
 		pil_video_mem: pil_video_region@86700000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86700000 0x0 0x500000>;
 		};
 
 		pil_cvp_mem: pil_cvp_region@86c00000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x86c00000 0x0 0x500000>;
 		};
 
 		pil_cdsp_mem: pil_cdsp_region@87100000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x87100000 0x0 0x800000>;
 		};
 
 		pil_slpi_mem: pil_slpi_region@87900000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x87900000 0x0 0x1400000>;
 		};
 
 		pil_adsp_mem: pil_adsp_region@88d00000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x88d00000 0x0 0x1a00000>;
 		};
 
 		pil_spss_mem: pil_spss_region@8a700000 {
+			compatible = "removed-dma-pool";
 			no-map;
 			reg = <0x0 0x8a700000 0x0 0x100000>;
 		};
@@ -362,6 +374,7 @@
 		compatible = "qcom,kona-llcc";
 		reg = <0x9200000 0x1d0000> , <0x9600000 0x50000>;
 		reg-names = "llcc_base", "llcc_broadcast_base";
+		interrupts = <GIC_SPI 582 IRQ_TYPE_LEVEL_HIGH>;
 	};
 
 	arch_timer: timer {
@@ -530,9 +543,13 @@
 		#clock-cells = <1>;
 	};
 
-	clock_gcc: qcom,gcc {
-		compatible = "qcom,dummycc";
-		clock-output-names = "gcc_clocks";
+	clock_gcc: qcom,gcc@100000 {
+		compatible = "qcom,gcc-kona";
+		reg = <0x100000 0x1f0000>;
+		reg-names = "cc_base";
+		vdd_cx-supply = <&VDD_CX_LEVEL>;
+		vdd_cx_ao-supply = <&VDD_CX_LEVEL_AO>;
+		vdd_mm-supply = <&VDD_MMCX_LEVEL>;
 		#clock-cells = <1>;
 		#reset-cells = <1>;
 	};
diff --git a/drivers/bus/mhi/devices/mhi_netdev.c b/drivers/bus/mhi/devices/mhi_netdev.c
index 6a5a5ff..6e65e46 100644
--- a/drivers/bus/mhi/devices/mhi_netdev.c
+++ b/drivers/bus/mhi/devices/mhi_netdev.c
@@ -93,18 +93,14 @@
 	spinlock_t rx_lock;
 	bool enabled;
 	rwlock_t pm_lock; /* state change lock */
-	int (*rx_queue)(struct mhi_netdev *mhi_netdev, gfp_t gfp_t);
 	struct work_struct alloc_work;
 	int wake;
 
-	struct sk_buff_head rx_allocated;
-
 	u32 mru;
 	const char *interface_name;
 	struct napi_struct napi;
 	struct net_device *ndev;
 	struct sk_buff *frag_skb;
-	bool recycle_buf;
 
 	struct mhi_stats stats;
 	struct dentry *dentry;
@@ -140,18 +136,6 @@
 	return protocol;
 }
 
-static void mhi_netdev_skb_destructor(struct sk_buff *skb)
-{
-	struct mhi_skb_priv *skb_priv = (struct mhi_skb_priv *)(skb->cb);
-	struct mhi_netdev *mhi_netdev = skb_priv->mhi_netdev;
-
-	skb->data = skb->head;
-	skb_reset_tail_pointer(skb);
-	skb->len = 0;
-	MHI_ASSERT(skb->data != skb_priv->buf, "incorrect buf");
-	skb_queue_tail(&mhi_netdev->rx_allocated, skb);
-}
-
 static int mhi_netdev_alloc_skb(struct mhi_netdev *mhi_netdev, gfp_t gfp_t)
 {
 	u32 cur_mru = mhi_netdev->mru;
@@ -180,9 +164,6 @@
 		skb_priv->mhi_netdev = mhi_netdev;
 		skb->dev = mhi_netdev->ndev;
 
-		if (mhi_netdev->recycle_buf)
-			skb->destructor = mhi_netdev_skb_destructor;
-
 		spin_lock_bh(&mhi_netdev->rx_lock);
 		ret = mhi_queue_transfer(mhi_dev, DMA_FROM_DEVICE, skb,
 					 skb_priv->size, MHI_EOT);
@@ -200,7 +181,6 @@
 	return 0;
 
 error_queue:
-	skb->destructor = NULL;
 	read_unlock_bh(&mhi_netdev->pm_lock);
 	dev_kfree_skb_any(skb);
 
@@ -231,66 +211,6 @@
 	MSG_LOG("Exit with status:%d retry:%d\n", ret, retry);
 }
 
-/* we will recycle buffers */
-static int mhi_netdev_skb_recycle(struct mhi_netdev *mhi_netdev, gfp_t gfp_t)
-{
-	struct mhi_device *mhi_dev = mhi_netdev->mhi_dev;
-	int no_tre;
-	int ret = 0;
-	struct sk_buff *skb;
-	struct mhi_skb_priv *skb_priv;
-
-	read_lock_bh(&mhi_netdev->pm_lock);
-	if (!mhi_netdev->enabled) {
-		read_unlock_bh(&mhi_netdev->pm_lock);
-		return -EIO;
-	}
-
-	no_tre = mhi_get_no_free_descriptors(mhi_dev, DMA_FROM_DEVICE);
-
-	spin_lock_bh(&mhi_netdev->rx_lock);
-	while (no_tre) {
-		skb = skb_dequeue(&mhi_netdev->rx_allocated);
-
-		/* no free buffers to recycle, reschedule work */
-		if (!skb) {
-			ret = -ENOMEM;
-			goto error_queue;
-		}
-
-		skb_priv = (struct mhi_skb_priv *)(skb->cb);
-		ret = mhi_queue_transfer(mhi_dev, DMA_FROM_DEVICE, skb,
-					 skb_priv->size, MHI_EOT);
-
-		/* failed to queue buffer */
-		if (ret) {
-			MSG_ERR("Failed to queue skb, ret:%d\n", ret);
-			skb_queue_tail(&mhi_netdev->rx_allocated, skb);
-			goto error_queue;
-		}
-
-		no_tre--;
-	}
-
-error_queue:
-	spin_unlock_bh(&mhi_netdev->rx_lock);
-	read_unlock_bh(&mhi_netdev->pm_lock);
-
-	return ret;
-}
-
-static void mhi_netdev_dealloc(struct mhi_netdev *mhi_netdev)
-{
-	struct sk_buff *skb;
-
-	skb = skb_dequeue(&mhi_netdev->rx_allocated);
-	while (skb) {
-		skb->destructor = NULL;
-		kfree_skb(skb);
-		skb = skb_dequeue(&mhi_netdev->rx_allocated);
-	}
-}
-
 static int mhi_netdev_poll(struct napi_struct *napi, int budget)
 {
 	struct net_device *dev = napi->dev;
@@ -320,7 +240,7 @@
 	}
 
 	/* queue new buffers */
-	ret = mhi_netdev->rx_queue(mhi_netdev, GFP_ATOMIC);
+	ret = mhi_netdev_alloc_skb(mhi_netdev, GFP_ATOMIC);
 	if (ret == -ENOMEM) {
 		MSG_LOG("out of tre, queuing bg worker\n");
 		mhi_netdev->stats.alloc_failed++;
@@ -439,10 +359,9 @@
 				ext_cmd.u.data, mhi_dev->mtu);
 			return -EINVAL;
 		}
-		if (!mhi_netdev->recycle_buf) {
-			MSG_LOG("MRU change request to 0x%x\n", ext_cmd.u.data);
-			mhi_netdev->mru = ext_cmd.u.data;
-		}
+
+		MSG_LOG("MRU change request to 0x%x\n", ext_cmd.u.data);
+		mhi_netdev->mru = ext_cmd.u.data;
 		break;
 	case RMNET_IOCTL_GET_SUPPORTED_FEATURES:
 		ext_cmd.u.data = 0;
@@ -602,8 +521,6 @@
 			MSG_ERR("Network device registration failed\n");
 			goto net_dev_reg_fail;
 		}
-
-		skb_queue_head_init(&mhi_netdev->rx_allocated);
 	}
 
 	write_lock_irq(&mhi_netdev->pm_lock);
@@ -616,25 +533,6 @@
 	if (ret)
 		schedule_work(&mhi_netdev->alloc_work);
 
-	/* if we recycle prepare one more set */
-	if (mhi_netdev->recycle_buf)
-		for (; no_tre >= 0; no_tre--) {
-			struct sk_buff *skb = alloc_skb(mhi_netdev->mru,
-							GFP_KERNEL);
-			struct mhi_skb_priv *skb_priv;
-
-			if (!skb)
-				break;
-
-			skb_priv = (struct mhi_skb_priv *)skb->cb;
-			skb_priv->buf = skb->data;
-			skb_priv->size = mhi_netdev->mru;
-			skb_priv->mhi_netdev = mhi_netdev;
-			skb->dev = mhi_netdev->ndev;
-			skb->destructor = mhi_netdev_skb_destructor;
-			skb_queue_tail(&mhi_netdev->rx_allocated, skb);
-		}
-
 	napi_enable(&mhi_netdev->napi);
 
 	MSG_LOG("Exited.\n");
@@ -722,11 +620,7 @@
 	    mhi_netdev->frag_skb) {
 		ret = mhi_netdev_process_fragment(mhi_netdev, skb);
 
-		/* recycle the skb */
-		if (mhi_netdev->recycle_buf)
-			mhi_netdev_skb_destructor(skb);
-		else
-			dev_kfree_skb(skb);
+		dev_kfree_skb(skb);
 
 		if (ret)
 			return;
@@ -782,9 +676,6 @@
 	/* disable all hardware channels */
 	mhi_unprepare_from_transfer(mhi_dev);
 
-	/* clean up all alocated buffers */
-	mhi_netdev_dealloc(mhi_netdev);
-
 	MSG_LOG("Restarting iface\n");
 
 	ret = mhi_netdev_enable_iface(mhi_netdev);
@@ -897,7 +788,6 @@
 
 	napi_disable(&mhi_netdev->napi);
 	netif_napi_del(&mhi_netdev->napi);
-	mhi_netdev_dealloc(mhi_netdev);
 	unregister_netdev(mhi_netdev->ndev);
 	free_netdev(mhi_netdev->ndev);
 	flush_work(&mhi_netdev->alloc_work);
@@ -938,11 +828,6 @@
 	if (ret)
 		mhi_netdev->interface_name = mhi_netdev_driver.driver.name;
 
-	mhi_netdev->recycle_buf = of_property_read_bool(of_node,
-							"mhi,recycle-buf");
-	mhi_netdev->rx_queue = mhi_netdev->recycle_buf ?
-		mhi_netdev_skb_recycle : mhi_netdev_alloc_skb;
-
 	spin_lock_init(&mhi_netdev->rx_lock);
 	rwlock_init(&mhi_netdev->pm_lock);
 	INIT_WORK(&mhi_netdev->alloc_work, mhi_netdev_alloc_work);
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index a06523a..ef08c49 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -234,6 +234,8 @@
 
 config USB_F_QDSS
 	tristate
+config USB_F_GSI
+	tristate
 
 # this first set of drivers all depend on bulk-capable hardware.
 
@@ -570,6 +572,16 @@
 	  between USB BAM and QDSS BAM for QDSS debug functionality
 	  over USB.
 
+config USB_CONFIGFS_F_GSI
+	bool "USB GSI function"
+	select USB_F_GSI
+	depends on USB_CONFIGFS
+	help
+	  Generic function driver to support h/w acceleration to IPA
+	  over GSI. This driver provides USB RMNET/RNDIS/ECM/MBIM/DPL
+	  related functionalities using GSI hardware accelerated data
+	  path and control path.
+
 choice
 	tristate "USB Gadget precomposed configurations"
 	default USB_ETH
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 48ac381..28570a1 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -62,3 +62,5 @@
 obj-$(CONFIG_USB_F_ACC)         += usb_f_accessory.o
 usb_f_qdss-y			:= f_qdss.o u_qdss.o
 obj-$(CONFIG_USB_F_QDSS)	+= usb_f_qdss.o
+usb_f_gsi-y			:= f_gsi.o rndis.o
+obj-$(CONFIG_USB_F_GSI)		+= usb_f_gsi.o
diff --git a/drivers/usb/gadget/function/f_gsi.c b/drivers/usb/gadget/function/f_gsi.c
new file mode 100644
index 0000000..c5cd4a5
--- /dev/null
+++ b/drivers/usb/gadget/function/f_gsi.c
@@ -0,0 +1,3455 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015-2018, Linux Foundation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include "f_gsi.h"
+#include "rndis.h"
+
+static bool qti_packet_debug;
+module_param(qti_packet_debug, bool, 0644);
+MODULE_PARM_DESC(qti_packet_debug, "Print QTI Packet's Raw Data");
+
+static struct workqueue_struct *ipa_usb_wq;
+static struct gsi_inst_status {
+	struct mutex gsi_lock;
+	bool inst_exist;
+	struct gsi_opts *opts;
+} inst_status[IPA_USB_MAX_TETH_PROT_SIZE];
+
+#define MAX_CDEV_INSTANCES		3
+
+static int major;
+static struct class *gsi_class;
+static DEFINE_IDA(gsi_ida);
+
+/* Deregister misc device and free instance structures */
+static void gsi_inst_clean(struct gsi_opts *opts);
+static void gsi_rndis_ipa_reset_trigger(struct gsi_data_port *d_port);
+static void ipa_disconnect_handler(struct gsi_data_port *d_port);
+static int gsi_ctrl_send_notification(struct f_gsi *gsi);
+static int gsi_alloc_trb_buffer(struct f_gsi *gsi);
+static void gsi_free_trb_buffer(struct f_gsi *gsi);
+static struct gsi_ctrl_pkt *gsi_ctrl_pkt_alloc(unsigned int len, gfp_t flags);
+static void gsi_ctrl_pkt_free(struct gsi_ctrl_pkt *pkt);
+
+static inline bool usb_gsi_remote_wakeup_allowed(struct usb_function *f)
+{
+	bool remote_wakeup_allowed;
+	struct f_gsi *gsi = func_to_gsi(f);
+
+	if (f->config->cdev->gadget->speed == USB_SPEED_SUPER)
+		remote_wakeup_allowed = f->func_wakeup_allowed;
+	else
+		remote_wakeup_allowed = f->config->cdev->gadget->remote_wakeup;
+
+	log_event_dbg("%s: remote_wakeup_allowed:%s", __func__,
+			(remote_wakeup_allowed ? "true" : "false"));
+	return remote_wakeup_allowed;
+}
+
+static void post_event(struct gsi_data_port *port, u8 event)
+{
+	unsigned long flags;
+	struct f_gsi *gsi = d_port_to_gsi(port);
+
+	spin_lock_irqsave(&port->evt_q.q_lock, flags);
+
+	port->evt_q.tail++;
+	/* Check for wraparound and make room */
+	port->evt_q.tail = port->evt_q.tail % MAXQUEUELEN;
+
+	/* Check for overflow */
+	if (port->evt_q.tail == port->evt_q.head) {
+		log_event_err("%s: event queue overflow error", __func__);
+		spin_unlock_irqrestore(&port->evt_q.q_lock, flags);
+		return;
+	}
+	/* Add event to queue */
+	port->evt_q.event[port->evt_q.tail] = event;
+	spin_unlock_irqrestore(&port->evt_q.q_lock, flags);
+}
+
+static void __maybe_unused post_event_to_evt_queue(struct gsi_data_port *port,
+								u8 event)
+{
+	post_event(port, event);
+	queue_work(port->ipa_usb_wq, &port->usb_ipa_w);
+}
+
+static u8 read_event(struct gsi_data_port *port)
+{
+	u8 event;
+	unsigned long flags;
+	struct f_gsi *gsi = d_port_to_gsi(port);
+
+	spin_lock_irqsave(&port->evt_q.q_lock, flags);
+	if (port->evt_q.head == port->evt_q.tail) {
+		log_event_dbg("%s: event queue empty", __func__);
+		spin_unlock_irqrestore(&port->evt_q.q_lock, flags);
+		return EVT_NONE;
+	}
+
+	port->evt_q.head++;
+	/* Check for wraparound and make room */
+	port->evt_q.head = port->evt_q.head % MAXQUEUELEN;
+
+	event = port->evt_q.event[port->evt_q.head];
+	spin_unlock_irqrestore(&port->evt_q.q_lock, flags);
+
+	return event;
+}
+
+static u8 peek_event(struct gsi_data_port *port)
+{
+	u8 event;
+	unsigned long flags;
+	u8 peek_index = 0;
+	struct f_gsi *gsi = d_port_to_gsi(port);
+
+	spin_lock_irqsave(&port->evt_q.q_lock, flags);
+	if (port->evt_q.head == port->evt_q.tail) {
+		log_event_dbg("%s: event queue empty", __func__);
+		spin_unlock_irqrestore(&port->evt_q.q_lock, flags);
+		return EVT_NONE;
+	}
+
+	peek_index = (port->evt_q.head + 1) % MAXQUEUELEN;
+	event = port->evt_q.event[peek_index];
+	spin_unlock_irqrestore(&port->evt_q.q_lock, flags);
+
+	return event;
+}
+
+static void __maybe_unused reset_event_queue(struct gsi_data_port *port)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&port->evt_q.q_lock, flags);
+	port->evt_q.head = port->evt_q.tail = MAXQUEUELEN - 1;
+	memset(&port->evt_q.event[0], EVT_NONE, MAXQUEUELEN);
+	spin_unlock_irqrestore(&port->evt_q.q_lock, flags);
+}
+
+static int gsi_wakeup_host(struct f_gsi *gsi)
+{
+
+	int ret;
+	struct usb_gadget *gadget;
+	struct usb_function *func;
+
+	func = &gsi->function;
+	gadget = gsi->function.config->cdev->gadget;
+
+	log_event_dbg("Entering %s", __func__);
+
+	if (!gadget) {
+		log_event_err("FAILED: d_port->cdev->gadget == NULL");
+		return -ENODEV;
+	}
+
+	/*
+	 * In Super-Speed mode, remote wakeup is not allowed for suspended
+	 * functions which have been disallowed by the host to issue Function
+	 * Remote Wakeup.
+	 * Note - We deviate here from the USB 3.0 spec and allow
+	 * non-suspended functions to issue remote-wakeup even if they were not
+	 * allowed to do so by the host. This is done in order to support non
+	 * fully USB 3.0 compatible hosts.
+	 */
+	if ((gadget->speed == USB_SPEED_SUPER) && (func->func_is_suspended)) {
+		log_event_dbg("%s: Calling usb_func_wakeup", __func__);
+		ret = usb_func_wakeup(func);
+	} else {
+		log_event_dbg("%s: Calling usb_gadget_wakeup", __func__);
+		ret = usb_gadget_wakeup(gadget);
+	}
+
+	if ((ret == -EBUSY) || (ret == -EAGAIN))
+		log_event_dbg("RW delayed due to LPM exit.");
+	else if (ret)
+		log_event_err("wakeup failed. ret=%d.", ret);
+
+	return ret;
+}
+
+/*
+ * Callback for when when network interface is up
+ * and userspace is ready to answer DHCP requests,  or remote wakeup
+ */
+int ipa_usb_notify_cb(enum ipa_usb_notify_event event,
+	void *driver_data)
+{
+	struct f_gsi *gsi = driver_data;
+	unsigned long flags;
+	struct gsi_ctrl_pkt *cpkt_notify_connect, *cpkt_notify_speed;
+
+	if (!gsi) {
+		log_event_err("%s: invalid driver data", __func__);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&gsi->d_port.lock, flags);
+
+	switch (event) {
+	case IPA_USB_DEVICE_READY:
+
+		if (gsi->d_port.net_ready_trigger) {
+			spin_unlock_irqrestore(&gsi->d_port.lock, flags);
+			log_event_dbg("%s: Already triggered", __func__);
+			return 1;
+		}
+
+		log_event_err("%s: Set net_ready_trigger", __func__);
+		gsi->d_port.net_ready_trigger = true;
+
+		if (gsi->prot_id == IPA_USB_ECM) {
+			cpkt_notify_connect = gsi_ctrl_pkt_alloc(0, GFP_ATOMIC);
+			if (IS_ERR(cpkt_notify_connect)) {
+				spin_unlock_irqrestore(&gsi->d_port.lock,
+								flags);
+				log_event_dbg("%s: err cpkt_notify_connect\n",
+								__func__);
+				return -ENOMEM;
+			}
+			cpkt_notify_connect->type = GSI_CTRL_NOTIFY_CONNECT;
+
+			cpkt_notify_speed = gsi_ctrl_pkt_alloc(0, GFP_ATOMIC);
+			if (IS_ERR(cpkt_notify_speed)) {
+				spin_unlock_irqrestore(&gsi->d_port.lock,
+								flags);
+				gsi_ctrl_pkt_free(cpkt_notify_connect);
+				log_event_dbg("%s: err cpkt_notify_speed\n",
+								__func__);
+				return -ENOMEM;
+			}
+			cpkt_notify_speed->type = GSI_CTRL_NOTIFY_SPEED;
+			spin_lock(&gsi->c_port.lock);
+			list_add_tail(&cpkt_notify_connect->list,
+					&gsi->c_port.cpkt_resp_q);
+			list_add_tail(&cpkt_notify_speed->list,
+					&gsi->c_port.cpkt_resp_q);
+			spin_unlock(&gsi->c_port.lock);
+			gsi_ctrl_send_notification(gsi);
+		}
+
+		/*
+		 * Do not post EVT_CONNECTED for RNDIS.
+		 * Data path for RNDIS is enabled on EVT_HOST_READY.
+		 */
+		if (gsi->prot_id != IPA_USB_RNDIS) {
+			post_event(&gsi->d_port, EVT_CONNECTED);
+			queue_work(gsi->d_port.ipa_usb_wq,
+					&gsi->d_port.usb_ipa_w);
+		}
+		break;
+
+	case IPA_USB_REMOTE_WAKEUP:
+		gsi_wakeup_host(gsi);
+		break;
+
+	case IPA_USB_SUSPEND_COMPLETED:
+		post_event(&gsi->d_port, EVT_IPA_SUSPEND);
+		queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+		break;
+	}
+
+	spin_unlock_irqrestore(&gsi->d_port.lock, flags);
+	return 1;
+}
+
+static int ipa_connect_channels(struct gsi_data_port *d_port)
+{
+	int ret;
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+	struct ipa_usb_xdci_chan_params *in_params =
+				&d_port->ipa_in_channel_params;
+	struct ipa_usb_xdci_chan_params *out_params =
+				&d_port->ipa_out_channel_params;
+	struct ipa_usb_xdci_connect_params *conn_params =
+				&d_port->ipa_conn_pms;
+	struct usb_composite_dev *cdev = gsi->function.config->cdev;
+	struct gsi_channel_info gsi_channel_info;
+	struct ipa_req_chan_out_params ipa_in_channel_out_params;
+	struct ipa_req_chan_out_params ipa_out_channel_out_params;
+
+	log_event_dbg("%s: USB GSI IN OPS", __func__);
+	usb_gsi_ep_op(d_port->in_ep, &d_port->in_request,
+		GSI_EP_OP_PREPARE_TRBS);
+	usb_gsi_ep_op(d_port->in_ep, &d_port->in_request,
+			GSI_EP_OP_STARTXFER);
+	d_port->in_xfer_rsc_index = usb_gsi_ep_op(d_port->in_ep, NULL,
+			GSI_EP_OP_GET_XFER_IDX);
+
+	memset(in_params, 0x0, sizeof(*in_params));
+	gsi_channel_info.ch_req = &d_port->in_request;
+	usb_gsi_ep_op(d_port->in_ep, (void *)&gsi_channel_info,
+			GSI_EP_OP_GET_CH_INFO);
+
+	log_event_dbg("%s: USB GSI IN OPS Completed", __func__);
+	in_params->client =
+		(gsi->prot_id != IPA_USB_DIAG) ? IPA_CLIENT_USB_CONS :
+						IPA_CLIENT_USB_DPL_CONS;
+	in_params->ipa_ep_cfg.mode.mode = IPA_BASIC;
+	in_params->teth_prot = gsi->prot_id;
+	in_params->gevntcount_low_addr =
+		gsi_channel_info.gevntcount_low_addr;
+	in_params->gevntcount_hi_addr =
+		gsi_channel_info.gevntcount_hi_addr;
+	in_params->dir = GSI_CHAN_DIR_FROM_GSI;
+	in_params->xfer_ring_len = gsi_channel_info.xfer_ring_len;
+	in_params->xfer_scratch.last_trb_addr_iova =
+					gsi_channel_info.last_trb_addr;
+	in_params->xfer_ring_base_addr_iova =
+					gsi_channel_info.xfer_ring_base_addr;
+	in_params->data_buff_base_len = d_port->in_request.buf_len *
+					d_port->in_request.num_bufs;
+	in_params->data_buff_base_addr_iova = d_port->in_request.dma;
+	in_params->sgt_xfer_rings = &d_port->in_request.sgt_trb_xfer_ring;
+	in_params->sgt_data_buff = &d_port->in_request.sgt_data_buff;
+	log_event_dbg("%s(): IN: sgt_xfer_rings:%pK sgt_data_buff:%pK\n",
+		__func__, in_params->sgt_xfer_rings, in_params->sgt_data_buff);
+	in_params->xfer_scratch.const_buffer_size =
+		gsi_channel_info.const_buffer_size;
+	in_params->xfer_scratch.depcmd_low_addr =
+		gsi_channel_info.depcmd_low_addr;
+	in_params->xfer_scratch.depcmd_hi_addr =
+		gsi_channel_info.depcmd_hi_addr;
+
+	if (d_port->out_ep) {
+		log_event_dbg("%s: USB GSI OUT OPS", __func__);
+		usb_gsi_ep_op(d_port->out_ep, &d_port->out_request,
+			GSI_EP_OP_PREPARE_TRBS);
+		usb_gsi_ep_op(d_port->out_ep, &d_port->out_request,
+				GSI_EP_OP_STARTXFER);
+		d_port->out_xfer_rsc_index =
+			usb_gsi_ep_op(d_port->out_ep,
+				NULL, GSI_EP_OP_GET_XFER_IDX);
+		memset(out_params, 0x0, sizeof(*out_params));
+		gsi_channel_info.ch_req = &d_port->out_request;
+		usb_gsi_ep_op(d_port->out_ep, (void *)&gsi_channel_info,
+				GSI_EP_OP_GET_CH_INFO);
+		log_event_dbg("%s: USB GSI OUT OPS Completed", __func__);
+		out_params->client = IPA_CLIENT_USB_PROD;
+		out_params->ipa_ep_cfg.mode.mode = IPA_BASIC;
+		out_params->teth_prot = gsi->prot_id;
+		out_params->gevntcount_low_addr =
+			gsi_channel_info.gevntcount_low_addr;
+		out_params->gevntcount_hi_addr =
+			gsi_channel_info.gevntcount_hi_addr;
+		out_params->dir = GSI_CHAN_DIR_TO_GSI;
+		out_params->xfer_ring_len =
+			gsi_channel_info.xfer_ring_len;
+		out_params->xfer_ring_base_addr_iova =
+			gsi_channel_info.xfer_ring_base_addr;
+		out_params->data_buff_base_len = d_port->out_request.buf_len *
+			d_port->out_request.num_bufs;
+		out_params->data_buff_base_addr_iova =
+			d_port->out_request.dma;
+		out_params->sgt_xfer_rings =
+			&d_port->out_request.sgt_trb_xfer_ring;
+		out_params->sgt_data_buff = &d_port->out_request.sgt_data_buff;
+		log_event_dbg("%s(): OUT: sgt_xfer_rings:%pK sgt_data_buff:%pK\n",
+			__func__, out_params->sgt_xfer_rings,
+			out_params->sgt_data_buff);
+
+		out_params->xfer_scratch.last_trb_addr_iova =
+			gsi_channel_info.last_trb_addr;
+		out_params->xfer_scratch.const_buffer_size =
+			gsi_channel_info.const_buffer_size;
+		out_params->xfer_scratch.depcmd_low_addr =
+			gsi_channel_info.depcmd_low_addr;
+		out_params->xfer_scratch.depcmd_hi_addr =
+			gsi_channel_info.depcmd_hi_addr;
+	}
+
+	/* Populate connection params */
+	conn_params->max_pkt_size =
+		(cdev->gadget->speed >= USB_SPEED_SUPER) ?
+		IPA_USB_SUPER_SPEED_1024B : IPA_USB_HIGH_SPEED_512B;
+	conn_params->ipa_to_usb_xferrscidx =
+			d_port->in_xfer_rsc_index;
+	conn_params->usb_to_ipa_xferrscidx =
+			d_port->out_xfer_rsc_index;
+	conn_params->usb_to_ipa_xferrscidx_valid =
+			(gsi->prot_id != IPA_USB_DIAG) ? true : false;
+	conn_params->ipa_to_usb_xferrscidx_valid = true;
+	conn_params->teth_prot = gsi->prot_id;
+	conn_params->teth_prot_params.max_xfer_size_bytes_to_dev = 23700;
+	conn_params->teth_prot_params.max_xfer_size_bytes_to_dev
+				= d_port->out_aggr_size;
+	conn_params->teth_prot_params.max_xfer_size_bytes_to_host
+					= d_port->in_aggr_size;
+	conn_params->teth_prot_params.max_packet_number_to_dev =
+		DEFAULT_MAX_PKT_PER_XFER;
+	conn_params->max_supported_bandwidth_mbps =
+		(cdev->gadget->speed == USB_SPEED_SUPER) ? 3600 : 400;
+
+	memset(&ipa_in_channel_out_params, 0x0,
+				sizeof(ipa_in_channel_out_params));
+	memset(&ipa_out_channel_out_params, 0x0,
+				sizeof(ipa_out_channel_out_params));
+
+	log_event_dbg("%s: Calling xdci_connect", __func__);
+	ret = ipa_usb_xdci_connect(out_params, in_params,
+					&ipa_out_channel_out_params,
+					&ipa_in_channel_out_params,
+					conn_params);
+	if (ret) {
+		log_event_err("%s: IPA connect failed %d", __func__, ret);
+		return ret;
+	}
+	log_event_dbg("%s: xdci_connect done", __func__);
+
+	log_event_dbg("%s: IN CH HDL %x", __func__,
+			ipa_in_channel_out_params.clnt_hdl);
+	log_event_dbg("%s: IN CH DBL addr %x", __func__,
+			ipa_in_channel_out_params.db_reg_phs_addr_lsb);
+
+	log_event_dbg("%s: OUT CH HDL %x", __func__,
+			ipa_out_channel_out_params.clnt_hdl);
+	log_event_dbg("%s: OUT CH DBL addr %x", __func__,
+			ipa_out_channel_out_params.db_reg_phs_addr_lsb);
+
+	d_port->in_channel_handle = ipa_in_channel_out_params.clnt_hdl;
+	d_port->in_request.db_reg_phs_addr_lsb =
+		ipa_in_channel_out_params.db_reg_phs_addr_lsb;
+	d_port->in_request.db_reg_phs_addr_msb =
+		ipa_in_channel_out_params.db_reg_phs_addr_msb;
+
+	if (gsi->prot_id != IPA_USB_DIAG) {
+		d_port->out_channel_handle =
+			ipa_out_channel_out_params.clnt_hdl;
+		d_port->out_request.db_reg_phs_addr_lsb =
+			ipa_out_channel_out_params.db_reg_phs_addr_lsb;
+		d_port->out_request.db_reg_phs_addr_msb =
+			ipa_out_channel_out_params.db_reg_phs_addr_msb;
+	}
+	return ret;
+}
+
+static void ipa_data_path_enable(struct gsi_data_port *d_port)
+{
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+	bool block_db = false;
+
+	log_event_dbg("IN: db_reg_phs_addr_lsb = %x",
+			gsi->d_port.in_request.db_reg_phs_addr_lsb);
+	usb_gsi_ep_op(gsi->d_port.in_ep,
+			&gsi->d_port.in_request,
+			GSI_EP_OP_STORE_DBL_INFO);
+
+	if (gsi->d_port.out_ep) {
+		log_event_dbg("OUT: db_reg_phs_addr_lsb = %x",
+				gsi->d_port.out_request.db_reg_phs_addr_lsb);
+		usb_gsi_ep_op(gsi->d_port.out_ep,
+				&gsi->d_port.out_request,
+				GSI_EP_OP_STORE_DBL_INFO);
+
+		usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request,
+				GSI_EP_OP_ENABLE_GSI);
+	}
+
+	/* Unblock doorbell to GSI */
+	usb_gsi_ep_op(d_port->in_ep, (void *)&block_db,
+				GSI_EP_OP_SET_CLR_BLOCK_DBL);
+
+	usb_gsi_ep_op(gsi->d_port.in_ep, &gsi->d_port.in_request,
+						GSI_EP_OP_RING_DB);
+
+	if (gsi->d_port.out_ep)
+		usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request,
+						GSI_EP_OP_RING_DB);
+}
+
+static void ipa_disconnect_handler(struct gsi_data_port *d_port)
+{
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+	bool block_db = true;
+
+	log_event_dbg("%s: EP Disable for data", __func__);
+
+	if (gsi->d_port.in_ep) {
+		/*
+		 * Block doorbell to GSI to avoid USB wrapper from
+		 * ringing doorbell in case IPA clocks are OFF.
+		 */
+		usb_gsi_ep_op(d_port->in_ep, (void *)&block_db,
+				GSI_EP_OP_SET_CLR_BLOCK_DBL);
+		usb_gsi_ep_op(gsi->d_port.in_ep,
+				&gsi->d_port.in_request, GSI_EP_OP_DISABLE);
+	}
+
+	if (gsi->d_port.out_ep)
+		usb_gsi_ep_op(gsi->d_port.out_ep,
+				&gsi->d_port.out_request, GSI_EP_OP_DISABLE);
+
+	gsi->d_port.net_ready_trigger = false;
+}
+
+static void ipa_disconnect_work_handler(struct gsi_data_port *d_port)
+{
+	int ret;
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+
+	log_event_dbg("%s: Calling xdci_disconnect", __func__);
+
+	ret = ipa_usb_xdci_disconnect(gsi->d_port.out_channel_handle,
+			gsi->d_port.in_channel_handle, gsi->prot_id);
+	if (ret)
+		log_event_err("%s: IPA disconnect failed %d",
+				__func__, ret);
+
+	log_event_dbg("%s: xdci_disconnect done", __func__);
+
+	/* invalidate channel handles*/
+	gsi->d_port.in_channel_handle = -EINVAL;
+	gsi->d_port.out_channel_handle = -EINVAL;
+
+	usb_gsi_ep_op(gsi->d_port.in_ep, &gsi->d_port.in_request,
+							GSI_EP_OP_FREE_TRBS);
+
+	if (gsi->d_port.out_ep)
+		usb_gsi_ep_op(gsi->d_port.out_ep, &gsi->d_port.out_request,
+							GSI_EP_OP_FREE_TRBS);
+
+	/* free buffers allocated with each TRB */
+	gsi_free_trb_buffer(gsi);
+}
+
+static int ipa_suspend_work_handler(struct gsi_data_port *d_port)
+{
+	int ret = 0;
+	bool block_db, f_suspend;
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+	struct usb_function *f = &gsi->function;
+
+	f_suspend = f->func_wakeup_allowed;
+	log_event_dbg("%s: f_suspend:%d", __func__, f_suspend);
+
+	if (!usb_gsi_ep_op(gsi->d_port.in_ep, (void *) &f_suspend,
+				GSI_EP_OP_CHECK_FOR_SUSPEND)) {
+		ret = -EFAULT;
+		block_db = false;
+		usb_gsi_ep_op(d_port->in_ep, (void *)&block_db,
+			GSI_EP_OP_SET_CLR_BLOCK_DBL);
+		goto done;
+	}
+
+	log_event_dbg("%s: Calling xdci_suspend", __func__);
+	ret = ipa_usb_xdci_suspend(gsi->d_port.out_channel_handle,
+				gsi->d_port.in_channel_handle, gsi->prot_id,
+				usb_gsi_remote_wakeup_allowed(f));
+	if (!ret) {
+		d_port->sm_state = STATE_SUSPENDED;
+		log_event_dbg("%s: STATE SUSPENDED", __func__);
+		goto done;
+	}
+
+	if (ret == -EFAULT) {
+		block_db = false;
+		usb_gsi_ep_op(d_port->in_ep, (void *)&block_db,
+					GSI_EP_OP_SET_CLR_BLOCK_DBL);
+		gsi_wakeup_host(gsi);
+	} else if (ret == -EINPROGRESS) {
+		d_port->sm_state = STATE_SUSPEND_IN_PROGRESS;
+	} else {
+		log_event_err("%s: Error %d for %d", __func__, ret,
+							gsi->prot_id);
+	}
+done:
+	log_event_dbg("%s: xdci_suspend ret %d", __func__, ret);
+	return ret;
+}
+
+static void ipa_resume_work_handler(struct gsi_data_port *d_port)
+{
+	bool block_db;
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+	int ret;
+
+	log_event_dbg("%s: Calling xdci_resume", __func__);
+
+	ret = ipa_usb_xdci_resume(gsi->d_port.out_channel_handle,
+					gsi->d_port.in_channel_handle,
+					gsi->prot_id);
+	if (ret)
+		log_event_dbg("%s: xdci_resume ret %d", __func__, ret);
+
+	log_event_dbg("%s: xdci_resume done", __func__);
+
+	block_db = false;
+	usb_gsi_ep_op(d_port->in_ep, (void *)&block_db,
+			GSI_EP_OP_SET_CLR_BLOCK_DBL);
+}
+
+static void ipa_work_handler(struct work_struct *w)
+{
+	struct gsi_data_port *d_port = container_of(w, struct gsi_data_port,
+						  usb_ipa_w);
+	u8 event;
+	int ret = 0;
+	struct usb_gadget *gadget = d_port->gadget;
+	struct device *dev;
+	struct device *gad_dev;
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+	bool block_db;
+
+	event = read_event(d_port);
+
+	log_event_dbg("%s: event = %x sm_state %x", __func__,
+			event, d_port->sm_state);
+
+	if (gadget) {
+		dev = &gadget->dev;
+		if (!dev || !dev->parent) {
+			log_event_err("%s(): dev or dev->parent is NULL.\n",
+					__func__);
+			return;
+		}
+		gad_dev = dev->parent;
+	} else {
+		log_event_err("%s(): gadget is NULL.\n", __func__);
+		return;
+	}
+
+	switch (d_port->sm_state) {
+	case STATE_UNINITIALIZED:
+		break;
+	case STATE_INITIALIZED:
+		if (event == EVT_CONNECT_IN_PROGRESS) {
+			usb_gadget_autopm_get(d_port->gadget);
+			log_event_dbg("%s: get = %d", __func__,
+				atomic_read(&gad_dev->power.usage_count));
+			/* allocate buffers used with each TRB */
+			ret = gsi_alloc_trb_buffer(gsi);
+			if (ret) {
+				log_event_err("%s: gsi_alloc_trb_failed\n",
+								__func__);
+				break;
+			}
+			ipa_connect_channels(d_port);
+			d_port->sm_state = STATE_CONNECT_IN_PROGRESS;
+			log_event_dbg("%s: ST_INIT_EVT_CONN_IN_PROG",
+					__func__);
+		} else if (event == EVT_HOST_READY) {
+			/*
+			 * When in a composition such as RNDIS + ADB,
+			 * RNDIS host sends a GEN_CURRENT_PACKET_FILTER msg
+			 * to enable/disable flow control eg. during RNDIS
+			 * adaptor disable/enable from device manager.
+			 * In the case of the msg to disable flow control,
+			 * connect IPA channels and enable data path.
+			 * EVT_HOST_READY is posted to the state machine
+			 * in the handler for this msg.
+			 */
+			usb_gadget_autopm_get(d_port->gadget);
+			log_event_dbg("%s: get = %d", __func__,
+				atomic_read(&gad_dev->power.usage_count));
+			/* allocate buffers used with each TRB */
+			ret = gsi_alloc_trb_buffer(gsi);
+			if (ret) {
+				log_event_err("%s: gsi_alloc_trb_failed\n",
+								__func__);
+				break;
+			}
+
+			ipa_connect_channels(d_port);
+			ipa_data_path_enable(d_port);
+			d_port->sm_state = STATE_CONNECTED;
+			log_event_dbg("%s: ST_INIT_EVT_HOST_READY", __func__);
+		}
+		break;
+	case STATE_CONNECT_IN_PROGRESS:
+		if (event == EVT_HOST_READY) {
+			ipa_data_path_enable(d_port);
+			d_port->sm_state = STATE_CONNECTED;
+			log_event_dbg("%s: ST_CON_IN_PROG_EVT_HOST_READY",
+					 __func__);
+		} else if (event == EVT_CONNECTED) {
+			if (peek_event(d_port) == EVT_SUSPEND) {
+				log_event_dbg("%s: ST_CON_IN_PROG_EVT_SUSPEND",
+					 __func__);
+				break;
+			}
+			ipa_data_path_enable(d_port);
+			d_port->sm_state = STATE_CONNECTED;
+			log_event_dbg("%s: ST_CON_IN_PROG_EVT_CON %d",
+					__func__, __LINE__);
+		} else if (event == EVT_SUSPEND) {
+			if (peek_event(d_port) == EVT_DISCONNECTED) {
+				read_event(d_port);
+				ipa_disconnect_work_handler(d_port);
+				d_port->sm_state = STATE_INITIALIZED;
+				usb_gadget_autopm_put_async(d_port->gadget);
+				log_event_dbg("%s: ST_CON_IN_PROG_EVT_SUS_DIS",
+						__func__);
+				log_event_dbg("%s: put_async1 = %d", __func__,
+						atomic_read(
+						&gad_dev->power.usage_count));
+				break;
+			}
+			ret = ipa_suspend_work_handler(d_port);
+			if (!ret) {
+				usb_gadget_autopm_put_async(d_port->gadget);
+				log_event_dbg("%s: ST_CON_IN_PROG_EVT_SUS",
+						__func__);
+				log_event_dbg("%s: put_async2 = %d", __func__,
+						atomic_read(
+						&gad_dev->power.usage_count));
+			}
+		} else if (event == EVT_DISCONNECTED) {
+			ipa_disconnect_work_handler(d_port);
+			d_port->sm_state = STATE_INITIALIZED;
+			usb_gadget_autopm_put_async(d_port->gadget);
+			log_event_dbg("%s: ST_CON_IN_PROG_EVT_DIS",
+						__func__);
+			log_event_dbg("%s: put_async3 = %d",
+					__func__, atomic_read(
+						&gad_dev->power.usage_count));
+		}
+		break;
+	case STATE_CONNECTED:
+		if (event == EVT_DISCONNECTED || event == EVT_HOST_NRDY) {
+			if (peek_event(d_port) == EVT_HOST_READY) {
+				read_event(d_port);
+				log_event_dbg("%s: NO_OP NRDY_RDY", __func__);
+				break;
+			}
+
+			if (event == EVT_HOST_NRDY) {
+				log_event_dbg("%s: ST_CON_HOST_NRDY\n",
+								__func__);
+				block_db = true;
+				/* stop USB ringing doorbell to GSI(OUT_EP) */
+				usb_gsi_ep_op(d_port->in_ep, (void *)&block_db,
+						GSI_EP_OP_SET_CLR_BLOCK_DBL);
+				gsi_rndis_ipa_reset_trigger(d_port);
+				usb_gsi_ep_op(d_port->in_ep, NULL,
+						GSI_EP_OP_ENDXFER);
+				usb_gsi_ep_op(d_port->out_ep, NULL,
+						GSI_EP_OP_ENDXFER);
+			}
+
+			ipa_disconnect_work_handler(d_port);
+			d_port->sm_state = STATE_INITIALIZED;
+			usb_gadget_autopm_put_async(d_port->gadget);
+			log_event_dbg("%s: ST_CON_EVT_DIS", __func__);
+			log_event_dbg("%s: put_async4 = %d",
+					__func__, atomic_read(
+						&gad_dev->power.usage_count));
+		} else if (event == EVT_SUSPEND) {
+			if (peek_event(d_port) == EVT_DISCONNECTED) {
+				read_event(d_port);
+				ipa_disconnect_work_handler(d_port);
+				d_port->sm_state = STATE_INITIALIZED;
+				usb_gadget_autopm_put_async(d_port->gadget);
+				log_event_dbg("%s: ST_CON_EVT_SUS_DIS",
+						__func__);
+				log_event_dbg("%s: put_async5 = %d",
+						__func__, atomic_read(
+						&gad_dev->power.usage_count));
+				break;
+			}
+			ret = ipa_suspend_work_handler(d_port);
+			if (!ret) {
+				usb_gadget_autopm_put_async(d_port->gadget);
+				log_event_dbg("%s: ST_CON_EVT_SUS",
+						__func__);
+				log_event_dbg("%s: put_async6 = %d",
+						__func__, atomic_read(
+						&gad_dev->power.usage_count));
+			}
+		} else if (event == EVT_CONNECTED) {
+			d_port->sm_state = STATE_CONNECTED;
+			log_event_dbg("%s: ST_CON_EVT_CON", __func__);
+		}
+		break;
+	case STATE_DISCONNECTED:
+		if (event == EVT_CONNECT_IN_PROGRESS) {
+			ipa_connect_channels(d_port);
+			d_port->sm_state = STATE_CONNECT_IN_PROGRESS;
+			log_event_dbg("%s: ST_DIS_EVT_CON_IN_PROG", __func__);
+		} else if (event == EVT_UNINITIALIZED) {
+			d_port->sm_state = STATE_UNINITIALIZED;
+			log_event_dbg("%s: ST_DIS_EVT_UNINIT", __func__);
+		}
+		break;
+	case STATE_SUSPEND_IN_PROGRESS:
+		if (event == EVT_IPA_SUSPEND) {
+			d_port->sm_state = STATE_SUSPENDED;
+			usb_gadget_autopm_put_async(d_port->gadget);
+			log_event_dbg("%s: ST_SUS_IN_PROG_EVT_IPA_SUS",
+					__func__);
+			log_event_dbg("%s: put_async6 = %d",
+						__func__, atomic_read(
+						&gad_dev->power.usage_count));
+		} else	if (event == EVT_RESUMED) {
+			ipa_resume_work_handler(d_port);
+			d_port->sm_state = STATE_CONNECTED;
+			/*
+			 * Increment usage count here to disallow gadget
+			 * parent suspend. This counter will decrement
+			 * after IPA disconnect is done in disconnect work
+			 * (due to cable disconnect) or in suspended state.
+			 */
+			usb_gadget_autopm_get_noresume(d_port->gadget);
+			log_event_dbg("%s: ST_SUS_IN_PROG_EVT_RES", __func__);
+			log_event_dbg("%s: get_nores1 = %d", __func__,
+					atomic_read(
+						&gad_dev->power.usage_count));
+		} else if (event == EVT_DISCONNECTED) {
+			ipa_disconnect_work_handler(d_port);
+			d_port->sm_state = STATE_INITIALIZED;
+			usb_gadget_autopm_put_async(d_port->gadget);
+			log_event_dbg("%s: ST_SUS_IN_PROG_EVT_DIS", __func__);
+			log_event_dbg("%s: put_async7 = %d", __func__,
+					atomic_read(
+						&gad_dev->power.usage_count));
+		}
+		break;
+
+	case STATE_SUSPENDED:
+		if (event == EVT_RESUMED) {
+			usb_gadget_autopm_get(d_port->gadget);
+			log_event_dbg("%s: ST_SUS_EVT_RES", __func__);
+			log_event_dbg("%s: get = %d", __func__,
+				atomic_read(&gad_dev->power.usage_count));
+			ipa_resume_work_handler(d_port);
+			d_port->sm_state = STATE_CONNECTED;
+		} else if (event == EVT_DISCONNECTED) {
+			usb_gadget_autopm_get(d_port->gadget);
+			ipa_disconnect_work_handler(d_port);
+			d_port->sm_state = STATE_INITIALIZED;
+			log_event_dbg("%s: ST_SUS_EVT_DIS", __func__);
+			usb_gadget_autopm_put_async(d_port->gadget);
+		}
+		break;
+	default:
+		log_event_dbg("%s: Invalid state to SM", __func__);
+	}
+
+	if (peek_event(d_port) != EVT_NONE) {
+		log_event_dbg("%s: New events to process", __func__);
+		queue_work(d_port->ipa_usb_wq, &d_port->usb_ipa_w);
+	}
+}
+
+static struct gsi_ctrl_pkt *gsi_ctrl_pkt_alloc(unsigned int len, gfp_t flags)
+{
+	struct gsi_ctrl_pkt *pkt;
+
+	pkt = kzalloc(sizeof(struct gsi_ctrl_pkt), flags);
+	if (!pkt)
+		return ERR_PTR(-ENOMEM);
+
+	pkt->buf = kmalloc(len, flags);
+	if (!pkt->buf) {
+		kfree(pkt);
+		return ERR_PTR(-ENOMEM);
+	}
+	pkt->len = len;
+
+	return pkt;
+}
+
+static void gsi_ctrl_pkt_free(struct gsi_ctrl_pkt *pkt)
+{
+	if (pkt) {
+		kfree(pkt->buf);
+		kfree(pkt);
+	}
+}
+
+static void gsi_ctrl_clear_cpkt_queues(struct f_gsi *gsi, bool skip_req_q)
+{
+	struct gsi_ctrl_pkt *cpkt = NULL;
+	struct list_head *act, *tmp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gsi->c_port.lock, flags);
+	if (skip_req_q)
+		goto clean_resp_q;
+
+	list_for_each_safe(act, tmp, &gsi->c_port.cpkt_req_q) {
+		cpkt = list_entry(act, struct gsi_ctrl_pkt, list);
+		list_del(&cpkt->list);
+		gsi_ctrl_pkt_free(cpkt);
+	}
+clean_resp_q:
+	list_for_each_safe(act, tmp, &gsi->c_port.cpkt_resp_q) {
+		cpkt = list_entry(act, struct gsi_ctrl_pkt, list);
+		list_del(&cpkt->list);
+		gsi_ctrl_pkt_free(cpkt);
+	}
+	spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+}
+
+static int gsi_ctrl_send_cpkt_tomodem(struct f_gsi *gsi, void *buf, size_t len)
+{
+	unsigned long flags;
+	struct gsi_ctrl_port *c_port = &gsi->c_port;
+	struct gsi_ctrl_pkt *cpkt;
+
+	spin_lock_irqsave(&c_port->lock, flags);
+	/* drop cpkt if port is not open */
+	if (!gsi->c_port.is_open) {
+		log_event_dbg("%s: ctrl device %s is not open",
+			   __func__, gsi->c_port.name);
+		c_port->cpkt_drop_cnt++;
+		spin_unlock_irqrestore(&c_port->lock, flags);
+		return -ENODEV;
+	}
+
+	cpkt = gsi_ctrl_pkt_alloc(len, GFP_ATOMIC);
+	if (IS_ERR(cpkt)) {
+		log_event_err("%s: Reset func pkt allocation failed", __func__);
+		spin_unlock_irqrestore(&c_port->lock, flags);
+		return -ENOMEM;
+	}
+
+	memcpy(cpkt->buf, buf, len);
+	cpkt->len = len;
+
+	list_add_tail(&cpkt->list, &c_port->cpkt_req_q);
+	c_port->host_to_modem++;
+	spin_unlock_irqrestore(&c_port->lock, flags);
+
+	log_event_dbg("%s: Wake up read queue", __func__);
+	wake_up(&c_port->read_wq);
+
+	return 0;
+}
+
+static int gsi_ctrl_dev_open(struct inode *ip, struct file *fp)
+{
+	struct gsi_ctrl_port *c_port = container_of(ip->i_cdev,
+						struct gsi_ctrl_port, cdev);
+	struct f_gsi *gsi;
+	struct gsi_inst_status *inst_cur;
+
+	if (!c_port) {
+		pr_err_ratelimited("%s: gsi ctrl port %p", __func__, c_port);
+		return -ENODEV;
+	}
+
+	gsi = container_of(c_port, struct f_gsi, c_port);
+	inst_cur = &inst_status[gsi->prot_id];
+	log_event_dbg("%s: open ctrl dev %s", __func__, c_port->name);
+
+	mutex_lock(&inst_cur->gsi_lock);
+
+	fp->private_data = &gsi->prot_id;
+
+	if (!inst_cur->inst_exist) {
+		mutex_unlock(&inst_cur->gsi_lock);
+		log_event_err("%s: [prot_id = %d], GSI instance freed already\n",
+				__func__, gsi->prot_id);
+		return -ENODEV;
+	}
+
+	if (c_port->is_open) {
+		mutex_unlock(&inst_cur->gsi_lock);
+		log_event_err("%s: Already opened\n", __func__);
+		return -EBUSY;
+	}
+
+	c_port->is_open = true;
+
+	mutex_unlock(&inst_cur->gsi_lock);
+
+	return 0;
+}
+
+static int gsi_ctrl_dev_release(struct inode *ip, struct file *fp)
+{
+	enum ipa_usb_teth_prot prot_id =
+		*(enum ipa_usb_teth_prot *)(fp->private_data);
+	struct gsi_inst_status *inst_cur = &inst_status[prot_id];
+	struct f_gsi *gsi;
+
+	mutex_lock(&inst_cur->gsi_lock);
+
+	if (unlikely(inst_cur->inst_exist == false)) {
+		if (inst_cur->opts) {
+			/* GSI instance clean up */
+			gsi_inst_clean(inst_cur->opts);
+			inst_cur->opts = NULL;
+		}
+		mutex_unlock(&inst_cur->gsi_lock);
+		pr_err_ratelimited("%s: prot_id:%d: delayed free memory\n",
+			__func__, prot_id);
+		return -ENODEV;
+	}
+
+	inst_cur->opts->gsi->c_port.is_open = false;
+	gsi = inst_cur->opts->gsi;
+	mutex_unlock(&inst_cur->gsi_lock);
+
+	log_event_dbg("close ctrl dev %s\n",
+			inst_cur->opts->gsi->c_port.name);
+
+	return 0;
+}
+
+static ssize_t
+gsi_ctrl_dev_read(struct file *fp, char __user *buf, size_t count, loff_t *pos)
+{
+	struct gsi_ctrl_port *c_port;
+	struct gsi_ctrl_pkt *cpkt = NULL;
+	enum ipa_usb_teth_prot prot_id =
+		*(enum ipa_usb_teth_prot *)(fp->private_data);
+	struct gsi_inst_status *inst_cur = &inst_status[prot_id];
+	struct f_gsi *gsi;
+	unsigned long flags;
+	int ret = 0;
+
+	pr_debug("%s: Enter %zu", __func__, count);
+
+	mutex_lock(&inst_cur->gsi_lock);
+	if (unlikely(inst_cur->inst_exist == false)) {
+		mutex_unlock(&inst_cur->gsi_lock);
+		pr_err_ratelimited("%s: free_inst is called and being freed\n",
+								__func__);
+		return -ENODEV;
+	}
+	mutex_unlock(&inst_cur->gsi_lock);
+
+	gsi = inst_cur->opts->gsi;
+	c_port = &inst_cur->opts->gsi->c_port;
+	if (!c_port) {
+		log_event_err("%s: gsi ctrl port %p", __func__, c_port);
+		return -ENODEV;
+	}
+
+	if (count > GSI_MAX_CTRL_PKT_SIZE) {
+		log_event_err("Large buff size %zu, should be %d",
+			count, GSI_MAX_CTRL_PKT_SIZE);
+		return -EINVAL;
+	}
+
+	/* block until a new packet is available */
+	spin_lock_irqsave(&c_port->lock, flags);
+	while (list_empty(&c_port->cpkt_req_q)) {
+		log_event_dbg("Requests list is empty. Wait.");
+		spin_unlock_irqrestore(&c_port->lock, flags);
+		ret = wait_event_interruptible(c_port->read_wq,
+			!list_empty(&c_port->cpkt_req_q));
+		if (ret < 0) {
+			log_event_err("Waiting failed");
+			return -ERESTARTSYS;
+		}
+		log_event_dbg("Received request packet");
+		spin_lock_irqsave(&c_port->lock, flags);
+	}
+
+	cpkt = list_first_entry(&c_port->cpkt_req_q, struct gsi_ctrl_pkt,
+							list);
+	list_del(&cpkt->list);
+	spin_unlock_irqrestore(&c_port->lock, flags);
+
+	if (cpkt->len > count) {
+		log_event_err("cpkt size large:%d > buf size:%zu",
+				cpkt->len, count);
+		gsi_ctrl_pkt_free(cpkt);
+		return -ENOMEM;
+	}
+
+	log_event_dbg("%s: cpkt size:%d", __func__, cpkt->len);
+	if (qti_packet_debug)
+		print_hex_dump(KERN_DEBUG, "READ:", DUMP_PREFIX_OFFSET, 16, 1,
+			cpkt->buf, min_t(int, 30, cpkt->len), false);
+
+	ret = copy_to_user(buf, cpkt->buf, cpkt->len);
+	if (ret) {
+		log_event_err("copy_to_user failed: err %d", ret);
+		ret = -EFAULT;
+	} else {
+		log_event_dbg("%s: copied %d bytes to user", __func__,
+							cpkt->len);
+		ret = cpkt->len;
+		c_port->copied_to_modem++;
+	}
+
+	gsi_ctrl_pkt_free(cpkt);
+
+	log_event_dbg("%s: Exit %zu", __func__, count);
+
+	return ret;
+}
+
+static ssize_t gsi_ctrl_dev_write(struct file *fp, const char __user *buf,
+		size_t count, loff_t *pos)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct gsi_ctrl_pkt *cpkt;
+	struct gsi_ctrl_port *c_port;
+	struct usb_request *req;
+	enum ipa_usb_teth_prot prot_id =
+		*(enum ipa_usb_teth_prot *)(fp->private_data);
+	struct gsi_inst_status *inst_cur = &inst_status[prot_id];
+	struct f_gsi *gsi;
+
+	pr_debug("Enter %zu", count);
+
+	mutex_lock(&inst_cur->gsi_lock);
+	if (unlikely(inst_cur->inst_exist == false)) {
+		mutex_unlock(&inst_cur->gsi_lock);
+		pr_err_ratelimited("%s: free_inst is called and being freed\n",
+								__func__);
+		return -ENODEV;
+	}
+	mutex_unlock(&inst_cur->gsi_lock);
+
+	gsi = inst_cur->opts->gsi;
+	c_port = &gsi->c_port;
+	req = c_port->notify_req;
+
+	if (!c_port || !req || !req->buf) {
+		log_event_err("%s: c_port %p req %p req->buf %p",
+			__func__, c_port, req, req ? req->buf : req);
+		return -ENODEV;
+	}
+
+	if (!count || count > GSI_MAX_CTRL_PKT_SIZE) {
+		log_event_err("error: ctrl pkt length %zu", count);
+		return -EINVAL;
+	}
+
+	if (!atomic_read(&gsi->connected)) {
+		log_event_err("USB cable not connected\n");
+		return -ECONNRESET;
+	}
+
+	if (gsi->function.func_is_suspended &&
+			!gsi->function.func_wakeup_allowed) {
+		c_port->cpkt_drop_cnt++;
+		log_event_err("drop ctrl pkt of len %zu", count);
+		return -ENOTSUPP;
+	}
+
+	cpkt = gsi_ctrl_pkt_alloc(count, GFP_KERNEL);
+	if (IS_ERR(cpkt)) {
+		log_event_err("failed to allocate ctrl pkt");
+		return -ENOMEM;
+	}
+
+	ret = copy_from_user(cpkt->buf, buf, count);
+	if (ret) {
+		log_event_err("copy_from_user failed err:%d", ret);
+		gsi_ctrl_pkt_free(cpkt);
+		return ret;
+	}
+	cpkt->type = GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE;
+	c_port->copied_from_modem++;
+	if (qti_packet_debug)
+		print_hex_dump(KERN_DEBUG, "WRITE:", DUMP_PREFIX_OFFSET, 16, 1,
+			cpkt->buf, min_t(int, 30, count), false);
+
+	spin_lock_irqsave(&c_port->lock, flags);
+	list_add_tail(&cpkt->list, &c_port->cpkt_resp_q);
+	spin_unlock_irqrestore(&c_port->lock, flags);
+
+	if (!gsi_ctrl_send_notification(gsi))
+		c_port->modem_to_host++;
+
+	log_event_dbg("Exit %zu", count);
+
+	return ret ? ret : count;
+}
+
+static long gsi_ctrl_dev_ioctl(struct file *fp, unsigned int cmd,
+		unsigned long arg)
+{
+	struct gsi_ctrl_port *c_port;
+	struct f_gsi *gsi;
+	struct gsi_ctrl_pkt *cpkt;
+	struct ep_info info;
+	struct data_buf_info data_info = {0};
+	enum ipa_usb_teth_prot prot_id =
+		*(enum ipa_usb_teth_prot *)(fp->private_data);
+	struct gsi_inst_status *inst_cur = &inst_status[prot_id];
+	int val, ret = 0;
+	unsigned long flags;
+
+	mutex_lock(&inst_cur->gsi_lock);
+	if (unlikely(inst_cur->inst_exist == false)) {
+		mutex_unlock(&inst_cur->gsi_lock);
+		pr_err_ratelimited("%s: free_inst is called and being freed\n",
+								__func__);
+		return -ENODEV;
+	}
+	mutex_unlock(&inst_cur->gsi_lock);
+
+	gsi = inst_cur->opts->gsi;
+	c_port = &gsi->c_port;
+
+	if (!c_port) {
+		log_event_err("%s: gsi ctrl port %p", __func__, c_port);
+		return -ENODEV;
+	}
+
+	switch (cmd) {
+	case QTI_CTRL_MODEM_OFFLINE:
+		if (gsi->prot_id == IPA_USB_DIAG) {
+			log_event_dbg("%s:Modem Offline not handled", __func__);
+			goto exit_ioctl;
+		}
+		atomic_set(&c_port->ctrl_online, 0);
+		gsi_ctrl_clear_cpkt_queues(gsi, true);
+		cpkt = gsi_ctrl_pkt_alloc(0, GFP_KERNEL);
+		if (IS_ERR(cpkt)) {
+			log_event_err("%s: err allocating cpkt\n", __func__);
+			return -ENOMEM;
+		}
+		cpkt->type = GSI_CTRL_NOTIFY_OFFLINE;
+		spin_lock_irqsave(&c_port->lock, flags);
+		list_add_tail(&cpkt->list, &c_port->cpkt_resp_q);
+		spin_unlock_irqrestore(&c_port->lock, flags);
+		gsi_ctrl_send_notification(gsi);
+		break;
+	case QTI_CTRL_MODEM_ONLINE:
+		if (gsi->prot_id == IPA_USB_DIAG) {
+			log_event_dbg("%s:Modem Online not handled", __func__);
+			goto exit_ioctl;
+		}
+
+		atomic_set(&c_port->ctrl_online, 1);
+		break;
+	case QTI_CTRL_GET_LINE_STATE:
+		val = atomic_read(&gsi->connected);
+		if (gsi->prot_id == IPA_USB_RMNET)
+			val = gsi->rmnet_dtr_status;
+
+		ret = copy_to_user((void __user *)arg, &val, sizeof(val));
+		if (ret) {
+			log_event_err("copy_to_user fail LINE_STATE");
+			ret = -EFAULT;
+		}
+		log_event_dbg("%s: Sent line_state: %d for prot id:%d",
+				__func__, val, gsi->prot_id);
+		break;
+	case QTI_CTRL_EP_LOOKUP:
+	case GSI_MBIM_EP_LOOKUP:
+		log_event_dbg("%s: EP_LOOKUP for prot id:%d", __func__,
+							gsi->prot_id);
+		if (!atomic_read(&gsi->connected)) {
+			log_event_dbg("EP_LOOKUP failed: not connected");
+			ret = -EAGAIN;
+			break;
+		}
+
+		if (gsi->prot_id == IPA_USB_DIAG &&
+				(gsi->d_port.in_channel_handle == -EINVAL)) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		if (gsi->d_port.in_channel_handle == -EINVAL &&
+			gsi->d_port.out_channel_handle == -EINVAL) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		info.ph_ep_info.ep_type = GSI_MBIM_DATA_EP_TYPE_HSUSB;
+		info.ph_ep_info.peripheral_iface_id = gsi->data_id;
+		info.ipa_ep_pair.cons_pipe_num =
+		(gsi->prot_id == IPA_USB_DIAG) ? -1 :
+				gsi->d_port.out_channel_handle;
+		info.ipa_ep_pair.prod_pipe_num = gsi->d_port.in_channel_handle;
+
+		log_event_dbg("%s: prot id :%d ep_type:%d intf:%d",
+				__func__, gsi->prot_id, info.ph_ep_info.ep_type,
+				info.ph_ep_info.peripheral_iface_id);
+
+		log_event_dbg("%s: ipa_cons_idx:%d ipa_prod_idx:%d",
+				__func__, info.ipa_ep_pair.cons_pipe_num,
+				info.ipa_ep_pair.prod_pipe_num);
+
+		ret = copy_to_user((void __user *)arg, &info,
+			sizeof(info));
+		if (ret) {
+			log_event_err("copy_to_user fail MBIM");
+			ret = -EFAULT;
+		}
+		break;
+	case GSI_MBIM_GET_NTB_SIZE:
+		ret = copy_to_user((void __user *)arg,
+			&gsi->d_port.ntb_info.ntb_input_size,
+			sizeof(gsi->d_port.ntb_info.ntb_input_size));
+		if (ret) {
+			log_event_err("copy_to_user failNTB_SIZE");
+			ret = -EFAULT;
+		}
+		log_event_dbg("Sent NTB size %d",
+				gsi->d_port.ntb_info.ntb_input_size);
+		break;
+	case GSI_MBIM_GET_DATAGRAM_COUNT:
+		ret = copy_to_user((void __user *)arg,
+			&gsi->d_port.ntb_info.ntb_max_datagrams,
+			sizeof(gsi->d_port.ntb_info.ntb_max_datagrams));
+		if (ret) {
+			log_event_err("copy_to_user fail DATAGRAM");
+			ret = -EFAULT;
+		}
+		log_event_dbg("Sent NTB datagrams count %d",
+			gsi->d_port.ntb_info.ntb_max_datagrams);
+		break;
+	case QTI_CTRL_DATA_BUF_INFO:
+		if (gsi->d_port.out_ep) {
+			data_info.epout_buf_len =
+				gsi->d_port.out_request.buf_len;
+			data_info.epout_total_buf_len =
+				gsi->d_port.out_request.buf_len *
+				gsi->d_port.out_request.num_bufs;
+			log_event_dbg("prot id :%d OUT: buf_len:%u total_len: %u",
+				gsi->prot_id, data_info.epout_buf_len,
+				data_info.epout_total_buf_len);
+		}
+
+		if (gsi->d_port.in_ep) {
+			data_info.epin_buf_len =
+				gsi->d_port.in_request.buf_len;
+			data_info.epin_total_buf_len =
+				gsi->d_port.in_request.buf_len *
+				gsi->d_port.in_request.num_bufs;
+			log_event_dbg("prot id :%d IN: buf_len:%u total_len:%u\n",
+				gsi->prot_id, data_info.epin_buf_len,
+				data_info.epin_total_buf_len);
+		}
+
+		ret = copy_to_user((void __user *)arg, &data_info,
+			sizeof(data_info));
+		if (ret) {
+			log_event_err("QTI_CTRL_DATA_BUF_INFO: copy_to_user failed");
+			ret = -EFAULT;
+		}
+		break;
+	default:
+		log_event_err("wrong parameter");
+		ret = -EINVAL;
+	}
+
+exit_ioctl:
+	return ret;
+}
+
+static unsigned int gsi_ctrl_dev_poll(struct file *fp, poll_table *wait)
+{
+	struct gsi_ctrl_port *c_port;
+	enum ipa_usb_teth_prot prot_id =
+		*(enum ipa_usb_teth_prot *)(fp->private_data);
+	struct gsi_inst_status *inst_cur = &inst_status[prot_id];
+	struct f_gsi *gsi;
+	unsigned long flags;
+	unsigned int mask = 0;
+
+	mutex_lock(&inst_cur->gsi_lock);
+	if (unlikely(inst_cur->inst_exist == false)) {
+		mutex_unlock(&inst_cur->gsi_lock);
+		pr_err_ratelimited("%s: free_inst is called and being freed\n",
+								__func__);
+		return -ENODEV;
+	}
+	mutex_unlock(&inst_cur->gsi_lock);
+
+	gsi = inst_cur->opts->gsi;
+	c_port = &inst_cur->opts->gsi->c_port;
+	if (!c_port) {
+		log_event_err("%s: gsi ctrl port %p", __func__, c_port);
+		return -ENODEV;
+	}
+
+	poll_wait(fp, &c_port->read_wq, wait);
+
+	spin_lock_irqsave(&c_port->lock, flags);
+	if (!list_empty(&c_port->cpkt_req_q)) {
+		mask |= POLLIN | POLLRDNORM;
+		log_event_dbg("%s sets POLLIN for %s", __func__, c_port->name);
+	}
+	spin_unlock_irqrestore(&c_port->lock, flags);
+
+	return mask;
+}
+
+/* file operations for rmnet/mbim/dpl devices */
+static const struct file_operations gsi_ctrl_dev_fops = {
+	.owner = THIS_MODULE,
+	.open = gsi_ctrl_dev_open,
+	.release = gsi_ctrl_dev_release,
+	.read = gsi_ctrl_dev_read,
+	.write = gsi_ctrl_dev_write,
+	.unlocked_ioctl = gsi_ctrl_dev_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = gsi_ctrl_dev_ioctl,
+#endif
+	.poll = gsi_ctrl_dev_poll,
+};
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static unsigned int gsi_xfer_bitrate(struct usb_gadget *g)
+{
+	if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER)
+		return 13 * 1024 * 8 * 1000 * 8;
+	else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+		return 13 * 512 * 8 * 1000 * 8;
+	else
+		return 19 * 64 * 1 * 1000 * 8;
+}
+
+static int gsi_function_ctrl_port_init(struct f_gsi *gsi)
+{
+	int ret;
+	int sz = GSI_CTRL_NAME_LEN;
+	int minor;
+	struct device *dev;
+	bool ctrl_dev_create = true;
+
+	if (!gsi) {
+		log_event_err("%s: gsi prot ctx is NULL", __func__);
+		return -EINVAL;
+	}
+
+	INIT_LIST_HEAD(&gsi->c_port.cpkt_req_q);
+	INIT_LIST_HEAD(&gsi->c_port.cpkt_resp_q);
+
+	spin_lock_init(&gsi->c_port.lock);
+
+	init_waitqueue_head(&gsi->c_port.read_wq);
+
+	if (gsi->prot_id == IPA_USB_RMNET)
+		strlcat(gsi->c_port.name, GSI_RMNET_CTRL_NAME, sz);
+	else if (gsi->prot_id == IPA_USB_MBIM)
+		strlcat(gsi->c_port.name, GSI_MBIM_CTRL_NAME, sz);
+	else if (gsi->prot_id == IPA_USB_DIAG)
+		strlcat(gsi->c_port.name, GSI_DPL_CTRL_NAME, sz);
+	else
+		ctrl_dev_create = false;
+
+	if (!ctrl_dev_create)
+		return 0;
+
+	minor = ida_simple_get(&gsi_ida, 0, MAX_CDEV_INSTANCES, GFP_KERNEL);
+	if (minor < 0) {
+		pr_err("%s: No more minor numbers left! rc:%d\n", __func__,
+				minor);
+		return minor;
+	}
+
+	cdev_init(&gsi->c_port.cdev, &gsi_ctrl_dev_fops);
+	ret = cdev_add(&gsi->c_port.cdev, MKDEV(major, minor), 1);
+	if (ret) {
+		log_event_err("%s: Failed to add cdev for (%s)\n", __func__,
+				gsi->c_port.name);
+		goto err_cdev_add;
+	}
+
+	dev = device_create(gsi_class, NULL, MKDEV(major, minor), NULL,
+			gsi->c_port.name);
+	if (IS_ERR(dev)) {
+		log_event_err("%s: device_create failed for (%s)\n", __func__,
+				gsi->c_port.name);
+		ret = PTR_ERR(dev);
+		goto err_create_dev;
+	}
+
+	return 0;
+
+err_create_dev:
+	cdev_del(&gsi->c_port.cdev);
+err_cdev_add:
+	ida_simple_remove(&gsi_ida, minor);
+	return ret;
+}
+
+static struct net_device *gsi_rndis_get_netdev(const char *netname)
+{
+	struct net_device *net_dev;
+
+	net_dev = dev_get_by_name(&init_net, netname);
+	if (!net_dev)
+		return ERR_PTR(-EINVAL);
+
+	/*
+	 * Decrement net_dev refcount as it was incremented in
+	 * dev_get_by_name().
+	 */
+	dev_put(net_dev);
+	return net_dev;
+}
+
+static void gsi_rndis_open(struct f_gsi *gsi)
+{
+	struct usb_composite_dev *cdev = gsi->function.config->cdev;
+
+	log_event_dbg("%s", __func__);
+
+	rndis_set_param_medium(gsi->params, RNDIS_MEDIUM_802_3,
+				gsi_xfer_bitrate(cdev->gadget) / 100);
+	rndis_signal_connect(gsi->params);
+}
+
+static void gsi_rndis_ipa_reset_trigger(struct gsi_data_port *d_port)
+{
+	unsigned long flags;
+	struct f_gsi *gsi = d_port_to_gsi(d_port);
+
+	log_event_dbg("%s: setting net_ready_trigger\n", __func__);
+	spin_lock_irqsave(&d_port->lock, flags);
+	d_port->net_ready_trigger = false;
+	spin_unlock_irqrestore(&d_port->lock, flags);
+}
+
+void gsi_rndis_flow_ctrl_enable(bool enable, struct rndis_params *param)
+{
+	struct f_gsi *gsi = param->v;
+	struct gsi_data_port *d_port;
+
+	if (!gsi) {
+		pr_err("%s: gsi prot ctx is %p", __func__, gsi);
+		return;
+	}
+
+	d_port = &gsi->d_port;
+	if (enable) {
+		log_event_dbg("%s: posting HOST_NRDY\n", __func__);
+		post_event(d_port, EVT_HOST_NRDY);
+	} else {
+		log_event_dbg("%s: posting HOST_READY\n", __func__);
+		post_event(d_port, EVT_HOST_READY);
+	}
+
+	queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+}
+
+static int queue_notification_request(struct f_gsi *gsi)
+{
+	int ret;
+	unsigned long flags;
+
+	ret = usb_func_ep_queue(&gsi->function, gsi->c_port.notify,
+			   gsi->c_port.notify_req, GFP_ATOMIC);
+	if (ret < 0) {
+		spin_lock_irqsave(&gsi->c_port.lock, flags);
+		gsi->c_port.notify_req_queued = false;
+		spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+	}
+
+	log_event_dbg("%s: ret:%d req_queued:%d",
+		__func__, ret, gsi->c_port.notify_req_queued);
+
+	return ret;
+}
+
+static int gsi_ctrl_send_notification(struct f_gsi *gsi)
+{
+	__le32 *data;
+	struct usb_cdc_notification *event;
+	struct usb_request *req = gsi->c_port.notify_req;
+	struct usb_composite_dev *cdev = gsi->function.config->cdev;
+	struct gsi_ctrl_pkt *cpkt;
+	unsigned long flags;
+	bool del_free_cpkt = false;
+
+	if (!atomic_read(&gsi->connected)) {
+		log_event_dbg("%s: cable disconnect", __func__);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&gsi->c_port.lock, flags);
+	if (list_empty(&gsi->c_port.cpkt_resp_q)) {
+		spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+		log_event_dbg("%s: cpkt_resp_q is empty\n", __func__);
+		return 0;
+	}
+
+	log_event_dbg("%s: notify_req_queued:%d\n",
+		__func__, gsi->c_port.notify_req_queued);
+
+	if (gsi->c_port.notify_req_queued) {
+		spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+		log_event_dbg("%s: notify_req is already queued.\n", __func__);
+		return 0;
+	}
+
+	cpkt = list_first_entry(&gsi->c_port.cpkt_resp_q,
+			struct gsi_ctrl_pkt, list);
+	log_event_dbg("%s: cpkt->type:%d\n", __func__, cpkt->type);
+
+	event = req->buf;
+
+	switch (cpkt->type) {
+	case GSI_CTRL_NOTIFY_CONNECT:
+		del_free_cpkt = true;
+		event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+		event->wValue = cpu_to_le16(1);
+		event->wLength = cpu_to_le16(0);
+		break;
+	case GSI_CTRL_NOTIFY_SPEED:
+		del_free_cpkt = true;
+		event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
+		event->wValue = cpu_to_le16(0);
+		event->wLength = cpu_to_le16(8);
+
+		/* SPEED_CHANGE data is up/down speeds in bits/sec */
+		data = req->buf + sizeof(*event);
+		data[0] = cpu_to_le32(gsi_xfer_bitrate(cdev->gadget));
+		data[1] = data[0];
+
+		log_event_dbg("notify speed %d",
+				gsi_xfer_bitrate(cdev->gadget));
+		break;
+	case GSI_CTRL_NOTIFY_OFFLINE:
+		del_free_cpkt = true;
+		event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+		event->wValue = cpu_to_le16(0);
+		event->wLength = cpu_to_le16(0);
+		break;
+	case GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE:
+		event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE;
+		event->wValue = cpu_to_le16(0);
+		event->wLength = cpu_to_le16(0);
+
+		if (gsi->prot_id == IPA_USB_RNDIS) {
+			data = req->buf;
+			data[0] = cpu_to_le32(1);
+			data[1] = cpu_to_le32(0);
+			/*
+			 * we need to free dummy packet for RNDIS as sending
+			 * notification about response available multiple time,
+			 * RNDIS host driver doesn't like. All SEND/GET
+			 * ENCAPSULATED response is one-to-one for RNDIS case
+			 * and host expects to have below sequence:
+			 * ep0: USB_CDC_SEND_ENCAPSULATED_COMMAND
+			 * int_ep: device->host: RESPONSE_AVAILABLE
+			 * ep0: USB_GET_SEND_ENCAPSULATED_COMMAND
+			 * For RMNET case: host ignores multiple notification.
+			 */
+			del_free_cpkt = true;
+		}
+		break;
+	default:
+		spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+		log_event_err("%s:unknown notify state", __func__);
+		WARN_ON(1);
+		return -EINVAL;
+	}
+
+	/*
+	 * Delete and free cpkt related to non NOTIFY_RESPONSE_AVAILABLE
+	 * notification whereas NOTIFY_RESPONSE_AVAILABLE related cpkt is
+	 * deleted from USB_CDC_GET_ENCAPSULATED_RESPONSE setup request
+	 */
+	if (del_free_cpkt) {
+		list_del(&cpkt->list);
+		gsi_ctrl_pkt_free(cpkt);
+	}
+
+	gsi->c_port.notify_req_queued = true;
+	spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+	log_event_dbg("send Notify type %02x", event->bNotificationType);
+
+	return queue_notification_request(gsi);
+}
+
+static void gsi_ctrl_notify_resp_complete(struct usb_ep *ep,
+					struct usb_request *req)
+{
+	struct f_gsi *gsi = req->context;
+	struct usb_cdc_notification *event = req->buf;
+	int status = req->status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gsi->c_port.lock, flags);
+	gsi->c_port.notify_req_queued = false;
+	spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+
+	switch (status) {
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		log_event_dbg("ESHUTDOWN/ECONNRESET, connection gone");
+		gsi_ctrl_clear_cpkt_queues(gsi, false);
+		gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0);
+		break;
+	default:
+		log_event_err("Unknown event %02x --> %d",
+			event->bNotificationType, req->status);
+		/* FALLTHROUGH */
+	case 0:
+		break;
+	}
+}
+
+static void gsi_rndis_response_available(void *_rndis)
+{
+	struct f_gsi *gsi = _rndis;
+	struct gsi_ctrl_pkt *cpkt;
+	unsigned long flags;
+
+	cpkt = gsi_ctrl_pkt_alloc(0, GFP_ATOMIC);
+	if (IS_ERR(cpkt)) {
+		log_event_err("%s: err allocating cpkt\n", __func__);
+		return;
+	}
+
+	cpkt->type = GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE;
+	spin_lock_irqsave(&gsi->c_port.lock, flags);
+	list_add_tail(&cpkt->list, &gsi->c_port.cpkt_resp_q);
+	spin_unlock_irqrestore(&gsi->c_port.lock, flags);
+	gsi_ctrl_send_notification(gsi);
+}
+
+static void gsi_rndis_command_complete(struct usb_ep *ep,
+		struct usb_request *req)
+{
+	struct f_gsi *gsi = req->context;
+	rndis_init_msg_type *buf;
+	int status;
+
+	status = rndis_msg_parser(gsi->params, (u8 *) req->buf);
+	if (status < 0)
+		log_event_err("RNDIS command error %d, %d/%d",
+			status, req->actual, req->length);
+
+	buf = (rndis_init_msg_type *)req->buf;
+	if (buf->MessageType == RNDIS_MSG_INIT) {
+		/* honor host dl aggr size */
+		gsi->d_port.in_aggr_size = gsi->params->dl_max_xfer_size;
+		log_event_dbg("RNDIS host dl_aggr_size:%d\n",
+				gsi->params->dl_max_xfer_size);
+	}
+}
+
+static void
+gsi_ctrl_set_ntb_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	/* now for SET_NTB_INPUT_SIZE only */
+	unsigned int in_size = 0;
+	struct f_gsi *gsi = req->context;
+	struct gsi_ntb_info *ntb = NULL;
+
+	log_event_dbg("dev:%p", gsi);
+
+	req->context = NULL;
+	if (req->status || req->actual != req->length) {
+		log_event_err("Bad control-OUT transfer");
+		goto invalid;
+	}
+
+	if (req->length == 4) {
+		in_size = get_unaligned_le32(req->buf);
+		if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE ||
+		in_size > le32_to_cpu(mbim_gsi_ntb_parameters.dwNtbInMaxSize))
+			goto invalid;
+	} else if (req->length == 8) {
+		ntb = (struct gsi_ntb_info *)req->buf;
+		in_size = get_unaligned_le32(&(ntb->ntb_input_size));
+		if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE ||
+		in_size > le32_to_cpu(mbim_gsi_ntb_parameters.dwNtbInMaxSize))
+			goto invalid;
+
+		gsi->d_port.ntb_info.ntb_max_datagrams =
+			get_unaligned_le16(&(ntb->ntb_max_datagrams));
+	} else {
+		goto invalid;
+	}
+
+	log_event_dbg("Set NTB INPUT SIZE %d", in_size);
+
+	gsi->d_port.ntb_info.ntb_input_size = in_size;
+	return;
+
+invalid:
+	log_event_err("Illegal NTB INPUT SIZE %d from host", in_size);
+	usb_ep_set_halt(ep);
+}
+
+static void gsi_ctrl_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_gsi *gsi = req->context;
+
+	gsi_ctrl_send_cpkt_tomodem(gsi, req->buf, req->actual);
+}
+
+static void gsi_ctrl_reset_cmd_complete(struct usb_ep *ep,
+		struct usb_request *req)
+{
+	struct f_gsi *gsi = req->context;
+
+	gsi_ctrl_send_cpkt_tomodem(gsi, req->buf, 0);
+}
+
+static void gsi_ctrl_send_response_complete(struct usb_ep *ep,
+		struct usb_request *req)
+{
+	struct f_gsi *gsi = req->context;
+
+	gsi_ctrl_send_notification(gsi);
+}
+
+static int
+gsi_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct f_gsi *gsi = func_to_gsi(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request *req = cdev->req;
+	int id, value = -EOPNOTSUPP;
+	u16 w_index = le16_to_cpu(ctrl->wIndex);
+	u16 w_value = le16_to_cpu(ctrl->wValue);
+	u16 w_length = le16_to_cpu(ctrl->wLength);
+	struct gsi_ctrl_pkt *cpkt;
+	u8 *buf;
+	u32 n;
+	bool line_state;
+
+	if (!atomic_read(&gsi->connected)) {
+		log_event_dbg("usb cable is not connected");
+		return -ENOTCONN;
+	}
+
+	/* rmnet and dpl does not have ctrl_id */
+	if (gsi->ctrl_id == -ENODEV)
+		id = gsi->data_id;
+	else
+		id = gsi->ctrl_id;
+
+	/* composite driver infrastructure handles everything except
+	 * CDC class messages; interface activation uses set_alt().
+	 */
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_RESET_FUNCTION:
+
+		log_event_dbg("USB_CDC_RESET_FUNCTION");
+		value = 0;
+		req->complete = gsi_ctrl_reset_cmd_complete;
+		req->context = gsi;
+		break;
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SEND_ENCAPSULATED_COMMAND:
+		log_event_dbg("USB_CDC_SEND_ENCAPSULATED_COMMAND");
+
+		if (w_value || w_index != id)
+			goto invalid;
+		/* read the request; process it later */
+		value = w_length;
+		req->context = gsi;
+		if (gsi->prot_id == IPA_USB_RNDIS)
+			req->complete = gsi_rndis_command_complete;
+		else
+			req->complete = gsi_ctrl_cmd_complete;
+		/* later, rndis_response_available() sends a notification */
+		break;
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_GET_ENCAPSULATED_RESPONSE:
+		log_event_dbg("USB_CDC_GET_ENCAPSULATED_RESPONSE");
+		if (w_value || w_index != id)
+			goto invalid;
+
+		if (gsi->prot_id == IPA_USB_RNDIS) {
+			/* return the result */
+			buf = rndis_get_next_response(gsi->params, &n);
+			if (buf) {
+				memcpy(req->buf, buf, n);
+				rndis_free_response(gsi->params, buf);
+				value = n;
+			}
+			break;
+		}
+
+		spin_lock(&gsi->c_port.lock);
+		if (list_empty(&gsi->c_port.cpkt_resp_q)) {
+			log_event_dbg("ctrl resp queue empty");
+			spin_unlock(&gsi->c_port.lock);
+			break;
+		}
+
+		cpkt = list_first_entry(&gsi->c_port.cpkt_resp_q,
+					struct gsi_ctrl_pkt, list);
+		list_del(&cpkt->list);
+		gsi->c_port.get_encap_cnt++;
+		spin_unlock(&gsi->c_port.lock);
+
+		value = min_t(unsigned int, w_length, cpkt->len);
+		memcpy(req->buf, cpkt->buf, value);
+		gsi_ctrl_pkt_free(cpkt);
+
+		req->complete = gsi_ctrl_send_response_complete;
+		req->context = gsi;
+		log_event_dbg("copied encap_resp %d bytes",
+			value);
+		break;
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_SET_CONTROL_LINE_STATE:
+		line_state = (w_value & GSI_CTRL_DTR ? true : false);
+		if (gsi->prot_id == IPA_USB_RMNET)
+			gsi->rmnet_dtr_status = line_state;
+		log_event_dbg("%s: USB_CDC_REQ_SET_CONTROL_LINE_STATE DTR:%d\n",
+						__func__, line_state);
+		gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0);
+		value = 0;
+		break;
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SET_ETHERNET_PACKET_FILTER:
+		/* see 6.2.30: no data, wIndex = interface,
+		 * wValue = packet filter bitmap
+		 */
+		if (w_length != 0 || w_index != id)
+			goto invalid;
+		log_event_dbg("packet filter %02x", w_value);
+		/* REVISIT locking of cdc_filter.  This assumes the UDC
+		 * driver won't have a concurrent packet TX irq running on
+		 * another CPU; or that if it does, this write is atomic...
+		 */
+		gsi->d_port.cdc_filter = w_value;
+		value = 0;
+		break;
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_PARAMETERS:
+		log_event_dbg("USB_CDC_GET_NTB_PARAMETERS");
+
+		if (w_length == 0 || w_value != 0 || w_index != id)
+			break;
+
+		value = w_length > sizeof(mbim_gsi_ntb_parameters) ?
+			sizeof(mbim_gsi_ntb_parameters) : w_length;
+		memcpy(req->buf, &mbim_gsi_ntb_parameters, value);
+		break;
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_GET_NTB_INPUT_SIZE:
+
+		log_event_dbg("USB_CDC_GET_NTB_INPUT_SIZE");
+
+		if (w_length < 4 || w_value != 0 || w_index != id)
+			break;
+
+		put_unaligned_le32(gsi->d_port.ntb_info.ntb_input_size,
+				req->buf);
+		value = 4;
+		log_event_dbg("Reply to host INPUT SIZE %d",
+			 gsi->d_port.ntb_info.ntb_input_size);
+		break;
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+		| USB_CDC_SET_NTB_INPUT_SIZE:
+		log_event_dbg("USB_CDC_SET_NTB_INPUT_SIZE");
+
+		if (w_length != 4 && w_length != 8) {
+			log_event_err("wrong NTB length %d", w_length);
+			break;
+		}
+
+		if (w_value != 0 || w_index != id)
+			break;
+
+		req->complete = gsi_ctrl_set_ntb_cmd_complete;
+		req->length = w_length;
+		req->context = gsi;
+
+		value = req->length;
+		break;
+	default:
+invalid:
+		log_event_err("inval ctrl req%02x.%02x v%04x i%04x l%d",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (value >= 0) {
+		log_event_dbg("req%02x.%02x v%04x i%04x l%d",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = (value < w_length);
+		req->length = value;
+		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0)
+			log_event_err("response on err %d", value);
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+/*
+ * Because the data interface supports multiple altsettings,
+ * function *MUST* implement a get_alt() method.
+ */
+static int gsi_get_alt(struct usb_function *f, unsigned int intf)
+{
+	struct f_gsi *gsi = func_to_gsi(f);
+
+	/* RNDIS, RMNET and DPL only support alt 0*/
+	if (intf == gsi->ctrl_id || gsi->prot_id == IPA_USB_RNDIS ||
+			gsi->prot_id == IPA_USB_RMNET ||
+			gsi->prot_id == IPA_USB_DIAG)
+		return 0;
+	else if (intf == gsi->data_id)
+		return gsi->data_interface_up;
+
+	return -EINVAL;
+}
+
+static int gsi_alloc_trb_buffer(struct f_gsi *gsi)
+{
+	u32 len_in = 0, len_out = 0;
+	int ret = 0;
+	struct device *dev;
+
+	log_event_dbg("allocate trb's buffer\n");
+
+	dev = gsi->d_port.gadget->dev.parent;
+	if (gsi->d_port.in_ep && !gsi->d_port.in_request.buf_base_addr) {
+		log_event_dbg("IN: num_bufs:=%zu, buf_len=%zu\n",
+			gsi->d_port.in_request.num_bufs,
+			gsi->d_port.in_request.buf_len);
+
+		len_in = gsi->d_port.in_request.buf_len *
+				gsi->d_port.in_request.num_bufs;
+		gsi->d_port.in_request.buf_base_addr =
+			dma_zalloc_coherent(dev->parent,
+			len_in, &gsi->d_port.in_request.dma, GFP_KERNEL);
+		if (!gsi->d_port.in_request.buf_base_addr) {
+			dev_err(&gsi->d_port.gadget->dev,
+					"IN buf_base_addr allocate failed %s\n",
+					gsi->function.name);
+			ret = -ENOMEM;
+			goto fail1;
+		}
+
+		dma_get_sgtable(dev->parent,
+			&gsi->d_port.in_request.sgt_data_buff,
+			gsi->d_port.in_request.buf_base_addr,
+			gsi->d_port.in_request.dma, len_in);
+	}
+
+	if (gsi->d_port.out_ep && !gsi->d_port.out_request.buf_base_addr) {
+		log_event_dbg("OUT: num_bufs:=%zu, buf_len=%zu\n",
+			gsi->d_port.out_request.num_bufs,
+			gsi->d_port.out_request.buf_len);
+
+		len_out = gsi->d_port.out_request.buf_len *
+				gsi->d_port.out_request.num_bufs;
+		gsi->d_port.out_request.buf_base_addr =
+			dma_zalloc_coherent(dev->parent,
+			len_out, &gsi->d_port.out_request.dma, GFP_KERNEL);
+		if (!gsi->d_port.out_request.buf_base_addr) {
+			dev_err(&gsi->d_port.gadget->dev,
+					"OUT buf_base_addr allocate failed %s\n",
+					gsi->function.name);
+			ret = -ENOMEM;
+			goto fail;
+		}
+
+		dma_get_sgtable(dev->parent,
+			&gsi->d_port.out_request.sgt_data_buff,
+			gsi->d_port.out_request.buf_base_addr,
+			gsi->d_port.out_request.dma, len_out);
+	}
+
+	log_event_dbg("finished allocating trb's buffer\n");
+	return ret;
+
+fail:
+	if (len_in && gsi->d_port.in_request.buf_base_addr) {
+		dma_free_coherent(dev->parent, len_in,
+				gsi->d_port.in_request.buf_base_addr,
+				gsi->d_port.in_request.dma);
+		gsi->d_port.in_request.buf_base_addr = NULL;
+	}
+fail1:
+	return ret;
+}
+
+static void gsi_free_trb_buffer(struct f_gsi *gsi)
+{
+	u32 len;
+
+	log_event_dbg("freeing trb's buffer\n");
+
+	if (gsi->d_port.out_ep &&
+			gsi->d_port.out_request.buf_base_addr) {
+		len = gsi->d_port.out_request.buf_len *
+			gsi->d_port.out_request.num_bufs;
+		dma_free_coherent(gsi->d_port.gadget->dev.parent->parent, len,
+			gsi->d_port.out_request.buf_base_addr,
+			gsi->d_port.out_request.dma);
+		gsi->d_port.out_request.buf_base_addr = NULL;
+		sg_free_table(&gsi->d_port.out_request.sgt_data_buff);
+	}
+
+	if (gsi->d_port.in_ep &&
+			gsi->d_port.in_request.buf_base_addr) {
+		len = gsi->d_port.in_request.buf_len *
+			gsi->d_port.in_request.num_bufs;
+		dma_free_coherent(gsi->d_port.gadget->dev.parent->parent, len,
+			gsi->d_port.in_request.buf_base_addr,
+			gsi->d_port.in_request.dma);
+		gsi->d_port.in_request.buf_base_addr = NULL;
+		sg_free_table(&gsi->d_port.in_request.sgt_data_buff);
+	}
+}
+
+static int gsi_set_alt(struct usb_function *f, unsigned int intf,
+						unsigned int alt)
+{
+	struct f_gsi	 *gsi = func_to_gsi(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct net_device	*net;
+	int ret;
+
+	log_event_dbg("intf=%u, alt=%u", intf, alt);
+
+	/* Control interface has only altsetting 0 */
+	if (intf == gsi->ctrl_id || gsi->prot_id == IPA_USB_RMNET) {
+		if (alt != 0)
+			goto fail;
+
+		if (!gsi->c_port.notify)
+			goto fail;
+
+		if (gsi->c_port.notify->driver_data) {
+			log_event_dbg("reset gsi control %d", intf);
+			usb_ep_disable(gsi->c_port.notify);
+		}
+
+		ret = config_ep_by_speed(cdev->gadget, f,
+					gsi->c_port.notify);
+		if (ret) {
+			gsi->c_port.notify->desc = NULL;
+			log_event_err("Config-fail notify ep %s: err %d",
+				gsi->c_port.notify->name, ret);
+			goto fail;
+		}
+
+		ret = usb_ep_enable(gsi->c_port.notify);
+		if (ret) {
+			log_event_err("usb ep#%s enable failed, err#%d",
+				gsi->c_port.notify->name, ret);
+			goto fail;
+		}
+		gsi->c_port.notify->driver_data = gsi;
+	}
+
+	/* Data interface has two altsettings, 0 and 1 */
+	if (intf == gsi->data_id) {
+		gsi->d_port.net_ready_trigger = false;
+		/* for rndis and rmnet alt is always 0 update alt accordingly */
+		if (gsi->prot_id == IPA_USB_RNDIS ||
+				gsi->prot_id == IPA_USB_RMNET ||
+				gsi->prot_id == IPA_USB_DIAG) {
+			if (gsi->d_port.in_ep &&
+				!gsi->d_port.in_ep->driver_data)
+				alt = 1;
+			else
+				alt = 0;
+		}
+
+		if (alt > 1)
+			goto notify_ep_disable;
+
+		if (gsi->data_interface_up == alt)
+			return 0;
+
+		if (gsi->d_port.in_ep && gsi->d_port.in_ep->driver_data)
+			gsi->d_port.ntb_info.ntb_input_size =
+				MBIM_NTB_DEFAULT_IN_SIZE;
+		if (alt == 1) {
+			if (gsi->d_port.in_ep && !gsi->d_port.in_ep->desc
+				&& config_ep_by_speed(cdev->gadget, f,
+					gsi->d_port.in_ep)) {
+				gsi->d_port.in_ep->desc = NULL;
+				goto notify_ep_disable;
+			}
+
+			if (gsi->d_port.out_ep && !gsi->d_port.out_ep->desc
+				&& config_ep_by_speed(cdev->gadget, f,
+					gsi->d_port.out_ep)) {
+				gsi->d_port.out_ep->desc = NULL;
+				goto notify_ep_disable;
+			}
+
+			/* Configure EPs for GSI */
+			if (gsi->d_port.in_ep) {
+				if (gsi->prot_id == IPA_USB_DIAG)
+					gsi->d_port.in_ep->ep_intr_num = 3;
+				else
+					gsi->d_port.in_ep->ep_intr_num = 2;
+				usb_gsi_ep_op(gsi->d_port.in_ep,
+					&gsi->d_port.in_request,
+						GSI_EP_OP_CONFIG);
+			}
+
+			if (gsi->d_port.out_ep) {
+				gsi->d_port.out_ep->ep_intr_num = 1;
+				usb_gsi_ep_op(gsi->d_port.out_ep,
+					&gsi->d_port.out_request,
+						GSI_EP_OP_CONFIG);
+			}
+
+			gsi->d_port.gadget = cdev->gadget;
+
+			if (gsi->prot_id == IPA_USB_RNDIS) {
+				gsi_rndis_open(gsi);
+				net = gsi_rndis_get_netdev("rndis0");
+				if (IS_ERR(net))
+					goto notify_ep_disable;
+
+				log_event_dbg("RNDIS RX/TX early activation");
+				gsi->d_port.cdc_filter = 0;
+				rndis_set_param_dev(gsi->params, net,
+						&gsi->d_port.cdc_filter);
+			}
+
+			if (gsi->prot_id == IPA_USB_ECM)
+				gsi->d_port.cdc_filter = DEFAULT_FILTER;
+
+			/*
+			 * For RNDIS the event is posted from the flow control
+			 * handler which is invoked when the host sends the
+			 * GEN_CURRENT_PACKET_FILTER message.
+			 */
+			if (gsi->prot_id != IPA_USB_RNDIS)
+				post_event(&gsi->d_port,
+						EVT_CONNECT_IN_PROGRESS);
+			queue_work(gsi->d_port.ipa_usb_wq,
+					&gsi->d_port.usb_ipa_w);
+		}
+
+		if (alt == 0 && ((gsi->d_port.in_ep &&
+				!gsi->d_port.in_ep->driver_data) ||
+				(gsi->d_port.out_ep &&
+				!gsi->d_port.out_ep->driver_data)))
+			ipa_disconnect_handler(&gsi->d_port);
+
+		gsi->data_interface_up = alt;
+		log_event_dbg("DATA_INTERFACE id = %d, status = %d",
+				gsi->data_id, gsi->data_interface_up);
+	}
+
+	atomic_set(&gsi->connected, 1);
+
+	/* send 0 len pkt to qti to notify state change */
+	if (gsi->prot_id == IPA_USB_DIAG)
+		gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0);
+
+	return 0;
+
+notify_ep_disable:
+	if (gsi->c_port.notify && gsi->c_port.notify->driver_data)
+		usb_ep_disable(gsi->c_port.notify);
+fail:
+	return -EINVAL;
+}
+
+static void gsi_disable(struct usb_function *f)
+{
+	struct f_gsi *gsi = func_to_gsi(f);
+
+	atomic_set(&gsi->connected, 0);
+
+	if (gsi->prot_id == IPA_USB_RNDIS)
+		rndis_uninit(gsi->params);
+
+	if (gsi->prot_id == IPA_USB_RMNET)
+		gsi->rmnet_dtr_status = false;
+
+	/* Disable Control Path */
+	if (gsi->c_port.notify &&
+		gsi->c_port.notify->driver_data) {
+		usb_ep_disable(gsi->c_port.notify);
+		gsi->c_port.notify->driver_data = NULL;
+	}
+
+	gsi_ctrl_clear_cpkt_queues(gsi, false);
+	/* send 0 len pkt to qti/qbi to notify state change */
+	gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0);
+	gsi->c_port.notify_req_queued = false;
+	/* Disable Data Path  - only if it was initialized already (alt=1) */
+	if (!gsi->data_interface_up) {
+		log_event_dbg("%s: data intf is closed", __func__);
+		return;
+	}
+
+	gsi->data_interface_up = false;
+
+	log_event_dbg("%s deactivated", gsi->function.name);
+	ipa_disconnect_handler(&gsi->d_port);
+	post_event(&gsi->d_port, EVT_DISCONNECTED);
+	queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+}
+
+static void gsi_suspend(struct usb_function *f)
+{
+	bool block_db;
+	struct f_gsi *gsi = func_to_gsi(f);
+
+	/* Check if function is already suspended in gsi_func_suspend() */
+	if (f->func_is_suspended) {
+		log_event_dbg("%s: func already suspended, return\n", __func__);
+		return;
+	}
+
+	block_db = true;
+	usb_gsi_ep_op(gsi->d_port.in_ep, (void *)&block_db,
+			GSI_EP_OP_SET_CLR_BLOCK_DBL);
+	post_event(&gsi->d_port, EVT_SUSPEND);
+	queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+	log_event_dbg("gsi suspended");
+}
+
+static void gsi_resume(struct usb_function *f)
+{
+	struct f_gsi *gsi = func_to_gsi(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	log_event_dbg("%s", __func__);
+
+	/*
+	 * If the function is in USB3 Function Suspend state, resume is
+	 * canceled. In this case resume is done by a Function Resume request.
+	 */
+	if ((cdev->gadget->speed == USB_SPEED_SUPER) &&
+		f->func_is_suspended)
+		return;
+
+	if (gsi->c_port.notify && !gsi->c_port.notify->desc)
+		config_ep_by_speed(cdev->gadget, f, gsi->c_port.notify);
+
+	/* Check any pending cpkt, and queue immediately on resume */
+	gsi_ctrl_send_notification(gsi);
+
+	/*
+	 * Linux host does not send RNDIS_MSG_INIT or non-zero
+	 * RNDIS_MESSAGE_PACKET_FILTER after performing bus resume.
+	 * Trigger state machine explicitly on resume.
+	 */
+	if (gsi->prot_id == IPA_USB_RNDIS &&
+			!usb_gsi_remote_wakeup_allowed(f))
+		rndis_flow_control(gsi->params, false);
+
+	post_event(&gsi->d_port, EVT_RESUMED);
+	queue_work(gsi->d_port.ipa_usb_wq, &gsi->d_port.usb_ipa_w);
+
+	log_event_dbg("%s: completed", __func__);
+}
+
+static int gsi_get_status(struct usb_function *f)
+{
+	unsigned int remote_wakeup_en_status = f->func_wakeup_allowed ? 1 : 0;
+
+	return (remote_wakeup_en_status << FUNC_WAKEUP_ENABLE_SHIFT) |
+		(1 << FUNC_WAKEUP_CAPABLE_SHIFT);
+}
+
+static int gsi_func_suspend(struct usb_function *f, u8 options)
+{
+	bool func_wakeup_allowed;
+	struct f_gsi *gsi = func_to_gsi(f);
+
+	log_event_dbg("func susp %u cmd for %s",
+		options, f->name ? f->name : "");
+
+	func_wakeup_allowed =
+		((options & FUNC_SUSPEND_OPT_RW_EN_MASK) != 0);
+
+	if (options & FUNC_SUSPEND_OPT_SUSP_MASK) {
+		f->func_wakeup_allowed = func_wakeup_allowed;
+		if (!f->func_is_suspended) {
+			gsi_suspend(f);
+			f->func_is_suspended = true;
+		}
+	} else {
+		if (f->func_is_suspended) {
+			f->func_is_suspended = false;
+			gsi_resume(f);
+		}
+		f->func_wakeup_allowed = func_wakeup_allowed;
+	}
+
+	return 0;
+}
+
+static int gsi_update_function_bind_params(struct f_gsi *gsi,
+	struct usb_composite_dev *cdev,
+	struct gsi_function_bind_info *info)
+{
+	struct usb_ep *ep;
+	struct usb_cdc_notification *event;
+	struct usb_function *f = &gsi->function;
+	int status;
+
+	/* maybe allocate device-global string IDs */
+	if (info->string_defs[0].id != 0)
+		goto skip_string_id_alloc;
+
+	if (info->ctrl_str_idx >= 0 && info->ctrl_desc) {
+		/* ctrl interface label */
+		status = usb_string_id(cdev);
+		if (status < 0)
+			return status;
+		info->string_defs[info->ctrl_str_idx].id = status;
+		info->ctrl_desc->iInterface = status;
+	}
+
+	if (info->data_str_idx >= 0 && info->data_desc) {
+		/* data interface label */
+		status = usb_string_id(cdev);
+		if (status < 0)
+			return status;
+		info->string_defs[info->data_str_idx].id = status;
+		info->data_desc->iInterface = status;
+	}
+
+	if (info->iad_str_idx >= 0 && info->iad_desc) {
+		/* IAD iFunction label */
+		status = usb_string_id(cdev);
+		if (status < 0)
+			return status;
+		info->string_defs[info->iad_str_idx].id = status;
+		info->iad_desc->iFunction = status;
+	}
+
+	if (info->mac_str_idx >= 0 && info->cdc_eth_desc) {
+		/* IAD iFunction label */
+		status = usb_string_id(cdev);
+		if (status < 0)
+			return status;
+		info->string_defs[info->mac_str_idx].id = status;
+		info->cdc_eth_desc->iMACAddress = status;
+	}
+
+skip_string_id_alloc:
+	if (info->ctrl_desc)
+		info->ctrl_desc->bInterfaceNumber = gsi->ctrl_id;
+
+	if (info->iad_desc)
+		info->iad_desc->bFirstInterface = gsi->ctrl_id;
+
+	if (info->union_desc) {
+		info->union_desc->bMasterInterface0 = gsi->ctrl_id;
+		info->union_desc->bSlaveInterface0 = gsi->data_id;
+	}
+
+	if (info->data_desc)
+		info->data_desc->bInterfaceNumber = gsi->data_id;
+
+	if (info->data_nop_desc)
+		info->data_nop_desc->bInterfaceNumber = gsi->data_id;
+
+	/* allocate instance-specific endpoints */
+	if (info->fs_in_desc) {
+		ep = usb_ep_autoconfig_by_name(cdev->gadget,
+				info->fs_in_desc, info->in_epname);
+		if (!ep)
+			goto fail;
+		gsi->d_port.in_ep = ep;
+		msm_ep_config(gsi->d_port.in_ep, NULL);
+		ep->driver_data = cdev;	/* claim */
+	}
+
+	if (info->fs_out_desc) {
+		ep = usb_ep_autoconfig_by_name(cdev->gadget,
+				info->fs_out_desc, info->out_epname);
+		if (!ep)
+			goto fail;
+		gsi->d_port.out_ep = ep;
+		msm_ep_config(gsi->d_port.out_ep, NULL);
+		ep->driver_data = cdev;	/* claim */
+	}
+
+	if (info->fs_notify_desc) {
+		ep = usb_ep_autoconfig(cdev->gadget, info->fs_notify_desc);
+		if (!ep)
+			goto fail;
+		gsi->c_port.notify = ep;
+		ep->driver_data = cdev;	/* claim */
+
+		/* allocate notification request and buffer */
+		gsi->c_port.notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
+		if (!gsi->c_port.notify_req)
+			goto fail;
+
+		gsi->c_port.notify_req->buf =
+			kmalloc(info->notify_buf_len, GFP_KERNEL);
+		if (!gsi->c_port.notify_req->buf)
+			goto fail;
+
+		gsi->c_port.notify_req->length = info->notify_buf_len;
+		gsi->c_port.notify_req->context = gsi;
+		gsi->c_port.notify_req->complete =
+				gsi_ctrl_notify_resp_complete;
+		event = gsi->c_port.notify_req->buf;
+		event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
+				| USB_RECIP_INTERFACE;
+
+		if (gsi->ctrl_id == -ENODEV)
+			event->wIndex = cpu_to_le16(gsi->data_id);
+		else
+			event->wIndex = cpu_to_le16(gsi->ctrl_id);
+
+		event->wLength = cpu_to_le16(0);
+	}
+
+	gsi->d_port.in_request.buf_len = info->in_req_buf_len;
+	gsi->d_port.in_request.num_bufs = info->in_req_num_buf;
+	if (gsi->d_port.out_ep) {
+		gsi->d_port.out_request.buf_len = info->out_req_buf_len;
+		gsi->d_port.out_request.num_bufs = info->out_req_num_buf;
+	}
+
+	/* Initialize event queue */
+	spin_lock_init(&gsi->d_port.evt_q.q_lock);
+	gsi->d_port.evt_q.head = gsi->d_port.evt_q.tail = MAXQUEUELEN - 1;
+
+	if (info->fs_in_desc) {
+		info->hs_in_desc->bEndpointAddress =
+			info->fs_in_desc->bEndpointAddress;
+		info->ss_in_desc->bEndpointAddress =
+			info->fs_in_desc->bEndpointAddress;
+	}
+
+	if (info->fs_out_desc) {
+		info->hs_out_desc->bEndpointAddress =
+			info->fs_out_desc->bEndpointAddress;
+		info->ss_out_desc->bEndpointAddress =
+			info->fs_out_desc->bEndpointAddress;
+	}
+
+	if (info->fs_notify_desc) {
+		info->hs_notify_desc->bEndpointAddress =
+			info->fs_notify_desc->bEndpointAddress;
+		info->ss_notify_desc->bEndpointAddress =
+			info->fs_notify_desc->bEndpointAddress;
+	}
+
+	status = usb_assign_descriptors(f, info->fs_desc_hdr, info->hs_desc_hdr,
+					info->ss_desc_hdr, info->ss_desc_hdr);
+	if (status)
+		goto fail;
+
+	return 0;
+
+fail:
+	if (gsi->c_port.notify_req) {
+		kfree(gsi->c_port.notify_req->buf);
+		usb_ep_free_request(gsi->c_port.notify, gsi->c_port.notify_req);
+	}
+	/* we might as well release our claims on endpoints */
+	if (gsi->c_port.notify)
+		gsi->c_port.notify->driver_data = NULL;
+	if (gsi->d_port.out_ep && gsi->d_port.out_ep->desc)
+		gsi->d_port.out_ep->driver_data = NULL;
+	if (gsi->d_port.in_ep && gsi->d_port.in_ep->desc)
+		gsi->d_port.in_ep->driver_data = NULL;
+	log_event_err("%s: bind failed for %s", __func__, f->name);
+	return -ENOMEM;
+}
+
+static void ipa_ready_callback(void *user_data)
+{
+	struct f_gsi *gsi = user_data;
+
+	log_event_info("%s: ipa is ready\n", __func__);
+
+	gsi->d_port.ipa_ready = true;
+	wake_up_interruptible(&gsi->d_port.wait_for_ipa_ready);
+}
+
+static int gsi_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct gsi_function_bind_info info = {0};
+	struct f_gsi *gsi = func_to_gsi(f);
+	struct rndis_params *params;
+	int status;
+	__u8  class;
+	__u8  subclass;
+	__u8  proto;
+
+
+	if (gsi->prot_id == IPA_USB_RMNET ||
+		gsi->prot_id == IPA_USB_DIAG)
+		gsi->ctrl_id = -ENODEV;
+	else {
+		status = gsi->ctrl_id = usb_interface_id(c, f);
+		if (status < 0)
+			goto fail;
+	}
+
+	status = gsi->data_id = usb_interface_id(c, f);
+	if (status < 0)
+		goto fail;
+
+	switch (gsi->prot_id) {
+	case IPA_USB_RNDIS:
+		info.string_defs = rndis_gsi_string_defs;
+		info.ctrl_desc = &rndis_gsi_control_intf;
+		info.ctrl_str_idx = 0;
+		info.data_desc = &rndis_gsi_data_intf;
+		info.data_str_idx = 1;
+		info.iad_desc = &rndis_gsi_iad_descriptor;
+		info.iad_str_idx = 2;
+		info.union_desc = &rndis_gsi_union_desc;
+		info.fs_in_desc = &rndis_gsi_fs_in_desc;
+		info.fs_out_desc = &rndis_gsi_fs_out_desc;
+		info.fs_notify_desc = &rndis_gsi_fs_notify_desc;
+		info.hs_in_desc = &rndis_gsi_hs_in_desc;
+		info.hs_out_desc = &rndis_gsi_hs_out_desc;
+		info.hs_notify_desc = &rndis_gsi_hs_notify_desc;
+		info.ss_in_desc = &rndis_gsi_ss_in_desc;
+		info.ss_out_desc = &rndis_gsi_ss_out_desc;
+		info.ss_notify_desc = &rndis_gsi_ss_notify_desc;
+		info.fs_desc_hdr = gsi_eth_fs_function;
+		info.hs_desc_hdr = gsi_eth_hs_function;
+		info.ss_desc_hdr = gsi_eth_ss_function;
+		info.in_epname = "gsi-epin";
+		info.out_epname = "gsi-epout";
+		info.in_req_buf_len = GSI_IN_RNDIS_BUFF_SIZE;
+		gsi->d_port.in_aggr_size = GSI_IN_RNDIS_AGGR_SIZE;
+		info.in_req_num_buf = GSI_NUM_IN_RNDIS_BUFFERS;
+		gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE;
+		info.out_req_buf_len = GSI_OUT_AGGR_SIZE;
+		info.out_req_num_buf = GSI_NUM_OUT_BUFFERS;
+		info.notify_buf_len = sizeof(struct usb_cdc_notification);
+
+		params = rndis_register(gsi_rndis_response_available, gsi,
+				gsi_rndis_flow_ctrl_enable);
+		if (IS_ERR(params))
+			goto fail;
+
+		gsi->params = params;
+
+		rndis_set_param_medium(gsi->params, RNDIS_MEDIUM_802_3, 0);
+
+		/* export host's Ethernet address in CDC format */
+		random_ether_addr(gsi->d_port.ipa_init_params.device_ethaddr);
+		random_ether_addr(gsi->d_port.ipa_init_params.host_ethaddr);
+		log_event_dbg("setting host_ethaddr=%pM, device_ethaddr = %pM",
+		gsi->d_port.ipa_init_params.host_ethaddr,
+		gsi->d_port.ipa_init_params.device_ethaddr);
+		memcpy(gsi->ethaddr, &gsi->d_port.ipa_init_params.host_ethaddr,
+				ETH_ALEN);
+		rndis_set_host_mac(gsi->params, gsi->ethaddr);
+
+		if (gsi->manufacturer && gsi->vendorID &&
+			rndis_set_param_vendor(gsi->params, gsi->vendorID,
+				gsi->manufacturer))
+			goto dereg_rndis;
+
+		log_event_dbg("%s: max_pkt_per_xfer : %d", __func__,
+					DEFAULT_MAX_PKT_PER_XFER);
+		rndis_set_max_pkt_xfer(gsi->params, DEFAULT_MAX_PKT_PER_XFER);
+
+		/* In case of aggregated packets QC device will request
+		 * aliment to 4 (2^2).
+		 */
+		log_event_dbg("%s: pkt_alignment_factor : %d", __func__,
+					DEFAULT_PKT_ALIGNMENT_FACTOR);
+		rndis_set_pkt_alignment_factor(gsi->params,
+					DEFAULT_PKT_ALIGNMENT_FACTOR);
+
+		/* Windows7/Windows10 automatically loads RNDIS drivers for
+		 * class drivers which represents MISC_ACTIVE_SYNC,
+		 * MISC_RNDIS_OVER_ETHERNET & WIRELESS_CONTROLLER_REMOTE_NDIS.
+		 * All the codes listed below are from
+		 * http://www.usb.org/developers/defined_class and its unknown
+		 * why windows loads rndis class driver for some of them.
+		 * Note that, Windows loads NDIS6 stack automatically for
+		 * MISC_RNDIS_OVER_ETHERNET. Windows loads NDIS5 stack for
+		 * MISC_ACTIVE_SYNC and WIRELESS_CONTROLLER_REMOTE_NDIS.
+		 * For other class codes, NDIS stack can be selected using
+		 * customized INF file but that defeats the purpose as its
+		 * expected to load drivers automatically for known class
+		 * drivers published by usbif.
+		 * Linux rndis host driver supports MISC_ACTIVE_SYNC and
+		 * WIRELESS_CONTROLLER_REMOTE_NDIS as of now.
+		 * Default to rndis over ethernet which loads NDIS6 drivers
+		 * for windows7/windows10 to avoid data stall issues
+		 */
+		if (gsi->rndis_id == RNDIS_ID_UNKNOWN)
+			gsi->rndis_id = MISC_RNDIS_OVER_ETHERNET;
+
+		switch (gsi->rndis_id) {
+		default:
+			/* fall throug */
+		case WIRELESS_CONTROLLER_REMOTE_NDIS:
+			class = USB_CLASS_WIRELESS_CONTROLLER;
+			subclass = 0x01;
+			proto = 0x03;
+			break;
+		case MISC_ACTIVE_SYNC:
+			class = USB_CLASS_MISC;
+			subclass = 0x01;
+			proto = 0x01;
+			break;
+		case MISC_RNDIS_OVER_ETHERNET:
+			class = USB_CLASS_MISC;
+			subclass = 0x04;
+			proto = 0x01;
+			break;
+		case MISC_RNDIS_OVER_WIFI:
+			class = USB_CLASS_MISC;
+			subclass = 0x04;
+			proto = 0x02;
+			break;
+		case MISC_RNDIS_OVER_WIMAX:
+			class = USB_CLASS_MISC;
+			subclass = 0x04;
+			proto = 0x03;
+			break;
+		case MISC_RNDIS_OVER_WWAN:
+			class = USB_CLASS_MISC;
+			subclass = 0x04;
+			proto = 0x04;
+			break;
+		case MISC_RNDIS_FOR_IPV4:
+			class = USB_CLASS_MISC;
+			subclass = 0x04;
+			proto = 0x05;
+			break;
+		case MISC_RNDIS_FOR_IPV6:
+			class = USB_CLASS_MISC;
+			subclass = 0x04;
+			proto = 0x06;
+			break;
+		case MISC_RNDIS_FOR_GPRS:
+			class = USB_CLASS_MISC;
+			subclass = 0x04;
+			proto = 0x07;
+			break;
+		}
+
+		info.iad_desc->bFunctionClass = class;
+		info.iad_desc->bFunctionSubClass = subclass;
+		info.iad_desc->bFunctionProtocol = proto;
+		info.ctrl_desc->bInterfaceClass = class;
+		info.ctrl_desc->bInterfaceSubClass = subclass;
+		info.ctrl_desc->bInterfaceProtocol = proto;
+
+		break;
+	case IPA_USB_MBIM:
+		info.string_defs = mbim_gsi_string_defs;
+		info.ctrl_desc = &mbim_gsi_control_intf;
+		info.ctrl_str_idx = 0;
+		info.data_desc = &mbim_gsi_data_intf;
+		info.data_str_idx = 1;
+		info.data_nop_desc = &mbim_gsi_data_nop_intf;
+		info.iad_desc = &mbim_gsi_iad_desc;
+		info.iad_str_idx = -1;
+		info.union_desc = &mbim_gsi_union_desc;
+		info.fs_in_desc = &mbim_gsi_fs_in_desc;
+		info.fs_out_desc = &mbim_gsi_fs_out_desc;
+		info.fs_notify_desc = &mbim_gsi_fs_notify_desc;
+		info.hs_in_desc = &mbim_gsi_hs_in_desc;
+		info.hs_out_desc = &mbim_gsi_hs_out_desc;
+		info.hs_notify_desc = &mbim_gsi_hs_notify_desc;
+		info.ss_in_desc = &mbim_gsi_ss_in_desc;
+		info.ss_out_desc = &mbim_gsi_ss_out_desc;
+		info.ss_notify_desc = &mbim_gsi_ss_notify_desc;
+		info.fs_desc_hdr = mbim_gsi_fs_function;
+		info.hs_desc_hdr = mbim_gsi_hs_function;
+		info.ss_desc_hdr = mbim_gsi_ss_function;
+		info.in_epname = "gsi-epin";
+		info.out_epname = "gsi-epout";
+		gsi->d_port.in_aggr_size = GSI_IN_MBIM_AGGR_SIZE;
+		info.in_req_buf_len = GSI_IN_MBIM_AGGR_SIZE;
+		info.in_req_num_buf = GSI_NUM_IN_BUFFERS;
+		gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE;
+		info.out_req_buf_len = GSI_OUT_MBIM_BUF_LEN;
+		info.out_req_num_buf = GSI_NUM_OUT_BUFFERS;
+		info.notify_buf_len = sizeof(struct usb_cdc_notification);
+		mbim_gsi_desc.wMaxSegmentSize = cpu_to_le16(0x800);
+
+		/*
+		 * If MBIM is bound in a config other than the first, tell
+		 * Windows about it by returning the num as a string in the
+		 * OS descriptor's subCompatibleID field. Windows only supports
+		 * up to config #4.
+		 */
+		if (c->bConfigurationValue >= 2 &&
+				c->bConfigurationValue <= 4) {
+			log_event_dbg("MBIM in configuration %d",
+					c->bConfigurationValue);
+			mbim_gsi_ext_config_desc.function.subCompatibleID[0] =
+				c->bConfigurationValue + '0';
+		}
+		break;
+	case IPA_USB_RMNET:
+		info.string_defs = rmnet_gsi_string_defs;
+		info.data_desc = &rmnet_gsi_interface_desc;
+		info.data_str_idx = 0;
+		info.fs_in_desc = &rmnet_gsi_fs_in_desc;
+		info.fs_out_desc = &rmnet_gsi_fs_out_desc;
+		info.fs_notify_desc = &rmnet_gsi_fs_notify_desc;
+		info.hs_in_desc = &rmnet_gsi_hs_in_desc;
+		info.hs_out_desc = &rmnet_gsi_hs_out_desc;
+		info.hs_notify_desc = &rmnet_gsi_hs_notify_desc;
+		info.ss_in_desc = &rmnet_gsi_ss_in_desc;
+		info.ss_out_desc = &rmnet_gsi_ss_out_desc;
+		info.ss_notify_desc = &rmnet_gsi_ss_notify_desc;
+		info.fs_desc_hdr = rmnet_gsi_fs_function;
+		info.hs_desc_hdr = rmnet_gsi_hs_function;
+		info.ss_desc_hdr = rmnet_gsi_ss_function;
+		info.in_epname = "gsi-epin";
+		info.out_epname = "gsi-epout";
+		gsi->d_port.in_aggr_size = GSI_IN_RMNET_AGGR_SIZE;
+		info.in_req_buf_len = GSI_IN_RMNET_BUFF_SIZE;
+		info.in_req_num_buf = GSI_NUM_IN_RMNET_BUFFERS;
+		gsi->d_port.out_aggr_size = GSI_OUT_AGGR_SIZE;
+		info.out_req_buf_len = GSI_OUT_RMNET_BUF_LEN;
+		info.out_req_num_buf = GSI_NUM_OUT_BUFFERS;
+		info.notify_buf_len = sizeof(struct usb_cdc_notification);
+		break;
+	case IPA_USB_ECM:
+		info.string_defs = ecm_gsi_string_defs;
+		info.ctrl_desc = &ecm_gsi_control_intf;
+		info.ctrl_str_idx = 0;
+		info.data_desc = &ecm_gsi_data_intf;
+		info.data_str_idx = 2;
+		info.data_nop_desc = &ecm_gsi_data_nop_intf;
+		info.cdc_eth_desc = &ecm_gsi_desc;
+		info.mac_str_idx = 1;
+		info.union_desc = &ecm_gsi_union_desc;
+		info.fs_in_desc = &ecm_gsi_fs_in_desc;
+		info.fs_out_desc = &ecm_gsi_fs_out_desc;
+		info.fs_notify_desc = &ecm_gsi_fs_notify_desc;
+		info.hs_in_desc = &ecm_gsi_hs_in_desc;
+		info.hs_out_desc = &ecm_gsi_hs_out_desc;
+		info.hs_notify_desc = &ecm_gsi_hs_notify_desc;
+		info.ss_in_desc = &ecm_gsi_ss_in_desc;
+		info.ss_out_desc = &ecm_gsi_ss_out_desc;
+		info.ss_notify_desc = &ecm_gsi_ss_notify_desc;
+		info.fs_desc_hdr = ecm_gsi_fs_function;
+		info.hs_desc_hdr = ecm_gsi_hs_function;
+		info.ss_desc_hdr = ecm_gsi_ss_function;
+		info.in_epname = "gsi-epin";
+		info.out_epname = "gsi-epout";
+		gsi->d_port.in_aggr_size = GSI_ECM_AGGR_SIZE;
+		info.in_req_buf_len = GSI_IN_BUFF_SIZE;
+		info.in_req_num_buf = GSI_NUM_IN_BUFFERS;
+		gsi->d_port.out_aggr_size = GSI_ECM_AGGR_SIZE;
+		info.out_req_buf_len = GSI_OUT_ECM_BUF_LEN;
+		info.out_req_num_buf = GSI_NUM_OUT_BUFFERS;
+		info.notify_buf_len = GSI_CTRL_NOTIFY_BUFF_LEN;
+
+		/* export host's Ethernet address in CDC format */
+		random_ether_addr(gsi->d_port.ipa_init_params.device_ethaddr);
+		random_ether_addr(gsi->d_port.ipa_init_params.host_ethaddr);
+		log_event_dbg("setting host_ethaddr=%pM, device_ethaddr = %pM",
+		gsi->d_port.ipa_init_params.host_ethaddr,
+		gsi->d_port.ipa_init_params.device_ethaddr);
+
+		snprintf(gsi->ethaddr, sizeof(gsi->ethaddr),
+		"%02X%02X%02X%02X%02X%02X",
+		gsi->d_port.ipa_init_params.host_ethaddr[0],
+		gsi->d_port.ipa_init_params.host_ethaddr[1],
+		gsi->d_port.ipa_init_params.host_ethaddr[2],
+		gsi->d_port.ipa_init_params.host_ethaddr[3],
+		gsi->d_port.ipa_init_params.host_ethaddr[4],
+		gsi->d_port.ipa_init_params.host_ethaddr[5]);
+		info.string_defs[1].s = gsi->ethaddr;
+		break;
+	case IPA_USB_DIAG:
+		info.string_defs = qdss_gsi_string_defs;
+		info.data_desc = &qdss_gsi_data_intf_desc;
+		info.data_str_idx = 0;
+		info.fs_in_desc = &qdss_gsi_fs_data_desc;
+		info.hs_in_desc = &qdss_gsi_hs_data_desc;
+		info.ss_in_desc = &qdss_gsi_ss_data_desc;
+		info.fs_desc_hdr = qdss_gsi_fs_data_only_desc;
+		info.hs_desc_hdr = qdss_gsi_hs_data_only_desc;
+		info.ss_desc_hdr = qdss_gsi_ss_data_only_desc;
+		info.in_epname = "gsi-epin";
+		info.out_epname = "";
+		info.in_req_buf_len = 16384;
+		info.in_req_num_buf = GSI_NUM_IN_BUFFERS;
+		info.notify_buf_len = sizeof(struct usb_cdc_notification);
+		break;
+	default:
+		log_event_err("%s: Invalid prot id %d", __func__,
+							gsi->prot_id);
+		return -EINVAL;
+	}
+
+	status = gsi_update_function_bind_params(gsi, cdev, &info);
+	if (status)
+		goto dereg_rndis;
+
+	status = ipa_register_ipa_ready_cb(ipa_ready_callback, gsi);
+	if (!status) {
+		log_event_info("%s: ipa is not ready", __func__);
+		status = wait_event_interruptible_timeout(
+			gsi->d_port.wait_for_ipa_ready, gsi->d_port.ipa_ready,
+			msecs_to_jiffies(GSI_IPA_READY_TIMEOUT));
+		if (!status) {
+			log_event_err("%s: ipa ready timeout", __func__);
+			status = -ETIMEDOUT;
+			goto dereg_rndis;
+		}
+	}
+
+	gsi->d_port.ipa_usb_notify_cb = ipa_usb_notify_cb;
+	status = ipa_usb_init_teth_prot(gsi->prot_id,
+		&gsi->d_port.ipa_init_params, gsi->d_port.ipa_usb_notify_cb,
+		gsi);
+	if (status) {
+		log_event_err("%s: failed to init teth prot(%d) with err:%d",
+					__func__, gsi->prot_id, status);
+		goto dereg_rndis;
+	}
+
+	gsi->d_port.sm_state = STATE_INITIALIZED;
+
+	DBG(cdev, "%s: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+			f->name,
+			gadget_is_superspeed(c->cdev->gadget) ? "super" :
+			gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+			(gsi->d_port.in_ep == NULL ? "NULL" :
+					gsi->d_port.in_ep->name),
+			(gsi->d_port.out_ep == NULL ? "NULL" :
+					gsi->d_port.out_ep->name),
+			(gsi->c_port.notify == NULL ? "NULL" :
+					gsi->c_port.notify->name));
+	return 0;
+
+dereg_rndis:
+	rndis_deregister(gsi->params);
+fail:
+	return status;
+}
+
+static void gsi_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_gsi *gsi = func_to_gsi(f);
+
+	/*
+	 * Use drain_workqueue to accomplish below conditions:
+	 * 1. Make sure that any running work completed
+	 * 2. Make sure to wait until all pending work completed i.e. workqueue
+	 * is not having any pending work.
+	 * Above conditions are making sure that ipa_usb_deinit_teth_prot()
+	 * with ipa driver shall not fail due to unexpected state.
+	 */
+	drain_workqueue(gsi->d_port.ipa_usb_wq);
+	ipa_usb_deinit_teth_prot(gsi->prot_id);
+
+	if (gsi->prot_id == IPA_USB_RNDIS) {
+		gsi->d_port.sm_state = STATE_UNINITIALIZED;
+		rndis_deregister(gsi->params);
+	}
+
+	if (gsi->prot_id == IPA_USB_MBIM)
+		mbim_gsi_ext_config_desc.function.subCompatibleID[0] = 0;
+
+	usb_free_all_descriptors(f);
+	f->hs_descriptors = NULL;
+	f->fs_descriptors = NULL;
+	f->ss_descriptors = NULL;
+	f->ssp_descriptors = NULL;
+
+	if (gsi->c_port.notify) {
+		kfree(gsi->c_port.notify_req->buf);
+		usb_ep_free_request(gsi->c_port.notify, gsi->c_port.notify_req);
+	}
+}
+
+
+static void gsi_free_func(struct usb_function *f)
+{
+	pr_debug("%s\n", __func__);
+}
+
+static int gsi_bind_config(struct f_gsi *gsi)
+{
+	int status = 0;
+	enum ipa_usb_teth_prot prot_id = gsi->prot_id;
+
+	log_event_dbg("%s: prot id %d", __func__, prot_id);
+
+	switch (prot_id) {
+	case IPA_USB_RNDIS:
+		gsi->function.name = "rndis";
+		gsi->function.strings = rndis_gsi_strings;
+		break;
+	case IPA_USB_ECM:
+		gsi->function.name = "cdc_ethernet";
+		gsi->function.strings = ecm_gsi_strings;
+		break;
+	case IPA_USB_RMNET:
+		gsi->function.name = "rmnet";
+		gsi->function.strings = rmnet_gsi_strings;
+		break;
+	case IPA_USB_MBIM:
+		gsi->function.name = "mbim";
+		gsi->function.strings = mbim_gsi_strings;
+		break;
+	case IPA_USB_DIAG:
+		gsi->function.name = "dpl";
+		gsi->function.strings = qdss_gsi_strings;
+		break;
+	default:
+		log_event_err("%s: invalid prot id %d", __func__, prot_id);
+		return -EINVAL;
+	}
+
+	/* descriptors are per-instance copies */
+	gsi->function.bind = gsi_bind;
+	gsi->function.unbind = gsi_unbind;
+	gsi->function.set_alt = gsi_set_alt;
+	gsi->function.get_alt = gsi_get_alt;
+	gsi->function.setup = gsi_setup;
+	gsi->function.disable = gsi_disable;
+	gsi->function.free_func = gsi_free_func;
+	gsi->function.suspend = gsi_suspend;
+	gsi->function.get_status = gsi_get_status;
+	gsi->function.func_suspend = gsi_func_suspend;
+	gsi->function.resume = gsi_resume;
+
+	INIT_WORK(&gsi->d_port.usb_ipa_w, ipa_work_handler);
+
+	return status;
+}
+
+static struct f_gsi *gsi_function_init(enum ipa_usb_teth_prot prot_id)
+{
+	struct f_gsi *gsi;
+	int ret = 0;
+
+	if (prot_id >= IPA_USB_MAX_TETH_PROT_SIZE) {
+		pr_err("%s: invalid prot id %d", __func__, prot_id);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	gsi = kzalloc(sizeof(*gsi), GFP_KERNEL);
+	if (!gsi) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	spin_lock_init(&gsi->d_port.lock);
+
+	init_waitqueue_head(&gsi->d_port.wait_for_ipa_ready);
+
+	gsi->d_port.in_channel_handle = -EINVAL;
+	gsi->d_port.out_channel_handle = -EINVAL;
+
+	gsi->prot_id = prot_id;
+
+	gsi->d_port.ipa_usb_wq = ipa_usb_wq;
+
+	ret = gsi_function_ctrl_port_init(gsi);
+	if (ret) {
+		kfree(gsi);
+		goto error;
+	}
+
+	return gsi;
+error:
+	return ERR_PTR(ret);
+}
+
+static void gsi_opts_release(struct config_item *item)
+{
+	struct gsi_opts *opts = to_gsi_opts(item);
+	struct f_gsi *gsi;
+
+	gsi = opts->gsi;
+	log_event_dbg("%s: releasing %s instance\n",
+			__func__, gsi->function.name);
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations gsi_item_ops = {
+	.release	= gsi_opts_release,
+};
+
+static ssize_t gsi_info_show(struct config_item *item, char *page)
+{
+	struct ipa_usb_xdci_chan_params *ipa_chnl_params;
+	struct ipa_usb_xdci_connect_params *con_pms;
+	struct f_gsi *gsi = to_gsi_opts(item)->gsi;
+	int ret, j = 0;
+	unsigned int len = 0;
+	char *buf;
+
+	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (gsi && atomic_read(&gsi->connected)) {
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+			"Info: Prot_id:%d\n", gsi->prot_id);
+		ipa_chnl_params = &gsi->d_port.ipa_in_channel_params;
+		con_pms = &gsi->d_port.ipa_conn_pms;
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%55s\n",
+		"==================================================");
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10s\n", "Ctrl Name: ", gsi->c_port.name);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Ctrl Online: ",
+				gsi->c_port.ctrl_online.counter);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Ctrl Open: ",
+				gsi->c_port.is_open);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Ctrl Host to Modem: ",
+				gsi->c_port.host_to_modem);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Ctrl Modem to Host: ",
+				gsi->c_port.modem_to_host);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Ctrl Cpd to Modem: ",
+				gsi->c_port.copied_to_modem);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Ctrl Cpd From Modem: ",
+				gsi->c_port.copied_from_modem);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Ctrl Pkt Drops: ",
+				gsi->c_port.cpkt_drop_cnt);
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%25s\n",
+		"==============");
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Protocol ID: ", gsi->prot_id);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "SM State: ", gsi->d_port.sm_state);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "IN XferRscIndex: ",
+				gsi->d_port.in_xfer_rsc_index);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10d\n", "IN Chnl Hdl: ",
+				gsi->d_port.in_channel_handle);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "IN Chnl Dbl Addr: ",
+				gsi->d_port.in_request.db_reg_phs_addr_lsb);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "IN TRB Ring Len: ",
+				ipa_chnl_params->xfer_ring_len);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "IN TRB Base Addr: ", (unsigned int)
+			ipa_chnl_params->xfer_ring_base_addr_iova);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "GEVENTCNTLO IN Addr: ",
+			ipa_chnl_params->gevntcount_low_addr);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "DEPCMDLO IN Addr: ",
+		ipa_chnl_params->xfer_scratch.depcmd_low_addr);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "IN LastTRB Addr Off: ",
+		ipa_chnl_params->xfer_scratch.last_trb_addr_iova);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "IN Buffer Size: ",
+		ipa_chnl_params->xfer_scratch.const_buffer_size);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "IN/DL Aggr Size: ",
+		con_pms->teth_prot_params.max_xfer_size_bytes_to_host);
+
+		ipa_chnl_params = &gsi->d_port.ipa_out_channel_params;
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%25s\n",
+		"==============");
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "OUT XferRscIndex: ",
+			gsi->d_port.out_xfer_rsc_index);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10d\n", "OUT Channel Hdl: ",
+			gsi->d_port.out_channel_handle);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "OUT Channel Dbl Addr: ",
+			gsi->d_port.out_request.db_reg_phs_addr_lsb);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "OUT TRB Ring Len: ",
+			ipa_chnl_params->xfer_ring_len);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "OUT TRB Base Addr: ", (unsigned int)
+			ipa_chnl_params->xfer_ring_base_addr_iova);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "GEVENTCNTLO OUT Addr: ",
+			ipa_chnl_params->gevntcount_low_addr);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "DEPCMDLO OUT Addr: ",
+			ipa_chnl_params->xfer_scratch.depcmd_low_addr);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10x\n", "OUT LastTRB Addr Off: ",
+		ipa_chnl_params->xfer_scratch.last_trb_addr_iova);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "OUT Buffer Size: ",
+		ipa_chnl_params->xfer_scratch.const_buffer_size);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "OUT/UL Aggr Size: ",
+		con_pms->teth_prot_params.max_xfer_size_bytes_to_dev);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "OUT/UL Packets to dev: ",
+		con_pms->teth_prot_params.max_packet_number_to_dev);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Net_ready_trigger:",
+		gsi->d_port.net_ready_trigger);
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%25s\n",
+		"USB Bus Events");
+		for (j = 0; j < MAXQUEUELEN; j++)
+			len += scnprintf(buf + len, PAGE_SIZE - len,
+				"%d\t", gsi->d_port.evt_q.event[j]);
+		len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Eventq head: ",
+				gsi->d_port.evt_q.head);
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+		"%25s %10u\n", "Eventq tail: ",
+				gsi->d_port.evt_q.tail);
+	}
+
+	if (len > PAGE_SIZE)
+		len = PAGE_SIZE;
+
+	ret = scnprintf(page, len, buf);
+
+	kfree(buf);
+
+	return ret;
+}
+
+CONFIGFS_ATTR_RO(gsi_, info);
+
+static struct configfs_attribute *gsi_attrs[] = {
+	&gsi_attr_info,
+	NULL,
+};
+
+static struct config_item_type gsi_func_type = {
+	.ct_item_ops	= &gsi_item_ops,
+	.ct_attrs	= gsi_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static ssize_t gsi_rndis_class_id_show(struct config_item *item, char *page)
+{
+	struct f_gsi *gsi = to_gsi_opts(item)->gsi;
+
+	return snprintf(page, PAGE_SIZE, "%d\n", gsi->rndis_id);
+}
+
+static ssize_t gsi_rndis_class_id_store(struct config_item *item,
+			const char *page, size_t len)
+{
+	struct f_gsi *gsi = to_gsi_opts(item)->gsi;
+	u8 id;
+
+	if (kstrtou8(page, 0, &id))
+		return -EINVAL;
+
+	if (id > RNDIS_ID_UNKNOWN && id < RNDIS_ID_MAX)
+		gsi->rndis_id = id;
+	else
+		return -EINVAL;
+
+	return len;
+}
+CONFIGFS_ATTR(gsi_, rndis_class_id);
+
+static struct configfs_attribute *gsi_rndis_attrs[] = {
+	&gsi_attr_info,
+	&gsi_attr_rndis_class_id,
+	NULL,
+};
+
+static struct config_item_type gsi_func_rndis_type = {
+	.ct_item_ops	= &gsi_item_ops,
+	.ct_attrs	= gsi_rndis_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void gsi_inst_clean(struct gsi_opts *opts)
+{
+	if (opts->gsi->c_port.cdev.dev) {
+		struct cdev *cdev = &opts->gsi->c_port.cdev;
+		int minor = MINOR(cdev->dev);
+
+		device_destroy(gsi_class, cdev->dev);
+		cdev_del(cdev);
+		cdev->dev = 0;
+		ida_simple_remove(&gsi_ida, minor);
+	}
+
+	kfree(opts->gsi);
+	kfree(opts);
+}
+
+static int gsi_set_inst_name(struct usb_function_instance *fi,
+	const char *name)
+{
+	int prot_id, name_len;
+	struct f_gsi *gsi;
+	char gsi_inst_name[MAX_INST_NAME_LEN + sizeof("gsi.") + 1];
+	void *ipc_log_ctxt;
+	struct gsi_opts *opts, *opts_prev;
+
+	opts = container_of(fi, struct gsi_opts, func_inst);
+
+	name_len = strlen(name) + 1;
+	if (name_len > MAX_INST_NAME_LEN)
+		return -ENAMETOOLONG;
+
+	prot_id = name_to_prot_id(name);
+	if (prot_id < 0) {
+		pr_err("%s: failed to find prot id for %s instance\n",
+						__func__, name);
+		return -EINVAL;
+	}
+
+	mutex_lock(&inst_status[prot_id].gsi_lock);
+	opts_prev = inst_status[prot_id].opts;
+	if (opts_prev) {
+		mutex_unlock(&inst_status[prot_id].gsi_lock);
+		pr_err("%s: prot_id = %d, prev inst do not freed yet\n",
+						__func__, prot_id);
+		return -EBUSY;
+	}
+	mutex_unlock(&inst_status[prot_id].gsi_lock);
+
+	if (prot_id == IPA_USB_RNDIS)
+		config_group_init_type_name(&opts->func_inst.group, "",
+					    &gsi_func_rndis_type);
+
+	gsi = gsi_function_init(prot_id);
+	if (IS_ERR(gsi))
+		return PTR_ERR(gsi);
+
+	opts->gsi = gsi;
+	/*
+	 * create instance name with prefixing "gsi." to differentiate
+	 * ipc log debugfs entry
+	 */
+	snprintf(gsi_inst_name, sizeof(gsi_inst_name), "gsi.%s", name);
+	ipc_log_ctxt = ipc_log_context_create(NUM_LOG_PAGES, gsi_inst_name, 0);
+	if (!ipc_log_ctxt)
+		pr_err("%s: Err allocating ipc_log_ctxt for prot:%s\n",
+						__func__, gsi_inst_name);
+	opts->gsi->ipc_log_ctxt = ipc_log_ctxt;
+
+	/* Set instance status */
+	mutex_lock(&inst_status[prot_id].gsi_lock);
+	inst_status[prot_id].inst_exist = true;
+	inst_status[prot_id].opts = opts;
+	mutex_unlock(&inst_status[prot_id].gsi_lock);
+
+	return 0;
+}
+
+static void gsi_free_inst(struct usb_function_instance *f)
+{
+	struct gsi_opts *opts = container_of(f, struct gsi_opts, func_inst);
+	enum ipa_usb_teth_prot prot_id;
+	struct f_gsi *gsi;
+
+	if (!opts->gsi)
+		return;
+
+	prot_id = opts->gsi->prot_id;
+	gsi = opts->gsi;
+	mutex_lock(&inst_status[prot_id].gsi_lock);
+	if (opts->gsi->c_port.is_open) {
+		/* Mark instance exist as false */
+		inst_status[prot_id].inst_exist = false;
+		mutex_unlock(&inst_status[prot_id].gsi_lock);
+		log_event_err(
+			"%s: [prot_id = %d] Dev is open, free mem when dev close\n",
+			__func__, prot_id);
+		return;
+	}
+
+	ipc_log_context_destroy(opts->gsi->ipc_log_ctxt);
+	/* Clear instance status */
+	gsi_inst_clean(opts);
+	inst_status[prot_id].inst_exist = false;
+	inst_status[prot_id].opts = NULL;
+	mutex_unlock(&inst_status[prot_id].gsi_lock);
+}
+
+static struct usb_function_instance *gsi_alloc_inst(void)
+{
+	struct gsi_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	opts->func_inst.set_inst_name = gsi_set_inst_name;
+	opts->func_inst.free_func_inst = gsi_free_inst;
+	config_group_init_type_name(&opts->func_inst.group, "",
+					&gsi_func_type);
+
+	return &opts->func_inst;
+}
+
+static struct usb_function *gsi_alloc(struct usb_function_instance *fi)
+{
+	struct gsi_opts *opts;
+	int ret;
+
+	opts = container_of(fi, struct gsi_opts, func_inst);
+
+	ret = gsi_bind_config(opts->gsi);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &opts->gsi->function;
+}
+
+DECLARE_USB_FUNCTION(gsi, gsi_alloc_inst, gsi_alloc);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("GSI function driver");
+
+static int fgsi_init(void)
+{
+	int i;
+	int ret;
+	dev_t dev;
+
+	ipa_usb_wq = alloc_workqueue("k_ipa_usb",
+				WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_FREEZABLE, 1);
+	if (!ipa_usb_wq) {
+		pr_err("%s(): Failed to create workqueue\n", __func__);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < IPA_USB_MAX_TETH_PROT_SIZE; i++)
+		mutex_init(&inst_status[i].gsi_lock);
+
+	gsi_class = class_create(THIS_MODULE, "gsi_usb");
+	if (IS_ERR(gsi_class)) {
+		ret = PTR_ERR(gsi_class);
+		gsi_class = NULL;
+		pr_err("%s: class_create() failed:%d\n", __func__, ret);
+		return ret;
+	}
+
+	ret = alloc_chrdev_region(&dev, 0, MAX_CDEV_INSTANCES, "gsi_usb");
+	if (ret) {
+		pr_err("%s: alloc_chrdev_region() failed:%d\n", __func__, ret);
+		class_destroy(gsi_class);
+		gsi_class = NULL;
+		return ret;
+	}
+
+	major = MAJOR(dev);
+
+	return usb_function_register(&gsiusb_func);
+}
+module_init(fgsi_init);
+
+static void __exit fgsi_exit(void)
+{
+	if (ipa_usb_wq)
+		destroy_workqueue(ipa_usb_wq);
+	usb_function_unregister(&gsiusb_func);
+
+	if (major) {
+		unregister_chrdev_region(MKDEV(major, 0), MAX_CDEV_INSTANCES);
+		major = 0;
+	}
+
+	class_destroy(gsi_class);
+}
+module_exit(fgsi_exit);
diff --git a/drivers/usb/gadget/function/f_gsi.h b/drivers/usb/gadget/function/f_gsi.h
new file mode 100644
index 0000000..208d8e4
--- /dev/null
+++ b/drivers/usb/gadget/function/f_gsi.h
@@ -0,0 +1,1420 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef _F_GSI_H
+#define _F_GSI_H
+
+#include <linux/poll.h>
+#include <linux/cdev.h>
+#include <linux/ipa.h>
+#include <uapi/linux/usb/cdc.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/usb_ctrl_qti.h>
+#include <linux/etherdevice.h>
+#include <linux/debugfs.h>
+#include <linux/ipa_usb.h>
+#include <linux/ipc_logging.h>
+
+#define GSI_RMNET_CTRL_NAME "rmnet_ctrl"
+#define GSI_MBIM_CTRL_NAME "android_mbim"
+#define GSI_DPL_CTRL_NAME "dpl_ctrl"
+#define GSI_CTRL_NAME_LEN (sizeof(GSI_MBIM_CTRL_NAME)+2)
+#define GSI_MAX_CTRL_PKT_SIZE 4096
+#define GSI_CTRL_DTR (1 << 0)
+
+#define GSI_NUM_IN_RNDIS_BUFFERS 50
+#define GSI_NUM_IN_RMNET_BUFFERS 50
+#define GSI_NUM_IN_BUFFERS 15
+#define GSI_IN_BUFF_SIZE 2048
+#define GSI_IN_RMNET_BUFF_SIZE 31744
+#define GSI_IN_RNDIS_BUFF_SIZE 16384
+#define GSI_NUM_OUT_BUFFERS 14
+#define GSI_OUT_AGGR_SIZE 24576
+
+#define GSI_IN_RNDIS_AGGR_SIZE 16384
+#define GSI_IN_MBIM_AGGR_SIZE 16384
+#define GSI_IN_RMNET_AGGR_SIZE 16384
+#define GSI_ECM_AGGR_SIZE 2048
+
+#define GSI_OUT_MBIM_BUF_LEN 16384
+#define GSI_OUT_RMNET_BUF_LEN 31744
+#define GSI_OUT_ECM_BUF_LEN 2048
+
+#define GSI_IPA_READY_TIMEOUT 5000
+
+#define ETH_ADDR_STR_LEN 14
+
+/* mbin and ecm */
+#define GSI_CTRL_NOTIFY_BUFF_LEN 16
+
+/* default max packets per tarnsfer value */
+#define DEFAULT_MAX_PKT_PER_XFER 15
+
+/* default pkt alignment factor */
+#define DEFAULT_PKT_ALIGNMENT_FACTOR 4
+
+#define GSI_MBIM_IOCTL_MAGIC 'o'
+#define GSI_MBIM_GET_NTB_SIZE  _IOR(GSI_MBIM_IOCTL_MAGIC, 2, u32)
+#define GSI_MBIM_GET_DATAGRAM_COUNT  _IOR(GSI_MBIM_IOCTL_MAGIC, 3, u16)
+#define GSI_MBIM_EP_LOOKUP _IOR(GSI_MBIM_IOCTL_MAGIC, 4, struct ep_info)
+#define GSI_MBIM_DATA_EP_TYPE_HSUSB 0x2
+/* ID for Microsoft OS String */
+#define GSI_MBIM_OS_STRING_ID 0xEE
+
+#define EVT_NONE			0
+#define EVT_UNINITIALIZED		1
+#define EVT_INITIALIZED			2
+#define EVT_CONNECT_IN_PROGRESS		3
+#define EVT_CONNECTED			4
+#define EVT_HOST_NRDY			5
+#define EVT_HOST_READY			6
+#define EVT_DISCONNECTED		7
+#define	EVT_SUSPEND			8
+#define	EVT_IPA_SUSPEND			9
+#define	EVT_RESUMED			10
+
+#define NUM_LOG_PAGES 10
+#define log_event_err(x, ...) do { \
+	if (gsi) { \
+		ipc_log_string(gsi->ipc_log_ctxt, x, ##__VA_ARGS__); \
+		pr_err(x, ##__VA_ARGS__); \
+	} \
+} while (0)
+
+#define log_event_dbg(x, ...) do { \
+	if (gsi) { \
+		ipc_log_string(gsi->ipc_log_ctxt, x, ##__VA_ARGS__); \
+		pr_debug(x, ##__VA_ARGS__); \
+	} \
+} while (0)
+
+#define log_event_info(x, ...) do { \
+	if (gsi) { \
+		ipc_log_string(gsi->ipc_log_ctxt, x, ##__VA_ARGS__); \
+		pr_info(x, ##__VA_ARGS__); \
+	} \
+} while (0)
+
+enum connection_state {
+	STATE_UNINITIALIZED,
+	STATE_INITIALIZED,
+	STATE_CONNECT_IN_PROGRESS,
+	STATE_CONNECTED,
+	STATE_DISCONNECTED,
+	STATE_SUSPEND_IN_PROGRESS,
+	STATE_SUSPENDED
+};
+
+enum gsi_ctrl_notify_state {
+	GSI_CTRL_NOTIFY_NONE,
+	GSI_CTRL_NOTIFY_CONNECT,
+	GSI_CTRL_NOTIFY_SPEED,
+	GSI_CTRL_NOTIFY_OFFLINE,
+	GSI_CTRL_NOTIFY_RESPONSE_AVAILABLE,
+};
+
+enum rndis_class_id {
+	RNDIS_ID_UNKNOWN,
+	WIRELESS_CONTROLLER_REMOTE_NDIS,
+	MISC_ACTIVE_SYNC,
+	MISC_RNDIS_OVER_ETHERNET,
+	MISC_RNDIS_OVER_WIFI,
+	MISC_RNDIS_OVER_WIMAX,
+	MISC_RNDIS_OVER_WWAN,
+	MISC_RNDIS_FOR_IPV4,
+	MISC_RNDIS_FOR_IPV6,
+	MISC_RNDIS_FOR_GPRS,
+	RNDIS_ID_MAX,
+};
+
+#define MAXQUEUELEN 128
+struct event_queue {
+	u8 event[MAXQUEUELEN];
+	u8 head, tail;
+	spinlock_t q_lock;
+};
+
+struct gsi_ntb_info {
+	u32	ntb_input_size;
+	u16	ntb_max_datagrams;
+	u16	reserved;
+};
+
+struct gsi_ctrl_pkt {
+	void				*buf;
+	int				len;
+	enum gsi_ctrl_notify_state	type;
+	struct list_head		list;
+};
+
+struct gsi_function_bind_info {
+	struct usb_string *string_defs;
+	int ctrl_str_idx;
+	int data_str_idx;
+	int iad_str_idx;
+	int mac_str_idx;
+	struct usb_interface_descriptor *ctrl_desc;
+	struct usb_interface_descriptor *data_desc;
+	struct usb_interface_assoc_descriptor *iad_desc;
+	struct usb_cdc_ether_desc *cdc_eth_desc;
+	struct usb_cdc_union_desc *union_desc;
+	struct usb_interface_descriptor *data_nop_desc;
+	struct usb_endpoint_descriptor *fs_in_desc;
+	struct usb_endpoint_descriptor *fs_out_desc;
+	struct usb_endpoint_descriptor *fs_notify_desc;
+	struct usb_endpoint_descriptor *hs_in_desc;
+	struct usb_endpoint_descriptor *hs_out_desc;
+	struct usb_endpoint_descriptor *hs_notify_desc;
+	struct usb_endpoint_descriptor *ss_in_desc;
+	struct usb_endpoint_descriptor *ss_out_desc;
+	struct usb_endpoint_descriptor *ss_notify_desc;
+
+	struct usb_descriptor_header **fs_desc_hdr;
+	struct usb_descriptor_header **hs_desc_hdr;
+	struct usb_descriptor_header **ss_desc_hdr;
+	const char *in_epname;
+	const char *out_epname;
+
+	u32 in_req_buf_len;
+	u32 in_req_num_buf;
+	u32 out_req_buf_len;
+	u32 out_req_num_buf;
+	u32 notify_buf_len;
+};
+
+struct gsi_ctrl_port {
+	char name[GSI_CTRL_NAME_LEN];
+	struct cdev cdev;
+
+	struct usb_ep *notify;
+	struct usb_request *notify_req;
+	bool notify_req_queued;
+
+	atomic_t ctrl_online;
+
+	bool is_open;
+
+	wait_queue_head_t read_wq;
+
+	struct list_head cpkt_req_q;
+	struct list_head cpkt_resp_q;
+	unsigned long cpkts_len;
+
+	spinlock_t lock;
+
+	int ipa_cons_clnt_hdl;
+	int ipa_prod_clnt_hdl;
+
+	unsigned int host_to_modem;
+	unsigned int copied_to_modem;
+	unsigned int copied_from_modem;
+	unsigned int modem_to_host;
+	unsigned int cpkt_drop_cnt;
+	unsigned int get_encap_cnt;
+};
+
+struct gsi_data_port {
+	struct usb_ep *in_ep;
+	struct usb_ep *out_ep;
+	struct usb_gsi_request in_request;
+	struct usb_gsi_request out_request;
+	struct usb_gadget *gadget;
+	int (*ipa_usb_notify_cb)(enum ipa_usb_notify_event, void *driver_data);
+	struct ipa_usb_teth_params ipa_init_params;
+	int in_channel_handle;
+	int out_channel_handle;
+	u32 in_xfer_rsc_index;
+	u32 out_xfer_rsc_index;
+	u16 in_last_trb_addr;
+	u16 cdc_filter;
+	u32 in_aggr_size;
+	u32 out_aggr_size;
+
+	bool ipa_ready;
+	bool net_ready_trigger;
+	struct gsi_ntb_info ntb_info;
+
+	spinlock_t lock;
+
+	struct work_struct usb_ipa_w;
+	struct workqueue_struct *ipa_usb_wq;
+	enum connection_state sm_state;
+	struct event_queue evt_q;
+	wait_queue_head_t wait_for_ipa_ready;
+
+	/* Track these for debugfs */
+	struct ipa_usb_xdci_chan_params ipa_in_channel_params;
+	struct ipa_usb_xdci_chan_params ipa_out_channel_params;
+	struct ipa_usb_xdci_connect_params ipa_conn_pms;
+};
+
+struct f_gsi {
+	struct usb_function function;
+	enum ipa_usb_teth_prot prot_id;
+	int ctrl_id;
+	int data_id;
+	u32 vendorID;
+	u8 ethaddr[ETH_ADDR_STR_LEN];
+	const char *manufacturer;
+	struct rndis_params *params;
+	atomic_t connected;
+	bool data_interface_up;
+	enum rndis_class_id rndis_id;
+
+	const struct usb_endpoint_descriptor *in_ep_desc_backup;
+	const struct usb_endpoint_descriptor *out_ep_desc_backup;
+
+	struct gsi_data_port d_port;
+	struct gsi_ctrl_port c_port;
+	void *ipc_log_ctxt;
+	bool rmnet_dtr_status;
+};
+
+static inline struct f_gsi *func_to_gsi(struct usb_function *f)
+{
+	return container_of(f, struct f_gsi, function);
+}
+
+static inline struct f_gsi *d_port_to_gsi(struct gsi_data_port *d)
+{
+	return container_of(d, struct f_gsi, d_port);
+}
+
+static inline struct f_gsi *c_port_to_gsi(struct gsi_ctrl_port *d)
+{
+	return container_of(d, struct f_gsi, c_port);
+}
+
+/* for configfs support */
+#define MAX_INST_NAME_LEN	40
+
+struct gsi_opts {
+	struct usb_function_instance func_inst;
+	struct f_gsi *gsi;
+};
+
+static inline struct gsi_opts *to_gsi_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct gsi_opts,
+			    func_inst.group);
+}
+
+static enum ipa_usb_teth_prot name_to_prot_id(const char *name)
+{
+	if (!name)
+		goto error;
+
+	if (!strncasecmp(name, "rndis", strlen("rndis")))
+		return IPA_USB_RNDIS;
+	if (!strncasecmp(name, "ecm", strlen("ecm")))
+		return IPA_USB_ECM;
+	if (!strncasecmp(name, "rmnet", strlen("rmnet")))
+		return IPA_USB_RMNET;
+	if (!strncasecmp(name, "mbim", strlen("mbim")))
+		return IPA_USB_MBIM;
+	if (!strncasecmp(name, "dpl", strlen("dpl")))
+		return IPA_USB_DIAG;
+
+error:
+	return -EINVAL;
+}
+
+/* device descriptors */
+
+#define LOG2_STATUS_INTERVAL_MSEC 5
+#define MAX_NOTIFY_SIZE sizeof(struct usb_cdc_notification)
+
+/* rmnet device descriptors */
+
+static struct usb_interface_descriptor rmnet_gsi_interface_desc = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	3,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+	.bInterfaceSubClass =	USB_CLASS_VENDOR_SPEC,
+	.bInterfaceProtocol =	USB_CLASS_VENDOR_SPEC,
+	/* .iInterface = DYNAMIC */
+};
+
+/* Full speed support */
+static struct usb_endpoint_descriptor rmnet_gsi_fs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MAX_NOTIFY_SIZE),
+	.bInterval =		1 << LOG2_STATUS_INTERVAL_MSEC,
+};
+
+static struct usb_endpoint_descriptor rmnet_gsi_fs_in_desc  = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize   =	cpu_to_le16(64),
+};
+
+static struct usb_endpoint_descriptor rmnet_gsi_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize   =	cpu_to_le16(64),
+};
+
+static struct usb_descriptor_header *rmnet_gsi_fs_function[] = {
+	(struct usb_descriptor_header *) &rmnet_gsi_interface_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_fs_notify_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_fs_in_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_fs_out_desc,
+	NULL,
+};
+
+/* High speed support */
+static struct usb_endpoint_descriptor rmnet_gsi_hs_notify_desc  = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MAX_NOTIFY_SIZE),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_endpoint_descriptor rmnet_gsi_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor rmnet_gsi_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *rmnet_gsi_hs_function[] = {
+	(struct usb_descriptor_header *) &rmnet_gsi_interface_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_hs_notify_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_hs_in_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_hs_out_desc,
+	NULL,
+};
+
+/* Super speed support */
+static struct usb_endpoint_descriptor rmnet_gsi_ss_notify_desc  = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MAX_NOTIFY_SIZE),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_ss_ep_comp_descriptor rmnet_gsi_ss_notify_comp_desc = {
+	.bLength =		sizeof(rmnet_gsi_ss_notify_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+	.wBytesPerInterval =	cpu_to_le16(MAX_NOTIFY_SIZE),
+};
+
+static struct usb_endpoint_descriptor rmnet_gsi_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor rmnet_gsi_ss_in_comp_desc = {
+	.bLength =		sizeof(rmnet_gsi_ss_in_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	.bMaxBurst =		2,
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_endpoint_descriptor rmnet_gsi_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor rmnet_gsi_ss_out_comp_desc = {
+	.bLength =		sizeof(rmnet_gsi_ss_out_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	.bMaxBurst =		2,
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_descriptor_header *rmnet_gsi_ss_function[] = {
+	(struct usb_descriptor_header *) &rmnet_gsi_interface_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_ss_notify_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_ss_notify_comp_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_ss_in_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_ss_in_comp_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_ss_out_desc,
+	(struct usb_descriptor_header *) &rmnet_gsi_ss_out_comp_desc,
+	NULL,
+};
+
+/* String descriptors */
+static struct usb_string rmnet_gsi_string_defs[] = {
+	[0].s = "RmNet",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings rmnet_gsi_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		rmnet_gsi_string_defs,
+};
+
+static struct usb_gadget_strings *rmnet_gsi_strings[] = {
+	&rmnet_gsi_string_table,
+	NULL,
+};
+
+/* rndis device descriptors */
+
+/* interface descriptor: Supports "Wireless" RNDIS; auto-detected by Windows*/
+static struct usb_interface_descriptor rndis_gsi_control_intf = {
+	.bLength =		sizeof(rndis_gsi_control_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	/* status endpoint is optional; this could be patched later */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_WIRELESS_CONTROLLER,
+	.bInterfaceSubClass =   0x01,
+	.bInterfaceProtocol =   0x03,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc rndis_gsi_header_desc = {
+	.bLength =		sizeof(rndis_gsi_header_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_call_mgmt_descriptor rndis_gsi_call_mgmt_descriptor = {
+	.bLength =		sizeof(rndis_gsi_call_mgmt_descriptor),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_CALL_MANAGEMENT_TYPE,
+
+	.bmCapabilities =	0x00,
+	.bDataInterface =	0x01,
+};
+
+static struct usb_cdc_acm_descriptor rndis_gsi_acm_descriptor = {
+	.bLength =		sizeof(rndis_gsi_acm_descriptor),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ACM_TYPE,
+
+	.bmCapabilities =	0x00,
+};
+
+static struct usb_cdc_union_desc rndis_gsi_union_desc = {
+	.bLength =		sizeof(rndis_gsi_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+/* the data interface has two bulk endpoints */
+
+static struct usb_interface_descriptor rndis_gsi_data_intf = {
+	.bLength =		sizeof(rndis_gsi_data_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+/*  Supports "Wireless" RNDIS; auto-detected by Windows */
+static struct usb_interface_assoc_descriptor
+rndis_gsi_iad_descriptor = {
+	.bLength =		sizeof(rndis_gsi_iad_descriptor),
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+	.bFirstInterface =	0, /* XXX, hardcoded */
+	.bInterfaceCount =	2, /* control + data */
+	.bFunctionClass =	USB_CLASS_WIRELESS_CONTROLLER,
+	.bFunctionSubClass =	0x01,
+	.bFunctionProtocol =	0x03,
+	/* .iFunction = DYNAMIC */
+};
+
+/* full speed support: */
+static struct usb_endpoint_descriptor rndis_gsi_fs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MAX_NOTIFY_SIZE),
+	.bInterval =		1 << LOG2_STATUS_INTERVAL_MSEC,
+};
+
+static struct usb_endpoint_descriptor rndis_gsi_fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.wMaxPacketSize =	cpu_to_le16(64),
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor rndis_gsi_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.wMaxPacketSize =	cpu_to_le16(64),
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *gsi_eth_fs_function[] = {
+	(struct usb_descriptor_header *) &rndis_gsi_iad_descriptor,
+	/* control interface matches ACM, not Ethernet */
+	(struct usb_descriptor_header *) &rndis_gsi_control_intf,
+	(struct usb_descriptor_header *) &rndis_gsi_header_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &rndis_gsi_acm_descriptor,
+	(struct usb_descriptor_header *) &rndis_gsi_union_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_fs_notify_desc,
+	/* data interface has no altsetting */
+	(struct usb_descriptor_header *) &rndis_gsi_data_intf,
+	(struct usb_descriptor_header *) &rndis_gsi_fs_in_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+static struct usb_endpoint_descriptor rndis_gsi_hs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MAX_NOTIFY_SIZE),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+static struct usb_endpoint_descriptor rndis_gsi_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor rndis_gsi_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *gsi_eth_hs_function[] = {
+	(struct usb_descriptor_header *) &rndis_gsi_iad_descriptor,
+	/* control interface matches ACM, not Ethernet */
+	(struct usb_descriptor_header *) &rndis_gsi_control_intf,
+	(struct usb_descriptor_header *) &rndis_gsi_header_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &rndis_gsi_acm_descriptor,
+	(struct usb_descriptor_header *) &rndis_gsi_union_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_hs_notify_desc,
+	/* data interface has no altsetting */
+	(struct usb_descriptor_header *) &rndis_gsi_data_intf,
+	(struct usb_descriptor_header *) &rndis_gsi_hs_in_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_hs_out_desc,
+	NULL,
+};
+
+/* super speed support: */
+static struct usb_endpoint_descriptor rndis_gsi_ss_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(MAX_NOTIFY_SIZE),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_ss_ep_comp_descriptor rndis_gsi_ss_intr_comp_desc = {
+	.bLength =		sizeof(rndis_gsi_ss_intr_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =		0, */
+	/* .bmAttributes =	0, */
+	.wBytesPerInterval =	cpu_to_le16(MAX_NOTIFY_SIZE),
+};
+
+static struct usb_endpoint_descriptor rndis_gsi_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor rndis_gsi_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor rndis_gsi_ss_bulk_comp_desc = {
+	.bLength =		sizeof(rndis_gsi_ss_bulk_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	.bMaxBurst =		2,
+	/* .bmAttributes =	0, */
+};
+
+static struct usb_descriptor_header *gsi_eth_ss_function[] = {
+	(struct usb_descriptor_header *) &rndis_gsi_iad_descriptor,
+
+	/* control interface matches ACM, not Ethernet */
+	(struct usb_descriptor_header *) &rndis_gsi_control_intf,
+	(struct usb_descriptor_header *) &rndis_gsi_header_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_call_mgmt_descriptor,
+	(struct usb_descriptor_header *) &rndis_gsi_acm_descriptor,
+	(struct usb_descriptor_header *) &rndis_gsi_union_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_ss_notify_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_ss_intr_comp_desc,
+
+	/* data interface has no altsetting */
+	(struct usb_descriptor_header *) &rndis_gsi_data_intf,
+	(struct usb_descriptor_header *) &rndis_gsi_ss_in_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_ss_bulk_comp_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_ss_out_desc,
+	(struct usb_descriptor_header *) &rndis_gsi_ss_bulk_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+static struct usb_string rndis_gsi_string_defs[] = {
+	[0].s = "RNDIS Communications Control",
+	[1].s = "RNDIS Ethernet Data",
+	[2].s = "RNDIS",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings rndis_gsi_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		rndis_gsi_string_defs,
+};
+
+static struct usb_gadget_strings *rndis_gsi_strings[] = {
+	&rndis_gsi_string_table,
+	NULL,
+};
+
+/* mbim device descriptors */
+#define MBIM_NTB_DEFAULT_IN_SIZE	(0x4000)
+
+static struct usb_cdc_ncm_ntb_parameters mbim_gsi_ntb_parameters = {
+	.wLength = sizeof(mbim_gsi_ntb_parameters),
+	.bmNtbFormatsSupported = cpu_to_le16(USB_CDC_NCM_NTB16_SUPPORTED),
+	.dwNtbInMaxSize = cpu_to_le32(MBIM_NTB_DEFAULT_IN_SIZE),
+	.wNdpInDivisor = cpu_to_le16(4),
+	.wNdpInPayloadRemainder = cpu_to_le16(0),
+	.wNdpInAlignment = cpu_to_le16(4),
+
+	.dwNtbOutMaxSize = cpu_to_le32(0x4000),
+	.wNdpOutDivisor = cpu_to_le16(4),
+	.wNdpOutPayloadRemainder = cpu_to_le16(0),
+	.wNdpOutAlignment = cpu_to_le16(4),
+	.wNtbOutMaxDatagrams = 16,
+};
+
+/*
+ * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one
+ * packet, to simplify cancellation;
+ */
+#define NCM_STATUS_BYTECOUNT		16	/* 8 byte header + data */
+
+static struct usb_interface_assoc_descriptor mbim_gsi_iad_desc = {
+	.bLength =		sizeof(mbim_gsi_iad_desc),
+	.bDescriptorType =	USB_DT_INTERFACE_ASSOCIATION,
+
+	/* .bFirstInterface =	DYNAMIC, */
+	.bInterfaceCount =	2,	/* control + data */
+	.bFunctionClass =	2,
+	.bFunctionSubClass =	0x0e,
+	.bFunctionProtocol =	0,
+	/* .iFunction =		DYNAMIC */
+};
+
+/* interface descriptor: */
+static struct usb_interface_descriptor mbim_gsi_control_intf = {
+	.bLength =		sizeof(mbim_gsi_control_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	0x02,
+	.bInterfaceSubClass =	0x0e,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc mbim_gsi_header_desc = {
+	.bLength =		sizeof(mbim_gsi_header_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_union_desc mbim_gsi_union_desc = {
+	.bLength =		sizeof(mbim_gsi_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+static struct usb_cdc_mbim_desc mbim_gsi_desc = {
+	.bLength =		sizeof(mbim_gsi_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_MBIM_TYPE,
+
+	.bcdMBIMVersion =	cpu_to_le16(0x0100),
+
+	.wMaxControlMessage =	cpu_to_le16(0x1000),
+	.bNumberFilters =	0x20,
+	.bMaxFilterSize =	0x80,
+	.wMaxSegmentSize =	cpu_to_le16(0xfe0),
+	.bmNetworkCapabilities = 0x20,
+};
+
+static struct usb_cdc_mbim_extended_desc mbim_gsi_ext_mbb_desc = {
+	.bLength =	sizeof(mbim_gsi_ext_mbb_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_MBIM_EXTENDED_TYPE,
+
+	.bcdMBIMExtendedVersion =		cpu_to_le16(0x0100),
+	.bMaxOutstandingCommandMessages =	64,
+	.wMTU =					cpu_to_le16(1500),
+};
+
+/* the default data interface has no endpoints ... */
+static struct usb_interface_descriptor mbim_gsi_data_nop_intf = {
+	.bLength =		sizeof(mbim_gsi_data_nop_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	0x0a,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0x02,
+	/* .iInterface = DYNAMIC */
+};
+
+/* ... but the "real" data interface has two bulk endpoints */
+static struct usb_interface_descriptor mbim_gsi_data_intf = {
+	.bLength =		sizeof(mbim_gsi_data_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	0x0a,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0x02,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor mbim_gsi_fs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	4*cpu_to_le16(NCM_STATUS_BYTECOUNT),
+	.bInterval =		1 << LOG2_STATUS_INTERVAL_MSEC,
+};
+
+static struct usb_endpoint_descriptor mbim_gsi_fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	4*cpu_to_le16(NCM_STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor mbim_gsi_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	4*cpu_to_le16(NCM_STATUS_BYTECOUNT),
+};
+
+static struct usb_descriptor_header *mbim_gsi_fs_function[] = {
+	(struct usb_descriptor_header *) &mbim_gsi_iad_desc,
+	/* MBIM control descriptors */
+	(struct usb_descriptor_header *) &mbim_gsi_control_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_header_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_union_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ext_mbb_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_fs_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &mbim_gsi_data_nop_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_data_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_fs_in_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+static struct usb_endpoint_descriptor mbim_gsi_hs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	4*cpu_to_le16(NCM_STATUS_BYTECOUNT),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+static struct usb_endpoint_descriptor mbim_gsi_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor mbim_gsi_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *mbim_gsi_hs_function[] = {
+	(struct usb_descriptor_header *) &mbim_gsi_iad_desc,
+	/* MBIM control descriptors */
+	(struct usb_descriptor_header *) &mbim_gsi_control_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_header_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_union_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ext_mbb_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_hs_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &mbim_gsi_data_nop_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_data_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_hs_in_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_hs_out_desc,
+	NULL,
+};
+
+/* Super Speed Support */
+static struct usb_endpoint_descriptor mbim_gsi_ss_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	4*cpu_to_le16(NCM_STATUS_BYTECOUNT),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_ss_ep_comp_descriptor mbim_gsi_ss_notify_comp_desc = {
+	.bLength =		sizeof(mbim_gsi_ss_notify_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =         0, */
+	/* .bmAttributes =      0, */
+	.wBytesPerInterval =	4*cpu_to_le16(NCM_STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor mbim_gsi_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor mbim_gsi_ss_in_comp_desc = {
+	.bLength =              sizeof(mbim_gsi_ss_in_comp_desc),
+	.bDescriptorType =      USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	.bMaxBurst =         2,
+	/* .bmAttributes =      0, */
+};
+
+static struct usb_endpoint_descriptor mbim_gsi_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor mbim_gsi_ss_out_comp_desc = {
+	.bLength =		sizeof(mbim_gsi_ss_out_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	.bMaxBurst =         2,
+	/* .bmAttributes =      0, */
+};
+
+static struct usb_descriptor_header *mbim_gsi_ss_function[] = {
+	(struct usb_descriptor_header *) &mbim_gsi_iad_desc,
+	/* MBIM control descriptors */
+	(struct usb_descriptor_header *) &mbim_gsi_control_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_header_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_union_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ext_mbb_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ss_notify_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ss_notify_comp_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &mbim_gsi_data_nop_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_data_intf,
+	(struct usb_descriptor_header *) &mbim_gsi_ss_in_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ss_in_comp_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ss_out_desc,
+	(struct usb_descriptor_header *) &mbim_gsi_ss_out_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+static struct usb_string mbim_gsi_string_defs[] = {
+	[0].s = "MBIM Control",
+	[1].s = "MBIM Data",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings mbim_gsi_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		mbim_gsi_string_defs,
+};
+
+static struct usb_gadget_strings *mbim_gsi_strings[] = {
+	&mbim_gsi_string_table,
+	NULL,
+};
+
+/* Microsoft OS Descriptors */
+
+/*
+ * We specify our own bMS_VendorCode byte which Windows will use
+ * as the bRequest value in subsequent device get requests.
+ */
+#define MBIM_VENDOR_CODE	0xA5
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mbim_gsi_ext_config_desc_header {
+	__le32	dwLength;
+	__u16	bcdVersion;
+	__le16	wIndex;
+	__u8	bCount;
+	__u8	reserved[7];
+};
+
+/* Microsoft Extended Configuration Descriptor Function Section */
+struct mbim_gsi_ext_config_desc_function {
+	__u8	bFirstInterfaceNumber;
+	__u8	bInterfaceCount;
+	__u8	compatibleID[8];
+	__u8	subCompatibleID[8];
+	__u8	reserved[6];
+};
+
+/* Microsoft Extended Configuration Descriptor */
+static struct {
+	struct mbim_gsi_ext_config_desc_header	header;
+	struct mbim_gsi_ext_config_desc_function    function;
+} mbim_gsi_ext_config_desc = {
+	.header = {
+		.dwLength = cpu_to_le32(sizeof(mbim_gsi_ext_config_desc)),
+		.bcdVersion = cpu_to_le16(0x0100),
+		.wIndex = cpu_to_le16(4),
+		.bCount = 1,
+	},
+	.function = {
+		.bFirstInterfaceNumber = 0,
+		.bInterfaceCount = 1,
+		.compatibleID = { 'A', 'L', 'T', 'R', 'C', 'F', 'G' },
+		/* .subCompatibleID = DYNAMIC */
+	},
+};
+/* ecm device descriptors */
+#define ECM_QC_LOG2_STATUS_INTERVAL_MSEC	5
+#define ECM_QC_STATUS_BYTECOUNT			16 /* 8 byte header + data */
+
+/* interface descriptor: */
+static struct usb_interface_descriptor ecm_gsi_control_intf = {
+	.bLength =		sizeof(ecm_gsi_control_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	/* .bInterfaceNumber = DYNAMIC */
+	/* status endpoint is optional; this could be patched later */
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_COMM,
+	.bInterfaceSubClass =	USB_CDC_SUBCLASS_ETHERNET,
+	.bInterfaceProtocol =	USB_CDC_PROTO_NONE,
+	/* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc ecm_gsi_header_desc = {
+	.bLength =		sizeof(ecm_gsi_header_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_HEADER_TYPE,
+
+	.bcdCDC =		cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_union_desc ecm_gsi_union_desc = {
+	.bLength =		sizeof(ecm_gsi_union_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_UNION_TYPE,
+	/* .bMasterInterface0 =	DYNAMIC */
+	/* .bSlaveInterface0 =	DYNAMIC */
+};
+
+static struct usb_cdc_ether_desc ecm_gsi_desc = {
+	.bLength =		sizeof(ecm_gsi_desc),
+	.bDescriptorType =	USB_DT_CS_INTERFACE,
+	.bDescriptorSubType =	USB_CDC_ETHERNET_TYPE,
+
+	/* this descriptor actually adds value, surprise! */
+	/* .iMACAddress = DYNAMIC */
+	.bmEthernetStatistics =	cpu_to_le32(0), /* no statistics */
+	.wMaxSegmentSize =	cpu_to_le16(ETH_FRAME_LEN),
+	.wNumberMCFilters =	cpu_to_le16(0),
+	.bNumberPowerFilters =	0,
+};
+
+/* the default data interface has no endpoints ... */
+
+static struct usb_interface_descriptor ecm_gsi_data_nop_intf = {
+	.bLength =		sizeof(ecm_gsi_data_nop_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	0,
+	.bNumEndpoints =	0,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+/* ... but the "real" data interface has two bulk endpoints */
+
+static struct usb_interface_descriptor ecm_gsi_data_intf = {
+	.bLength =		sizeof(ecm_gsi_data_intf),
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bInterfaceNumber =	1,
+	.bAlternateSetting =	1,
+	.bNumEndpoints =	2,
+	.bInterfaceClass =	USB_CLASS_CDC_DATA,
+	.bInterfaceSubClass =	0,
+	.bInterfaceProtocol =	0,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+static struct usb_endpoint_descriptor ecm_gsi_fs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(ECM_QC_STATUS_BYTECOUNT),
+	.bInterval =		1 << LOG2_STATUS_INTERVAL_MSEC,
+};
+
+static struct usb_endpoint_descriptor ecm_gsi_fs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(ECM_QC_STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor ecm_gsi_fs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(ECM_QC_STATUS_BYTECOUNT),
+};
+
+static struct usb_descriptor_header *ecm_gsi_fs_function[] = {
+	/* CDC ECM control descriptors */
+	(struct usb_descriptor_header *) &ecm_gsi_control_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_header_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_union_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_desc,
+	/* NOTE: status endpoint might need to be removed */
+	(struct usb_descriptor_header *) &ecm_gsi_fs_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ecm_gsi_data_nop_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_data_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_fs_in_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_fs_out_desc,
+	NULL,
+};
+
+/* high speed support: */
+static struct usb_endpoint_descriptor ecm_gsi_hs_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(ECM_QC_STATUS_BYTECOUNT),
+	.bInterval =		LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+static struct usb_endpoint_descriptor ecm_gsi_hs_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor ecm_gsi_hs_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *ecm_gsi_hs_function[] = {
+	/* CDC ECM control descriptors */
+	(struct usb_descriptor_header *) &ecm_gsi_control_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_header_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_union_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_desc,
+	/* NOTE: status endpoint might need to be removed */
+	(struct usb_descriptor_header *) &ecm_gsi_hs_notify_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ecm_gsi_data_nop_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_data_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_hs_in_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_hs_out_desc,
+	NULL,
+};
+
+static struct usb_endpoint_descriptor ecm_gsi_ss_notify_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(ECM_QC_STATUS_BYTECOUNT),
+	.bInterval =		ECM_QC_LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ecm_gsi_ss_notify_comp_desc = {
+	.bLength =		sizeof(ecm_gsi_ss_notify_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 3 values can be tweaked if necessary */
+	/* .bMaxBurst =         0, */
+	/* .bmAttributes =      0, */
+	.wBytesPerInterval =	cpu_to_le16(ECM_QC_STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor ecm_gsi_ss_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ecm_gsi_ss_in_comp_desc = {
+	.bLength =		sizeof(ecm_gsi_ss_in_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	.bMaxBurst =         2,
+	/* .bmAttributes =      0, */
+};
+
+static struct usb_endpoint_descriptor ecm_gsi_ss_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ecm_gsi_ss_out_comp_desc = {
+	.bLength =		sizeof(ecm_gsi_ss_out_comp_desc),
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+
+	/* the following 2 values can be tweaked if necessary */
+	.bMaxBurst =         2,
+	/* .bmAttributes =      0, */
+};
+
+static struct usb_descriptor_header *ecm_gsi_ss_function[] = {
+	/* CDC ECM control descriptors */
+	(struct usb_descriptor_header *) &ecm_gsi_control_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_header_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_union_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_desc,
+	/* NOTE: status endpoint might need to be removed */
+	(struct usb_descriptor_header *) &ecm_gsi_ss_notify_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_ss_notify_comp_desc,
+	/* data interface, altsettings 0 and 1 */
+	(struct usb_descriptor_header *) &ecm_gsi_data_nop_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_data_intf,
+	(struct usb_descriptor_header *) &ecm_gsi_ss_in_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_ss_in_comp_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_ss_out_desc,
+	(struct usb_descriptor_header *) &ecm_gsi_ss_out_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+static struct usb_string ecm_gsi_string_defs[] = {
+	[0].s = "CDC Ethernet Control Model (ECM)",
+	[1].s = NULL /* DYNAMIC */,
+	[2].s = "CDC Ethernet Data",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings ecm_gsi_string_table = {
+	.language =		0x0409,	/* en-us */
+	.strings =		ecm_gsi_string_defs,
+};
+
+static struct usb_gadget_strings *ecm_gsi_strings[] = {
+	&ecm_gsi_string_table,
+	NULL,
+};
+
+/* qdss device descriptor */
+
+static struct usb_interface_descriptor qdss_gsi_data_intf_desc = {
+	.bLength            =	sizeof(qdss_gsi_data_intf_desc),
+	.bDescriptorType    =	USB_DT_INTERFACE,
+	.bAlternateSetting  =   0,
+	.bNumEndpoints      =	1,
+	.bInterfaceClass    =	0xff,
+	.bInterfaceSubClass =	0xff,
+	.bInterfaceProtocol =	0xff,
+};
+
+static struct usb_endpoint_descriptor qdss_gsi_fs_data_desc = {
+	.bLength              =	 USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType      =	 USB_DT_ENDPOINT,
+	.bEndpointAddress     =	 USB_DIR_IN,
+	.bmAttributes         =	 USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize       =	 cpu_to_le16(64),
+};
+
+static struct usb_endpoint_descriptor qdss_gsi_hs_data_desc = {
+	.bLength              =	 USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType      =	 USB_DT_ENDPOINT,
+	.bEndpointAddress     =	 USB_DIR_IN,
+	.bmAttributes         =	 USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize       =	 cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor qdss_gsi_ss_data_desc = {
+	.bLength              =	 USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType      =	 USB_DT_ENDPOINT,
+	.bEndpointAddress     =	 USB_DIR_IN,
+	.bmAttributes         =  USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize       =	 cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor qdss_gsi_data_ep_comp_desc = {
+	.bLength              =	 sizeof(qdss_gsi_data_ep_comp_desc),
+	.bDescriptorType      =	 USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst            =	 1,
+	.bmAttributes         =	 0,
+	.wBytesPerInterval    =	 0,
+};
+
+static struct usb_descriptor_header *qdss_gsi_fs_data_only_desc[] = {
+	(struct usb_descriptor_header *) &qdss_gsi_data_intf_desc,
+	(struct usb_descriptor_header *) &qdss_gsi_fs_data_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *qdss_gsi_hs_data_only_desc[] = {
+	(struct usb_descriptor_header *) &qdss_gsi_data_intf_desc,
+	(struct usb_descriptor_header *) &qdss_gsi_hs_data_desc,
+	NULL,
+};
+
+static struct usb_descriptor_header *qdss_gsi_ss_data_only_desc[] = {
+	(struct usb_descriptor_header *) &qdss_gsi_data_intf_desc,
+	(struct usb_descriptor_header *) &qdss_gsi_ss_data_desc,
+	(struct usb_descriptor_header *) &qdss_gsi_data_ep_comp_desc,
+	NULL,
+};
+
+/* string descriptors: */
+static struct usb_string qdss_gsi_string_defs[] = {
+	[0].s = "DPL Data",
+	{}, /* end of list */
+};
+
+static struct usb_gadget_strings qdss_gsi_string_table = {
+	.language =		0x0409,
+	.strings =		qdss_gsi_string_defs,
+};
+
+static struct usb_gadget_strings *qdss_gsi_strings[] = {
+	&qdss_gsi_string_table,
+	NULL,
+};
+#endif
diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
index 5780fba..5ee1a29 100644
--- a/drivers/usb/gadget/function/f_ncm.c
+++ b/drivers/usb/gadget/function/f_ncm.c
@@ -1404,17 +1404,39 @@
 	 */
 	if (!ncm_opts->bound) {
 		mutex_lock(&ncm_opts->lock);
+		ncm_opts->net = gether_setup_default();
+		if (IS_ERR(ncm_opts->net)) {
+			status = PTR_ERR(ncm_opts->net);
+			mutex_unlock(&ncm_opts->lock);
+			goto error;
+		}
 		gether_set_gadget(ncm_opts->net, cdev->gadget);
 		status = gether_register_netdev(ncm_opts->net);
 		mutex_unlock(&ncm_opts->lock);
-		if (status)
-			return status;
+		if (status) {
+			free_netdev(ncm_opts->net);
+			goto error;
+		}
 		ncm_opts->bound = true;
 	}
+
+	/* export host's Ethernet address in CDC format */
+	status = gether_get_host_addr_cdc(ncm_opts->net, ncm->ethaddr,
+				      sizeof(ncm->ethaddr));
+	if (status < 12) { /* strlen("01234567890a") */
+		ERROR(cdev, "%s: failed to get host eth addr, err %d\n",
+		__func__, status);
+		status = -EINVAL;
+		goto netdev_cleanup;
+	}
+	ncm->port.ioport = netdev_priv(ncm_opts->net);
+
 	us = usb_gstrings_attach(cdev, ncm_strings,
 				 ARRAY_SIZE(ncm_string_defs));
-	if (IS_ERR(us))
-		return PTR_ERR(us);
+	if (IS_ERR(us)) {
+		status = PTR_ERR(us);
+		goto netdev_cleanup;
+	}
 	ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id;
 	ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id;
 	ncm_data_intf.iInterface = us[STRING_DATA_IDX].id;
@@ -1514,7 +1536,10 @@
 		kfree(ncm->notify_req->buf);
 		usb_ep_free_request(ncm->notify, ncm->notify_req);
 	}
+netdev_cleanup:
+	gether_cleanup(netdev_priv(ncm_opts->net));
 
+error:
 	ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
 
 	return status;
@@ -1562,8 +1587,6 @@
 	opts = container_of(f, struct f_ncm_opts, func_inst);
 	if (opts->bound)
 		gether_cleanup(netdev_priv(opts->net));
-	else
-		free_netdev(opts->net);
 	kfree(opts);
 }
 
@@ -1576,12 +1599,6 @@
 		return ERR_PTR(-ENOMEM);
 	mutex_init(&opts->lock);
 	opts->func_inst.free_func_inst = ncm_free_inst;
-	opts->net = gether_setup_default();
-	if (IS_ERR(opts->net)) {
-		struct net_device *net = opts->net;
-		kfree(opts);
-		return ERR_CAST(net);
-	}
 
 	config_group_init_type_name(&opts->func_inst.group, "", &ncm_func_type);
 
@@ -1604,6 +1621,8 @@
 static void ncm_unbind(struct usb_configuration *c, struct usb_function *f)
 {
 	struct f_ncm *ncm = func_to_ncm(f);
+	struct f_ncm_opts *opts = container_of(f->fi, struct f_ncm_opts,
+					func_inst);
 
 	DBG(c->cdev, "ncm unbind\n");
 
@@ -1614,13 +1633,15 @@
 
 	kfree(ncm->notify_req->buf);
 	usb_ep_free_request(ncm->notify, ncm->notify_req);
+
+	gether_cleanup(netdev_priv(opts->net));
+	opts->bound = false;
 }
 
 static struct usb_function *ncm_alloc(struct usb_function_instance *fi)
 {
 	struct f_ncm		*ncm;
 	struct f_ncm_opts	*opts;
-	int status;
 
 	/* allocate and initialize one new instance */
 	ncm = kzalloc(sizeof(*ncm), GFP_KERNEL);
@@ -1630,20 +1651,9 @@
 	opts = container_of(fi, struct f_ncm_opts, func_inst);
 	mutex_lock(&opts->lock);
 	opts->refcnt++;
-
-	/* export host's Ethernet address in CDC format */
-	status = gether_get_host_addr_cdc(opts->net, ncm->ethaddr,
-				      sizeof(ncm->ethaddr));
-	if (status < 12) { /* strlen("01234567890a") */
-		kfree(ncm);
-		mutex_unlock(&opts->lock);
-		return ERR_PTR(-EINVAL);
-	}
 	ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr;
-
 	spin_lock_init(&ncm->lock);
 	ncm_reset_values(ncm);
-	ncm->port.ioport = netdev_priv(opts->net);
 	mutex_unlock(&opts->lock);
 	ncm->port.is_fixed = true;
 	ncm->port.supports_multi_frame = true;
diff --git a/include/uapi/linux/usb/cdc.h b/include/uapi/linux/usb/cdc.h
index 6d61550..fcc4200 100644
--- a/include/uapi/linux/usb/cdc.h
+++ b/include/uapi/linux/usb/cdc.h
@@ -232,6 +232,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