usb: gadget: u_bam_data: Add support for BAM reset

This change adds the capability for f_qc_rndis, f_qc_ecm and
f_mbim running in BAM-to-BAM mode, to perform USB BAM reset,
upon a peer BAM reset.

The u_bam_data registers a callback from USB BAM driver, to be
notified about peer BAM reset. When the callback is invoked,
USB BAM reset is executed.

Change-Id: Ia7d8f4a5bbef78c83e0ca0891511547001df29a9
CRs-Fixed: 424867
Signed-off-by: Amit Blay <ablay@codeaurora.org>
diff --git a/drivers/usb/gadget/f_mbim.c b/drivers/usb/gadget/f_mbim.c
index 98c6dbc..16a40dd 100644
--- a/drivers/usb/gadget/f_mbim.c
+++ b/drivers/usb/gadget/f_mbim.c
@@ -659,7 +659,7 @@
 	pr_info("dev:%p port:%d. Do nothing.\n",
 			dev, dev->port_num);
 
-	/* bam_data_disconnect(&dev->bam_port, dev->port_num); */
+	bam_data_disconnect(&dev->bam_port, dev->port_num);
 
 	return 0;
 }
@@ -1273,7 +1273,6 @@
 		if (mbim->bam_port.in->driver_data) {
 			pr_info("reset mbim\n");
 			mbim_reset_values(mbim);
-			mbim_bam_disconnect(mbim);
 		}
 
 		/*
diff --git a/drivers/usb/gadget/f_qc_ecm.c b/drivers/usb/gadget/f_qc_ecm.c
index 0b41197..b1c5985 100644
--- a/drivers/usb/gadget/f_qc_ecm.c
+++ b/drivers/usb/gadget/f_qc_ecm.c
@@ -324,8 +324,9 @@
 
 static int ecm_qc_bam_disconnect(struct f_ecm_qc *dev)
 {
-	pr_debug("dev:%p. %s Do nothing.\n",
-			 dev, __func__);
+	pr_debug("dev:%p. %s Disconnect BAM.\n", dev, __func__);
+
+	bam_data_disconnect(&ecm_qc_bam_port, 0);
 
 	return 0;
 }
@@ -518,8 +519,12 @@
 
 		if (ecm->port.in_ep->driver_data) {
 			DBG(cdev, "reset ecm\n");
-			gether_qc_disconnect_name(&ecm->port, "ecm0");
+			/* ecm->port is needed for disconnecting the BAM data
+			 * path. Only after the BAM data path is disconnected,
+			 * we can disconnect the port from the network layer.
+			 */
 			ecm_qc_bam_disconnect(ecm);
