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

Commit 99b4f5af authored by Hemant Kumar's avatar Hemant Kumar Committed by Gerrit - the friendly Code Review server
Browse files

usb: gadget: f_mbim: Queue notification request upon function resume



In super speed mode if userspace issues a write after usb bus suspend
usb_func_ep_queue() schedules wakeup to resume the function. After that
it queues the request which fails with -ENOTSUPP. As a result no
notification request queued to hw and write request gets delayed to be
sent until another write request comes and queues notification request
after function resume. This causes mismatch to the mbim request response.
Fix this by queuing the notification request upon function resume if
notify count is greater than zero. Also, drop control packet after bus
suspend if remote wakeup is not supported or if ep enqueue returns error
other than -EAGAIN.

CRs-Fixed: 789467
Change-Id: I446de1eb169b4ccb8f4db5f003b622d7b9c0b22b
Signed-off-by: default avatarHemant Kumar <hemantk@codeaurora.org>
parent bbbf11a2
Loading
Loading
Loading
Loading
+3 −38
Original line number Diff line number Diff line
@@ -331,6 +331,7 @@ int usb_interface_id(struct usb_configuration *config,

	if (id < MAX_CONFIG_INTERFACES) {
		config->interface[id] = function;
		function->intf_id = id;
		config->next_interface_id = id + 1;
		return id;
	}
@@ -338,35 +339,9 @@ int usb_interface_id(struct usb_configuration *config,
}
EXPORT_SYMBOL_GPL(usb_interface_id);

/**
 * usb_get_func_interface_id() - Find the interface ID of a function
 * @function: the function for which want to find the interface ID
 * Context: single threaded
 *
 * Returns the interface ID of the function or -ENODEV if this function
 * is not part of this configuration
 */
int usb_get_func_interface_id(struct usb_function *func)
{
	int id;
	struct usb_configuration *config;

	if (!func)
		return -EINVAL;

	config = func->config;

	for (id = 0; id < MAX_CONFIG_INTERFACES; id++) {
		if (config->interface[id] == func)
			return id;
	}
	return -ENODEV;
}

static int usb_func_wakeup_int(struct usb_function *func)
{
	int ret;
	int interface_id;
	unsigned long flags;
	struct usb_gadget *gadget;
	struct usb_composite_dev *cdev;
@@ -390,19 +365,9 @@ static int usb_func_wakeup_int(struct usb_function *func)
	}

	cdev = get_gadget_data(gadget);
	spin_lock_irqsave(&cdev->lock, flags);
	ret = usb_get_func_interface_id(func);
	if (ret < 0) {
		ERROR(func->config->cdev,
			"Function %s - Unknown interface id. Canceling USB request. ret=%d\n",
			func->name ? func->name : "", ret);

		spin_unlock_irqrestore(&cdev->lock, flags);
		return ret;
	}

	interface_id = ret;
	ret = usb_gadget_func_wakeup(gadget, interface_id);
	spin_lock_irqsave(&cdev->lock, flags);
	ret = usb_gadget_func_wakeup(gadget, func->intf_id);
	spin_unlock_irqrestore(&cdev->lock, flags);

	return ret;
+63 −127
Original line number Diff line number Diff line
@@ -130,6 +130,7 @@ struct f_mbim {
	u16			ntb_max_datagrams;

	atomic_t		error;
	unsigned int		cpkt_drop_cnt;
};

struct mbim_ntb_input_size {
@@ -591,114 +592,6 @@ void fmbim_free_req(struct usb_ep *ep, struct usb_request *req)
	}
}

static void fmbim_ctrl_response_available(struct f_mbim *dev)
{
	const unsigned int		max_ep_queue_trials = 10;

	struct usb_request		*req = dev->not_port.notify_req;
	struct usb_cdc_notification	*event = NULL;
	unsigned long			flags;
	int				ret;
	int                             ep_queue_trials;

	pr_debug("dev:%p portno#%d\n", dev, dev->port_num);

	spin_lock_irqsave(&dev->lock, flags);

	if (!atomic_read(&dev->online)) {
		pr_err("dev:%p is not online\n", dev);
		spin_unlock_irqrestore(&dev->lock, flags);
		return;
	}

	if (!req) {
		pr_err("dev:%p req is NULL\n", dev);
		spin_unlock_irqrestore(&dev->lock, flags);
		return;
	}

	if (!req->buf) {
		pr_err("dev:%p req->buf is NULL\n", dev);
		spin_unlock_irqrestore(&dev->lock, flags);
		return;
	}

	if (atomic_inc_return(&dev->not_port.notify_count) != 1) {
		pr_debug("delay ep_queue: notifications queue is busy[%d]\n",
			atomic_read(&dev->not_port.notify_count));
		spin_unlock_irqrestore(&dev->lock, flags);
		return;
	}

	req->length = sizeof *event;
	event = req->buf;
	event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
			| USB_RECIP_INTERFACE;
	event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE;
	event->wValue = cpu_to_le16(0);
	event->wIndex = cpu_to_le16(dev->ctrl_id);
	event->wLength = cpu_to_le16(0);
	spin_unlock_irqrestore(&dev->lock, flags);

	ep_queue_trials = 0;
	while (ep_queue_trials <= max_ep_queue_trials) {
		ret = usb_func_ep_queue(&dev->function, dev->not_port.notify,
			   req, GFP_ATOMIC);

		ep_queue_trials++;

		if (ret == -EAGAIN) {
			pr_debug("ep queueing is delayed (-EAGAIN).\n");
			usleep_range(1000, 3000);
		} else {
			break;
		}
	}

	if (ret) {
		atomic_dec(&dev->not_port.notify_count);
		pr_err("ep enqueue error %d\n", ret);
	}

	pr_debug("Successful Exit\n");
}

