Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c32314b3 authored by Danny Segal's avatar Danny Segal
Browse files

USB: gadget: IPA disconnect on USB suspend when remote wakeup is disabled



When remote wakeup feature is disabled due to the usb host decision,
our tethering device is not allowed to wakeup the usb bus after bus
suspend. Therefore, in case the IPA is willing to wakeup the USB bus,
the USB cannot wakeup the host and therefore cannot grant the consumer
resource back to the IPA resource manager. In this case the IPA resource
manager is waiting until expiring timeout which lead system crash.

Therefore, in case of remote wakeup disabled, we should avoid the
expectation from the IPA side to get a response from the USB. So, the
solution is to indicate to the IPA driver that USB cable has been unplugged
when bus suspend with remote wakeup disabled.

CRs-Fixed: 571703
Change-Id: I03e1e2ad3549b7aad0df263de41b9a3dcc2f3503
Signed-off-by: default avatarDanny Segal <dsegal@codeaurora.org>
parent 6d52439a
Loading
Loading
Loading
Loading
+8 −9
Original line number Diff line number Diff line
@@ -1633,7 +1633,7 @@ static int ci13xxx_wakeup(struct usb_gadget *_gadget)
	trace();

	spin_lock_irqsave(udc->lock, flags);
	if (!udc->remote_wakeup) {
	if (!udc->gadget.remote_wakeup) {
		ret = -EOPNOTSUPP;
		dbg_trace("remote wakeup feature is not enabled\n");
		goto out;
@@ -1684,7 +1684,7 @@ static void usb_do_remote_wakeup(struct work_struct *w)
	 * if wakeup conditions are still met.
	 */
	spin_lock_irqsave(udc->lock, flags);
	do_wake = udc->suspended && udc->remote_wakeup;
	do_wake = udc->suspended && udc->gadget.remote_wakeup;
	spin_unlock_irqrestore(udc->lock, flags);

	if (do_wake)
@@ -2026,8 +2026,7 @@ static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq)

	/* Remote Wakeup */
	if (udc->suspended) {
		if (!udc->remote_wakeup) {

		if (!udc->gadget.remote_wakeup) {
			mReq->req.status = -EAGAIN;

			dev_dbg(mEp->device,
@@ -2404,7 +2403,7 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)

	spin_lock_irqsave(udc->lock, flags);
	udc->gadget.speed = USB_SPEED_UNKNOWN;
	udc->remote_wakeup = 0;
	udc->gadget.remote_wakeup = 0;
	udc->suspended = 0;
	udc->configured = 0;
	spin_unlock_irqrestore(udc->lock, flags);
@@ -2587,7 +2586,7 @@ __acquires(mEp->lock)
			req->length = 1;
		} else {
			/* Assume that device is bus powered for now. */
			*((u16 *)req->buf) = _udc->remote_wakeup << 1;
			*((u16 *)req->buf) = _udc->gadget.remote_wakeup << 1;
		}
		/* TODO: D1 - Remote Wakeup; D0 - Self Powered */
		retval = 0;
@@ -2857,7 +2856,7 @@ __acquires(udc->lock)
					USB_DEVICE_REMOTE_WAKEUP) {
				if (req.wLength != 0)
					break;
				udc->remote_wakeup = 0;
				udc->gadget.remote_wakeup = 0;
				err = isr_setup_status_phase(udc);
			} else {
				goto delegate;
@@ -2909,7 +2908,7 @@ __acquires(udc->lock)
					break;
				switch (le16_to_cpu(req.wValue)) {
				case USB_DEVICE_REMOTE_WAKEUP:
					udc->remote_wakeup = 1;
					udc->gadget.remote_wakeup = 1;
					err = isr_setup_status_phase(udc);
					break;
				case USB_DEVICE_B_HNP_ENABLE:
@@ -3310,7 +3309,7 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req,

	if (udc->suspended) {
		/* Remote Wakeup */
		if (!udc->remote_wakeup) {
		if (!udc->gadget.remote_wakeup) {

			dev_dbg(mEp->device,
					"%s: queue failed (suspend)."
+0 −2
Original line number Diff line number Diff line
@@ -167,8 +167,6 @@ struct ci13xxx {
	u32                        ep0_dir;    /* ep0 direction */
#define ep0out ci13xxx_ep[0]
#define ep0in  ci13xxx_ep[hw_ep_max / 2]
	u8                         remote_wakeup; /* Is remote wakeup feature
							enabled by the host? */
	u8                         suspended;  /* suspended by the host */
	u8                         configured;  /* is device configured */
	u8                         test_mode;  /* the selected test mode */
+33 −6
Original line number Diff line number Diff line
/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -90,6 +90,9 @@ struct f_mbim {
	struct mbim_ep_descs		fs;
	struct mbim_ep_descs		hs;

	const struct usb_endpoint_descriptor *in_ep_desc_backup;
	const struct usb_endpoint_descriptor *out_ep_desc_backup;

	u8				ctrl_id, data_id;
	u8				data_alt_int;

@@ -720,9 +723,7 @@ static int mbim_bam_connect(struct f_mbim *dev)

static int mbim_bam_disconnect(struct f_mbim *dev)
{
	pr_info("dev:%p port:%d. Do nothing.\n",
			dev, dev->port_num);

	pr_info("%s - dev:%p port:%d\n", __func__, dev, dev->port_num);
	bam_data_disconnect(&dev->bam_port, dev->port_num);

	return 0;
@@ -1394,14 +1395,40 @@ static void mbim_disable(struct usb_function *f)

static void mbim_suspend(struct usb_function *f)
{
	struct f_mbim	*mbim = func_to_mbim(f);

	pr_info("mbim suspended\n");

	if (mbim->cdev->gadget->remote_wakeup) {
		bam_data_suspend(MBIM_ACTIVE_PORT);
	} else {
		/*
		 * When remote wakeup is disabled, IPA BAM is disconnected
		 * because it cannot send new data until the USB bus is resumed.
		 * Endpoint descriptors info is saved before it gets reset by
		 * the BAM disconnect API. This lets us restore this info when
		 * the USB bus is resumed.
		 */
		mbim->in_ep_desc_backup  = mbim->bam_port.in->desc;
		mbim->out_ep_desc_backup = mbim->bam_port.out->desc;
		mbim_bam_disconnect(mbim);
	}
}

static void mbim_resume(struct usb_function *f)
{
	struct f_mbim	*mbim = func_to_mbim(f);

	pr_info("mbim resumed\n");

	if (mbim->cdev->gadget->remote_wakeup) {
		bam_data_resume(MBIM_ACTIVE_PORT);
	} else {
		/* Restore endpoint descriptors info. */
		mbim->bam_port.in->desc  = mbim->in_ep_desc_backup;
		mbim->bam_port.out->desc = mbim->out_ep_desc_backup;
		mbim_bam_connect(mbim);
	}
}

/*---------------------- function driver setup/binding ---------------------*/
+36 −9
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
 *
 * Copyright (C) 2003-2005,2008 David Brownell
 * Copyright (C) 2008 Nokia Corporation
 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
 * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -76,6 +76,9 @@ struct f_ecm_qc {
	u8				notify_state;
	bool				is_open;
	struct data_port		bam_port;

	const struct usb_endpoint_descriptor *in_ep_desc_backup;
	const struct usb_endpoint_descriptor *out_ep_desc_backup;
};

static struct ecm_ipa_params ipa_params;
@@ -407,10 +410,11 @@ static int ecm_qc_bam_connect(struct f_ecm_qc *dev)
	if (ret) {
		pr_err("bam_data_connect failed: err:%d\n", ret);
		return ret;
	} else {
		pr_debug("ecm bam connected\n");
	}


	pr_debug("ecm bam connected\n");

	dev->is_open = true;
	ecm_qc_notify(dev);

@@ -419,8 +423,7 @@ static int ecm_qc_bam_connect(struct f_ecm_qc *dev)

static int ecm_qc_bam_disconnect(struct f_ecm_qc *dev)
{
	pr_debug("dev:%p. Disconnect BAM.\n", dev);

	pr_debug("%s: dev:%p. Disconnect BAM.\n", __func__, dev);
	bam_data_disconnect(&dev->bam_port, 0);

	return 0;
@@ -677,16 +680,40 @@ static void ecm_qc_disable(struct usb_function *f)

static void ecm_qc_suspend(struct usb_function *f)
{
	pr_debug("ecm suspended\n");
	struct f_ecm_qc	*ecm = func_to_ecm_qc(f);

	if (f->config->cdev->gadget->remote_wakeup) {
		bam_data_suspend(ECM_QC_ACTIVE_PORT);
	} else {
		/*
		 * When remote wakeup is disabled, IPA BAM is disconnected
		 * because it cannot send new data until the USB bus is resumed.
		 * Endpoint descriptors info is saved before it gets reset by
		 * the BAM disconnect API. This lets us restore this info when
		 * the USB bus is resumed.
		 */
		ecm->in_ep_desc_backup  = ecm->bam_port.in->desc;
		ecm->out_ep_desc_backup = ecm->bam_port.out->desc;
		ecm_qc_bam_disconnect(ecm);
	}

	pr_debug("ecm suspended\n");
}

static void ecm_qc_resume(struct usb_function *f)
{
	pr_debug("ecm resumed\n");
	struct f_ecm_qc	*ecm = func_to_ecm_qc(f);

	if (f->config->cdev->gadget->remote_wakeup) {
		bam_data_resume(ECM_QC_ACTIVE_PORT);
	} else {
		/* Restore endpoint descriptors info. */
		ecm->bam_port.in->desc  = ecm->in_ep_desc_backup;
		ecm->bam_port.out->desc = ecm->out_ep_desc_backup;
		ecm_qc_bam_connect(ecm);
	}

	pr_debug("ecm resumed\n");
}

/*-------------------------------------------------------------------------*/
+37 −11
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ static struct work_struct *rndis_disconn_w;
static bool is_ipa_rndis_net_on;

struct bam_data_port {
	bool                            is_connected;
	unsigned			port_num;
	unsigned int                    ref_count;
	struct data_port		*port_usb;
@@ -200,6 +201,11 @@ static void bam2bam_data_disconnect_work(struct work_struct *w)
	struct bam_data_ch_info *d = &port->data_ch;
	int ret;

	if (!port->is_connected) {
		pr_info("%s: Already disconnected. Bailing out.\n", __func__);
		return;
	}

	if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) {
		if (d->func_type == USB_FUNC_ECM)
			ecm_ipa_disconnect(d->ipa_params.priv);
@@ -254,6 +260,11 @@ static void bam2bam_data_connect_work(struct work_struct *w)

	pr_debug("%s: Connect workqueue started", __func__);

	if (port->is_connected) {
		pr_info("%s: Already connected. Bailing out.\n", __func__);
		return;
	}

	if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) {
		if (d->func_type == USB_FUNC_MBIM) {
			ret = teth_bridge_init(&usb_notify_cb, &priv,
@@ -488,6 +499,7 @@ static int bam2bam_data_port_alloc(int portno)

	port->port_num  = portno;
	port->ref_count = 1;
	port->is_connected = false;

	INIT_WORK(&port->connect_w, bam2bam_data_connect_work);
	INIT_WORK(&port->disconnect_w, bam2bam_data_disconnect_work);
@@ -562,15 +574,14 @@ void bam_data_disconnect(struct data_port *gr, u8 port_num)
	}

	d = &port->data_ch;
	if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA)
	if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA) {
		queue_work(bam_data_wq, &port->disconnect_w);
	else {
		if (usb_bam_client_ready(false)) {
	} else {
		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,
	enum transport_type trans, u8 src_connection_idx,
@@ -593,13 +604,12 @@ int bam_data_connect(struct data_port *gr, u8 port_num,
	}

	port = bam2bam_data_ports[port_num];

	d = &port->data_ch;

	ret = usb_ep_enable(gr->in);
	if (ret) {
		pr_err("usb_ep_enable failed eptype:IN ep:%p", gr->in);
		return ret;
		goto exit;
	}
	gr->in->driver_data = port;

@@ -607,7 +617,7 @@ int bam_data_connect(struct data_port *gr, u8 port_num,
	if (ret) {
		pr_err("usb_ep_enable failed eptype:OUT ep:%p", gr->out);
		gr->in->driver_data = 0;
		return ret;
		goto exit;
	}
	gr->out->driver_data = port;

@@ -629,11 +639,15 @@ int bam_data_connect(struct data_port *gr, u8 port_num,
	if (d->trans == USB_GADGET_XPORT_BAM2BAM_IPA && d->func_type ==
		USB_FUNC_RNDIS) {
			rndis_conn_w = &port->connect_w;
			return 0;
			ret = 0;
			goto exit;
	}

	queue_work(bam_data_wq, &port->connect_w);
	ret = 0;

	return 0;
exit:
	return ret;
}

int bam_data_destroy(unsigned int no_bam2bam_port)
@@ -778,10 +792,16 @@ static void bam2bam_data_suspend_work(struct work_struct *w)

	pr_debug("%s: suspend work started\n", __func__);

	if (!port->is_connected) {
		pr_info("%s: Port is disconnected. Bailing out.\n", __func__);
		return;
	}

	ret = usb_bam_register_wake_cb(d->dst_connection_idx,
					bam_data_wake_cb, port);
	if (ret) {
		pr_err("%s(): pipe is NULL.\n", __func__);
		pr_err("%s(): Failed to register BAM wake callback.\n",
			__func__);
		return;
	}

@@ -802,9 +822,15 @@ static void bam2bam_data_resume_work(struct work_struct *w)

	pr_debug("%s: resume work started\n", __func__);

	if (!port->is_connected) {
		pr_info("%s: Port is disconnected. Bailing out.\n", __func__);
		return;
	}

	ret = usb_bam_register_wake_cb(d->dst_connection_idx, NULL, NULL);
	if (ret) {
		pr_err("%s(): pipe is NULL.\n", __func__);
		pr_err("%s(): Failed to un-register BAM wake callback.\n",
			__func__);
		return;
	}

Loading