+			gether_qc_disconnect_name(&ecm->port, "ecm0");
 		}
 
 		if (!ecm->port.in_ep->desc ||
@@ -591,8 +596,8 @@
 	DBG(cdev, "ecm deactivated\n");
 
 	if (ecm->port.in_ep->driver_data) {
-		gether_qc_disconnect_name(&ecm->port, "ecm0");
 		ecm_qc_bam_disconnect(ecm);
+		gether_qc_disconnect_name(&ecm->port, "ecm0");
 	}
 
 	if (ecm->notify->driver_data) {
diff --git a/drivers/usb/gadget/f_qc_rndis.c b/drivers/usb/gadget/f_qc_rndis.c
index b632b54..0d229dc 100644
--- a/drivers/usb/gadget/f_qc_rndis.c
+++ b/drivers/usb/gadget/f_qc_rndis.c
@@ -441,8 +441,9 @@
 
 static int rndis_qc_bam_disconnect(struct f_rndis_qc *dev)
 {
-	pr_info("dev:%p. %s Do nothing.\n",
-			dev, __func__);
+	pr_debug("dev:%p. %s Disconnect BAM.\n", dev, __func__);
+
+	bam_data_disconnect(&dev->bam_port, 0);
 
 	return 0;
 }
@@ -674,8 +675,12 @@
 
 		if (rndis->port.in_ep->driver_data) {
 			DBG(cdev, "reset rndis\n");
-			gether_qc_disconnect_name(&rndis->port, "rndis0");
+			/* rndis->port is needed for disconnecting the BAM data
+			 * path. Only after the BAM data path is disconnected,
+			 * we can disconnect the port from the network layer.
+			 */
 			rndis_qc_bam_disconnect(rndis);
+			gether_qc_disconnect_name(&rndis->port, "rndis0");
 		}
 
 		if (!rndis->port.in_ep->desc || !rndis->port.out_ep->desc) {
@@ -735,8 +740,8 @@
 	pr_info("rndis deactivated\n");
 
 	rndis_uninit(rndis->config);
-	gether_qc_disconnect_name(&rndis->port, "rndis0");
 	rndis_qc_bam_disconnect(rndis);
+	gether_qc_disconnect_name(&rndis->port, "rndis0");
 
 	usb_ep_disable(rndis->notify);
 	rndis->notify->driver_data = NULL;
diff --git a/drivers/usb/gadget/u_bam_data.c b/drivers/usb/gadget/u_bam_data.c
index 32f683e..3cc0419 100644
--- a/drivers/usb/gadget/u_bam_data.c
+++ b/drivers/usb/gadget/u_bam_data.c
@@ -73,7 +73,7 @@
 {
 	int status = req->status;
 
-	pr_info("status: %d\n", status);
+	pr_debug("%s: status: %d\n", __func__, status);
 }
 
 static void bam_data_endless_tx_complete(struct usb_ep *ep,
@@ -81,7 +81,7 @@
 {
 	int status = req->status;
 
-	pr_info("status: %d\n", status);
+	pr_debug("%s: status: %d\n", __func__, status);
 }
 
 static void bam_data_start_endless_rx(struct bam_data_port *port)
@@ -89,6 +89,9 @@
 	struct bam_data_ch_info *d = &port->data_ch;
 	int status;
 
+	if (!port->port_usb)
+		return;
+
 	status = usb_ep_queue(port->port_usb->out, d->rx_req, GFP_ATOMIC);
 	if (status)
 		pr_err("error enqueuing transfer, %d\n", status);
@@ -99,32 +102,77 @@
 	struct bam_data_ch_info *d = &port->data_ch;
 	int status;
 
+	if (!port->port_usb)
+		return;
+
 	status = usb_ep_queue(port->port_usb->in, d->tx_req, GFP_ATOMIC);
 	if (status)
 		pr_err("error enqueuing transfer, %d\n", status);
 }
 
-static void bam2bam_data_disconnect_work(struct work_struct *w)
+static int bam_data_peer_reset_cb(void *param)
 {
-	struct bam_data_port *port =
-			container_of(w, struct bam_data_port, disconnect_w);
+	struct bam_data_port	*port = (struct bam_data_port *)param;
+	struct bam_data_ch_info *d;
+	int ret;
+	bool reenable_eps = false;
 
-	pr_info("Enter");
+	d = &port->data_ch;
 
-	/* disable endpoints */
-	if (!port->port_usb || !port->port_usb->out || !port->port_usb->in) {
-		pr_err("port_usb->out/in == NULL. Exit");
-		return;
+	pr_debug("%s: reset by peer\n", __func__);
+
+	/* Disable the relevant EPs if currently EPs are enabled */
+	if (port->port_usb && port->port_usb->in &&
+	  port->port_usb->in->driver_data) {
+		usb_ep_disable(port->port_usb->out);
+		usb_ep_disable(port->port_usb->in);
+
+		port->port_usb->in->driver_data = NULL;
+		port->port_usb->out->driver_data = NULL;
+		reenable_eps = true;
 	}
-	usb_ep_disable(port->port_usb->out);
-	usb_ep_disable(port->port_usb->in);
 
-	port->port_usb->in->driver_data = NULL;
-	port->port_usb->out->driver_data = NULL;
+	/* Disable BAM */
+	msm_hw_bam_disable(1);
 
-	port->port_usb = 0;
+	/* Reset BAM */
+	ret = usb_bam_reset();
+	if (ret) {
+		pr_err("%s: BAM reset failed %d\n", __func__, ret);
+		goto reenable_eps;
+	}
 
-	pr_info("Exit");
+	/* Enable BAM */
+	msm_hw_bam_disable(0);
+
+reenable_eps:
+	/* Re-Enable the relevant EPs, if EPs were originally enabled */
+	if (reenable_eps) {
+		ret = usb_ep_enable(port->port_usb->in);
+		if (ret) {
+			pr_err("%s: usb_ep_enable failed eptype:IN ep:%p",
+				__func__, port->port_usb->in);
+			return ret;
+		}
+		port->port_usb->in->driver_data = port;
+
+		ret = usb_ep_enable(port->port_usb->out);
+		if (ret) {
+			pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p",
+				__func__, port->port_usb->out);
+			port->port_usb->in->driver_data = 0;
+			return ret;
+		}
+		port->port_usb->out->driver_data = port;
+
+		bam_data_start_endless_rx(port);
+		bam_data_start_endless_tx(port);
+	}
+
+	/* Unregister the peer reset callback */
+	usb_bam_register_peer_reset_cb(d->connection_idx, NULL, NULL);
+
+	return 0;
 }
 
 static void bam2bam_data_connect_work(struct work_struct *w)
@@ -135,7 +183,7 @@
 	u32 sps_params;
 	int ret;
 
-	pr_info("Enter");
+	pr_debug("%s: Connect workqueue started", __func__);
 
 	ret = usb_bam_connect(d->connection_idx, &d->src_pipe_idx,
 						  &d->dst_pipe_idx);
@@ -182,7 +230,18 @@
 	bam_data_start_endless_rx(port);
 	bam_data_start_endless_tx(port);
 
-	pr_info("Done\n");
+	/* Register for peer reset callback */
+	usb_bam_register_peer_reset_cb(d->connection_idx,
+			bam_data_peer_reset_cb, port);
+
+	ret = usb_bam_client_ready(true);
+	if (ret) {
+		pr_err("%s: usb_bam_client_ready failed: err:%d\n",
+			__func__, ret);
+		return;
+	}
+
+	pr_debug("%s: Connect workqueue done", __func__);
 }
 
 static void bam2bam_data_port_free(int portno)
@@ -203,14 +262,13 @@
 	port->port_num = portno;
 
 	INIT_WORK(&port->connect_w, bam2bam_data_connect_work);
-	INIT_WORK(&port->disconnect_w, bam2bam_data_disconnect_work);
 
 	/* data ch */
 	d = &port->data_ch;
 	d->port = port;
 	bam2bam_data_ports[portno] = port;
 
-	pr_info("port:%p portno:%d\n", port, portno);
+	pr_debug("port:%p portno:%d\n", port, portno);
 
 	return 0;
 }
@@ -218,9 +276,8 @@
 void bam_data_disconnect(struct data_port *gr, u8 port_num)
 {
 	struct bam_data_port	*port;
-	struct bam_data_ch_info	*d;
 
-	pr_info("dev:%p port#%d\n", gr, port_num);
+	pr_debug("dev:%p port#%d\n", gr, port_num);
 
 	if (port_num >= n_bam2bam_data_ports) {
 		pr_err("invalid bam2bam portno#%d\n", port_num);
@@ -234,10 +291,20 @@
 
 	port = bam2bam_data_ports[port_num];
 
-	d = &port->data_ch;
-	port->port_usb = gr;
+	if (port->port_usb && port->port_usb->in &&
+	  port->port_usb->in->driver_data) {
+		/* disable endpoints */
+		usb_ep_disable(port->port_usb->out);
+		usb_ep_disable(port->port_usb->in);
 
-	queue_work(bam_data_wq, &port->disconnect_w);
+		port->port_usb->in->driver_data = NULL;
+		port->port_usb->out->driver_data = NULL;
+
+		port->port_usb = 0;
+	}
+
+	if (usb_bam_client_ready(false))
+		pr_err("%s: usb_bam_client_ready failed\n", __func__);
 }
 
 int bam_data_connect(struct data_port *gr, u8 port_num,
@@ -247,7 +314,7 @@
 	struct bam_data_ch_info	*d;
 	int			ret;
 
-	pr_info("dev:%p port#%d\n", gr, port_num);
+	pr_debug("dev:%p port#%d\n", gr, port_num);
 
 	if (port_num >= n_bam2bam_data_ports) {
 		pr_err("invalid portno#%d\n", port_num);
@@ -292,7 +359,7 @@
 	int	i;
 	int	ret;
 
-	pr_info("requested %d BAM2BAM ports", no_bam2bam_port);
+	pr_debug("requested %d BAM2BAM ports", no_bam2bam_port);
 
 	if (!no_bam2bam_port || no_bam2bam_port > BAM2BAM_DATA_N_PORTS) {
 		pr_err("Invalid num of ports count:%d\n", no_bam2bam_port);
@@ -331,7 +398,7 @@
 	struct bam_data_port *port = (struct bam_data_port *)param;
 	struct data_port *d_port = port->port_usb;
 
-	pr_info("%s: woken up by peer\n", __func__);
+	pr_debug("%s: woken up by peer\n", __func__);
 
 	if (!d_port) {
 		pr_err("FAILED: d_port == NULL");
@@ -360,7 +427,7 @@
 	port = bam2bam_data_ports[port_num];
 	d = &port->data_ch;
 
-	pr_info("%s: suspended port %d\n", __func__, port_num);
+	pr_debug("%s: suspended port %d\n", __func__, port_num);
 	usb_bam_register_wake_cb(d->connection_idx, bam_data_wake_cb, port);
 }
 
@@ -373,7 +440,7 @@
 	port = bam2bam_data_ports[port_num];
 	d = &port->data_ch;
 
-	pr_info("%s: resumed port %d\n", __func__, port_num);
+	pr_debug("%s: resumed port %d\n", __func__, port_num);
 	usb_bam_register_wake_cb(d->connection_idx, NULL, NULL);
 }