static int
fmbim_send_cpkt_response(struct f_mbim *gr, struct ctrl_pkt *cpkt)
{
	struct f_mbim	*dev = gr;
	unsigned long	flags;

	if (!gr || !cpkt) {
		pr_err("Invalid cpkt, dev:%p cpkt:%p\n",
				gr, cpkt);
		return -ENODEV;
	}

	pr_debug("dev:%p port_num#%d\n", dev, dev->port_num);

	if (!atomic_read(&dev->online)) {
		pr_err("dev:%p is not connected\n", dev);
		mbim_free_ctrl_pkt(cpkt);
		return 0;
	}

	if (dev->not_port.notify_state != MBIM_NOTIFY_RESPONSE_AVAILABLE) {
		pr_err("dev:%p state=%d, recover!!\n", dev,
			dev->not_port.notify_state);
		mbim_free_ctrl_pkt(cpkt);
		return 0;
	}

	spin_lock_irqsave(&dev->lock, flags);
	list_add_tail(&cpkt->list, &dev->cpkt_resp_q);
	spin_unlock_irqrestore(&dev->lock, flags);

	fmbim_ctrl_response_available(dev);

	return 0;
}

/* ---------------------------- BAM INTERFACE ----------------------------- */

static int mbim_bam_setup(int no_ports)
@@ -1496,6 +1389,7 @@ static int mbim_func_suspend(struct usb_function *f, unsigned char options)
	} else {
		if (f->func_is_suspended) {
			f->func_is_suspended = false;
			mbim_do_notify(mbim);
			mbim_resume(f);
		}
		f->func_wakeup_allowed = func_wakeup_allowed;
@@ -1528,6 +1422,7 @@ mbim_bind(struct usb_configuration *c, struct usb_function *f)
	struct f_mbim			*mbim = func_to_mbim(f);
	int				status;
	struct usb_ep			*ep;
	struct usb_cdc_notification	*event;

	pr_info("Enter\n");

@@ -1598,6 +1493,14 @@ mbim_bind(struct usb_configuration *c, struct usb_function *f)

	mbim->not_port.notify_req->context = mbim;
	mbim->not_port.notify_req->complete = mbim_notify_complete;
	mbim->not_port.notify_req->length = sizeof(*event);
	event = mbim->not_port.notify_req->buf;
	event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
			| USB_RECIP_INTERFACE;
	event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE;
	event->wValue = cpu_to_le16(0);
	event->wIndex = cpu_to_le16(mbim->ctrl_id);
	event->wLength = cpu_to_le16(0);

	if (mbim->xport == USB_GADGET_XPORT_BAM2BAM_IPA)
		mbim_desc.wMaxSegmentSize = cpu_to_le16(0x800);
@@ -1891,24 +1794,21 @@ mbim_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos)
{
	struct f_mbim *dev = fp->private_data;
	struct ctrl_pkt *cpkt = NULL;
	struct usb_request *req = dev->not_port.notify_req;
	int ret = 0;
	unsigned long flags;

	pr_debug("Enter(%zu)\n", count);

	if (!dev) {
		pr_err("Received NULL mbim pointer\n");
		return -ENODEV;
	}

	if (!count) {
		pr_err("zero length ctrl pkt\n");
	if (!dev || !req || !req->buf) {
		pr_err("%s: dev %p req %p req->buf %p\n",
			__func__, dev, req, req ? req->buf : req);
		return -ENODEV;
	}

	if (count > MAX_CTRL_PKT_SIZE) {
		pr_err("given pkt size too big:%zu > max_pkt_size:%d\n",
				count, MAX_CTRL_PKT_SIZE);
		return -ENOMEM;
	if (!count || count > MAX_CTRL_PKT_SIZE) {
		pr_err("error: ctrl pkt lenght %zu\n", count);
		return -EINVAL;
	}

	if (mbim_lock(&dev->write_excl)) {
@@ -1922,6 +1822,20 @@ mbim_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos)
		return -EPIPE;
	}

	if (dev->not_port.notify_state != MBIM_NOTIFY_RESPONSE_AVAILABLE) {
		pr_err("dev:%p state=%d error\n", dev,
			dev->not_port.notify_state);
		mbim_unlock(&dev->write_excl);
		return -EINVAL;
	}

	if (dev->function.func_is_suspended &&
			!dev->function.func_wakeup_allowed) {
		dev->cpkt_drop_cnt++;
		pr_err("drop ctrl pkt of len %zu\n", count);
		return -ENOTSUPP;
	}

	cpkt = mbim_alloc_ctrl_pkt(count, GFP_KERNEL);
	if (!cpkt) {
		pr_err("failed to allocate ctrl pkt\n");
@@ -1934,17 +1848,39 @@ mbim_write(struct file *fp, const char __user *buf, size_t count, loff_t *pos)
		pr_err("copy_from_user failed err:%d\n", ret);
		mbim_free_ctrl_pkt(cpkt);
		mbim_unlock(&dev->write_excl);
		return 0;
		return ret;
	}

	fmbim_send_cpkt_response(dev, cpkt);
	spin_lock_irqsave(&dev->lock, flags);
	list_add_tail(&cpkt->list, &dev->cpkt_resp_q);

	if (atomic_inc_return(&dev->not_port.notify_count) != 1) {
		pr_debug("delay ep_queue: notifications queue is busy[%d]\n",
			atomic_read(&dev->not_port.notify_count));
		spin_unlock_irqrestore(&dev->lock, flags);
		mbim_unlock(&dev->write_excl);
		return count;
	}
	spin_unlock_irqrestore(&dev->lock, flags);

	ret = usb_func_ep_queue(&dev->function, dev->not_port.notify,
			   req, GFP_ATOMIC);
	if (ret == -ENOTSUPP || (ret < 0 && ret != -EAGAIN)) {
		spin_lock_irqsave(&dev->lock, flags);
		list_del(&cpkt->list);
		spin_unlock_irqrestore(&dev->lock, flags);
		dev->cpkt_drop_cnt++;
		atomic_dec(&dev->not_port.notify_count);
		pr_err("drop ctrl pkt of len %d error %d\n", cpkt->len, ret);
		mbim_free_ctrl_pkt(cpkt);
	} else {
		ret = 0;
	}
	mbim_unlock(&dev->write_excl);

	pr_debug("Exit(%zu)\n", count);

	return count;

	return ret ? ret : count;
}

static int mbim_open(struct inode *ip, struct file *fp)
+17 −13
Original line number Diff line number Diff line
@@ -441,29 +441,33 @@ EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver);
int usb_func_ep_queue(struct usb_function *func, struct usb_ep *ep,
			       struct usb_request *req, gfp_t gfp_flags)
{
	int ret;
	int ret = -ENOTSUPP;
	struct usb_gadget *gadget;

