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

Commit 23061549 authored by Danny Segal's avatar Danny Segal Committed by Gerrit - the friendly Code Review server
Browse files

usb: gadget: add MBIM support for super-speed function suspend



The USB 3.0 specification defines a new 'Function Suspend' feature.
This feature enables the USB host to put inactive composite device
functions in a suspended state even if the device itself is not
suspended.
This patch adds support for this feature in the MBIM function and
the transport layer.

Change-Id: I0ff9a2af5ddc94679087f5a21f7740adfb1ce708
Signed-off-by: default avatarDanny Segal <dsegal@codeaurora.org>
parent 38be2e8f
Loading
Loading
Loading
Loading
+123 −10
Original line number Diff line number Diff line
@@ -707,7 +707,7 @@ static void fmbim_ctrl_response_available(struct f_mbim *dev)
	event->wLength = cpu_to_le16(0);
	spin_unlock_irqrestore(&dev->lock, flags);

	ret = usb_ep_queue(dev->not_port.notify,
	ret = usb_func_ep_queue(&dev->function, dev->not_port.notify,
			   req, GFP_ATOMIC);
	if (ret) {
		atomic_dec(&dev->not_port.notify_count);
@@ -912,7 +912,9 @@ static void mbim_do_notify(struct f_mbim *mbim)
		}

		spin_unlock(&mbim->lock);
		status = usb_ep_queue(mbim->not_port.notify, req, GFP_ATOMIC);
		status = usb_func_ep_queue(&mbim->function,
				mbim->not_port.notify,
				req, GFP_ATOMIC);
		spin_lock(&mbim->lock);
		if (status) {
			atomic_dec(&mbim->not_port.notify_count);
@@ -935,11 +937,12 @@ static void mbim_do_notify(struct f_mbim *mbim)
	pr_debug("queue request: notify_count = %d\n",
		atomic_read(&mbim->not_port.notify_count));
	spin_unlock(&mbim->lock);
	status = usb_ep_queue(mbim->not_port.notify, req, GFP_ATOMIC);
	status = usb_func_ep_queue(&mbim->function, mbim->not_port.notify, req,
			GFP_ATOMIC);
	spin_lock(&mbim->lock);
	if (status) {
		atomic_dec(&mbim->not_port.notify_count);
		pr_err("usb_ep_queue failed, err: %d\n", status);
		pr_err("usb_func_ep_queue failed, err: %d\n", status);
	}
}

@@ -1258,6 +1261,7 @@ mbim_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
		req->zero = (value < w_length);
		req->length = value;
		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);

		if (value < 0) {
			pr_err("queueing req failed: %02x.%02x, err %d\n",
				ctrl->bRequestType,
@@ -1336,6 +1340,8 @@ static int mbim_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
	struct usb_composite_dev *cdev = mbim->cdev;
	int ret = 0;

	pr_debug("intf=%u, alt=%u\n", intf, alt);

	/* Control interface has only altsetting 0 */
	if (intf == mbim->ctrl_id) {

@@ -1474,6 +1480,7 @@ static void mbim_disable(struct usb_function *f)

	pr_info("SET DEVICE OFFLINE\n");
	atomic_set(&mbim->online, 0);
	mbim->data_alt_int = 0;

	mbim->not_port.notify_state = MBIM_NOTIFY_NONE;

@@ -1502,14 +1509,33 @@ static void mbim_disable(struct usb_function *f)

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

	pr_info("mbim suspended\n");

	pr_debug("%s(): remote_wakeup:%d\n:", __func__,
			mbim->cdev->gadget->remote_wakeup);
	if (mbim->cdev->gadget->remote_wakeup ||
			(mbim->cdev->gadget->speed == USB_SPEED_SUPER)) {

	/* If the function is in Function Suspend state, avoid suspending the
	 * MBIM function again.
	 */
	if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) &&
		f->func_is_suspended)
		return;

	if (mbim->cdev->gadget->speed == USB_SPEED_SUPER)
		remote_wakeup_allowed = f->func_wakeup_allowed;
	else
		remote_wakeup_allowed = mbim->cdev->gadget->remote_wakeup;

	/* MBIM data interface is up only when alt setting is set to 1. */
	if (mbim->data_alt_int == 0) {
		pr_debug("MBIM data interface is not opened. Returning\n");
		return;
	}

	if (remote_wakeup_allowed) {
		bam_data_suspend(MBIM_ACTIVE_PORT);
	} else {
		/*
@@ -1519,29 +1545,114 @@ static void mbim_suspend(struct usb_function *f)
		 * the BAM disconnect API. This lets us restore this info when
		 * the USB bus is resumed.
		 */
		if (mbim->bam_port.in->desc)
			mbim->in_ep_desc_backup  = mbim->bam_port.in->desc;

		if (mbim->bam_port.out->desc)
			mbim->out_ep_desc_backup = mbim->bam_port.out->desc;

		pr_debug("in_ep_desc_backup = %p, out_ep_desc_backup = %p",
			mbim->in_ep_desc_backup, mbim->out_ep_desc_backup);

		mbim_bam_disconnect(mbim);
	}
}

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

	pr_info("mbim resumed\n");

	if (mbim->cdev->gadget->remote_wakeup ||
			(mbim->cdev->gadget->speed == USB_SPEED_SUPER)) {
	/*
	 * If the function is in USB3 Function Suspend state, resume is
	 * canceled. In this case resume is done by a Function Resume request.
	 */
	if ((mbim->cdev->gadget->speed == USB_SPEED_SUPER) &&
		f->func_is_suspended)
		return;

	if (mbim->cdev->gadget->speed == USB_SPEED_SUPER)
		remote_wakeup_allowed = f->func_wakeup_allowed;
	else
		remote_wakeup_allowed = mbim->cdev->gadget->remote_wakeup;

	/* MBIM data interface is up only when alt setting is set to 1. */
	if (mbim->data_alt_int == 0) {
		pr_debug("MBIM data interface is not opened. Returning\n");
		return;
	}

	if (remote_wakeup_allowed) {
		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;

		pr_debug("in_ep_desc_backup = %p, out_ep_desc_backup = %p",
			mbim->in_ep_desc_backup, mbim->out_ep_desc_backup);

		mbim_bam_connect(mbim);
	}
}

static int mbim_func_suspend(struct usb_function *f, unsigned char options)
{
	enum {
		MBIM_FUNC_SUSPEND_MASK   = 0x1,
		MBIM_FUNC_WAKEUP_EN_MASK = 0x2
	};

	bool func_wakeup_allowed;
	struct f_mbim	*mbim = func_to_mbim(f);

	if (f == NULL)
		return -EINVAL;

	pr_debug("Got Function Suspend(%u) command for %s function\n",
		options, f->name ? f->name : "");

	/* Function Suspend is supported by Super Speed devices only */
	if (mbim->cdev->gadget->speed != USB_SPEED_SUPER)
		return -ENOTSUPP;

	func_wakeup_allowed =
		((options & MBIM_FUNC_WAKEUP_EN_MASK) != 0);

	if (options & MBIM_FUNC_SUSPEND_MASK) {
		f->func_wakeup_allowed = func_wakeup_allowed;
		if (!f->func_is_suspended) {
			mbim_suspend(f);
			f->func_is_suspended = true;
		}
	} else {
		if (f->func_is_suspended) {
			f->func_is_suspended = false;
			mbim_resume(f);
		}
		f->func_wakeup_allowed = func_wakeup_allowed;
	}

	return 0;
}

static int mbim_get_status(struct usb_function *f)
{
	enum {
		MBIM_STS_FUNC_WAKEUP_CAP_SHIFT  = 0,
		MBIM_STS_FUNC_WAKEUP_EN_SHIFT   = 1
	};

	unsigned remote_wakeup_enabled_bit;
	const unsigned remote_wakeup_capable_bit = 1;

	remote_wakeup_enabled_bit = f->func_wakeup_allowed ? 1 : 0;
	return (remote_wakeup_enabled_bit << MBIM_STS_FUNC_WAKEUP_EN_SHIFT) |
		(remote_wakeup_capable_bit << MBIM_STS_FUNC_WAKEUP_CAP_SHIFT);
}

/*---------------------- function driver setup/binding ---------------------*/

static int
@@ -1790,6 +1901,8 @@ int mbim_bind_config(struct usb_configuration *c, unsigned portno,
	mbim->function.setup = mbim_setup;
	mbim->function.disable = mbim_disable;
	mbim->function.suspend = mbim_suspend;
	mbim->function.func_suspend = mbim_func_suspend;
	mbim->function.get_status = mbim_get_status;
	mbim->function.resume = mbim_resume;
	mbim->xport = str_to_xport(xport_name);

+33 −2
Original line number Diff line number Diff line
@@ -1410,8 +1410,11 @@ free_bam_ports:

static int bam_data_wake_cb(void *param)
{
	int ret;
	struct bam_data_port *port = (struct bam_data_port *)param;
	struct data_port *d_port = port->port_usb;
	struct usb_gadget *gadget;
	struct usb_function *func;

	pr_debug("%s: woken up by peer\n", __func__);

@@ -1425,12 +1428,40 @@ static int bam_data_wake_cb(void *param)
		return -ENODEV;
	}

	if (!d_port->cdev->gadget) {
	gadget = d_port->cdev->gadget;
	if (!gadget) {
		pr_err("FAILED: d_port->cdev->gadget == NULL");
		return -ENODEV;
	}

	return usb_gadget_wakeup(d_port->cdev->gadget);
	func = d_port->func;

	/*
	 * In Super-Speed mode, remote wakeup is not allowed for suspended
	 * functions which have been disallowed by the host to issue Funtion
	 * 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) &&
		!func->func_wakeup_allowed)
		return -ENOTSUPP;

	ret = usb_gadget_wakeup(gadget);
	if (ret) {
		pr_err("Failed to wake up the USB core. ret=%d", ret);
		return ret;
	}

	if (gadget->speed == USB_SPEED_SUPER) {
		ret = usb_func_wakeup(func);
		if (ret)
			return ret;
	}

	return 0;
}

static void bam_data_start(void *param, enum usb_bam_pipe_dir dir)