	if (!func || !ep || !req) {
		pr_err("Invalid argument. func=%p, ep=%p, req=%p\n",
			func, ep, req);
	if (!func || !func->config || !func->config->cdev ||
			!func->config->cdev->gadget || !ep || !req)
		return -EINVAL;
	}

	pr_debug("Function %s queueing new data into ep %u\n",
		func->name ? func->name : "", ep->address);

	gadget = func->config->cdev->gadget;
	if ((gadget->speed == USB_SPEED_SUPER) && func->func_is_suspended) {
		ret = usb_func_wakeup(func);
		if (ret) {
			pr_err("Failed to send function wake up notification. func name:%s, ep:%u\n",
				func->name ? func->name : "",
				ep->address);
			return ret;

	if (func->func_is_suspended && func->func_wakeup_allowed) {
		ret = usb_gadget_func_wakeup(gadget, func->intf_id);
		if (ret == -EAGAIN) {
			pr_debug("bus suspended func wakeup for %s delayed until bus resume.\n",
				func->name ? func->name : "");
		} else if (ret < 0 && ret != -ENOTSUPP) {
			pr_err("Failed to wake function %s from suspend state. ret=%d.\n",
				func->name ? func->name : "", ret);
		}
	}

	if (!func->func_is_suspended)
		ret = 0;

	if (!ret)
		ret = usb_ep_queue(ep, req, gfp_flags);
	return ret;
}
+1 −0
Original line number Diff line number Diff line
@@ -129,6 +129,7 @@ struct usb_configuration;

struct usb_function {
	const char			*name;
	int				intf_id;
	struct usb_gadget_strings	**strings;
	struct usb_descriptor_header	**fs_descriptors;
	struct usb_descriptor_header	**hs_descriptors;