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

Commit fc705cad authored by Mayank Rana's avatar Mayank Rana
Browse files

usb: u_data_bridge: Fix handling of USB RX requests



This change handles below cases and improves performance:
1. If USB OUT request buffer is bigger than provided read buffer
from application, then data equal to read buffer is copied. This
results into data loss. Handle this case by making sure that
whole USB OUT request buffer is provided to application across
multiple read() call.

2. If read buffer size is greater than available data as part of
one USB OUT request buffer, then copy data from other USB OUT
request buffers until read buffer is filled completely.

Also reduce USB Tx/RX buffer size from PAGE_SIZE to 2048 bytes.
It also fixes issue where USB RX requests are not being queued
after USB composition switch.

Change-Id: I8ec0e0e09dba853dd2b8dba078549ab52e5c4ce2
Signed-off-by: default avatarMayank Rana <mrana@codeaurora.org>
parent a93872da
Loading
Loading
Loading
Loading
+121 −74
Original line number Diff line number Diff line
@@ -8,6 +8,11 @@
 * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
 * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
 *
 * gbridge_port_read() API implementation is using borrowed code from
 * drivers/usb/gadget/legacy/printer.c, which is
 * Copyright (C) 2003-2005 David Brownell
 * Copyright (C) 2006 Craig W. Nadler
 *
 * 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
 * only version 2 as published by the Free Software Foundation.
@@ -39,9 +44,9 @@
#define num_of_instance 2

#define BRIDGE_RX_QUEUE_SIZE	8
#define BRIDGE_RX_BUF_SIZE	PAGE_SIZE
#define BRIDGE_RX_BUF_SIZE	2048
#define BRIDGE_TX_QUEUE_SIZE	8
#define BRIDGE_TX_BUF_SIZE	PAGE_SIZE
#define BRIDGE_TX_BUF_SIZE	2048

struct gbridge_port {
	struct cdev		gbridge_cdev;
@@ -58,11 +63,17 @@ struct gbridge_port {
	struct list_head	read_queued;
	struct list_head	write_pool;

	/* current active USB RX request */
	struct usb_request	*current_rx_req;
	/* number of pending bytes */
	size_t			pending_rx_bytes;
	/* current USB RX buffer */
	u8			*current_rx_buf;

	struct gserial		*port_usb;

	unsigned		cbits_to_modem;
	bool			cbits_updated;
	unsigned		cbits_to_host;

	bool			is_connected;
	bool			port_open;
@@ -78,6 +89,7 @@ struct class *gbridge_classp;
static dev_t gbridge_number;
static struct workqueue_struct *gbridge_wq;
static unsigned n_bridge_ports;
static void gbridge_read_complete(struct usb_ep *ep, struct usb_request *req);
static void gbridge_free_req(struct usb_ep *ep, struct usb_request *req)
{
	kfree(req->buf);
@@ -90,7 +102,7 @@ static void gbridge_free_requests(struct usb_ep *ep, struct list_head *head)

	while (!list_empty(head)) {
		req = list_entry(head->next, struct usb_request, list);
		list_del(&req->list);
		list_del_init(&req->list);
		gbridge_free_req(ep, req);
	}
}
@@ -160,6 +172,12 @@ static void gbridge_start_rx(struct gbridge_port *port)
		return;
	}

	if (!(port->is_connected && port->port_open)) {
		spin_unlock_irqrestore(&port->port_lock, flags);
		pr_debug("can't start rx\n");
		return;
	}

	pool = &port->read_pool;
	ep = port->port_usb->out;

@@ -167,7 +185,9 @@ static void gbridge_start_rx(struct gbridge_port *port)
		struct usb_request	*req;

		req = list_entry(pool->next, struct usb_request, list);
		list_del(&req->list);
		list_del_init(&req->list);
		req->length = BRIDGE_RX_BUF_SIZE;
		req->complete = gbridge_read_complete;
		spin_unlock_irqrestore(&port->port_lock, flags);
		ret = usb_ep_queue(ep, req, GFP_KERNEL);
		spin_lock_irqsave(&port->port_lock, flags);
@@ -195,17 +215,13 @@ static void gbridge_read_complete(struct usb_ep *ep, struct usb_request *req)
	}

	spin_lock_irqsave(&port->port_lock, flags);
	if (!port->port_open) {
	if (!port->port_open || req->status || !req->actual) {
		list_add_tail(&req->list, &port->read_pool);
		spin_unlock_irqrestore(&port->port_lock, flags);
		return;
	}

	if (!req->status)
	port->nbytes_from_host += req->actual;
	else
		pr_warn("read_complete with status:%d\n", req->status);

	list_add_tail(&req->list, &port->read_queued);
	spin_unlock_irqrestore(&port->port_lock, flags);

@@ -260,6 +276,10 @@ static void gbridge_start_io(struct gbridge_port *port)
	if (!port->port_usb)
		goto start_io_out;

	port->current_rx_req = NULL;
	port->pending_rx_bytes = 0;
	port->current_rx_buf = NULL;

	ret = gbridge_alloc_requests(port->port_usb->out,
				&port->read_pool,
				BRIDGE_RX_QUEUE_SIZE, BRIDGE_RX_BUF_SIZE,
@@ -303,20 +323,24 @@ static void gbridge_stop_io(struct gbridge_port *port)
	out = port->port_usb->out;
	spin_unlock_irqrestore(&port->port_lock, flags);

	usb_ep_fifo_flush(in);
	usb_ep_fifo_flush(out);
	/* disable endpoints, aborting down any active I/O */
	usb_ep_disable(out);
	out->driver_data = NULL;
	usb_ep_disable(in);
	in->driver_data = NULL;

	spin_lock_irqsave(&port->port_lock, flags);
	if (port->port_usb) {
		gbridge_free_requests(out, &port->read_pool);
		gbridge_free_requests(in, &port->write_pool);
		port->cbits_to_host = 0;
	if (port->current_rx_req != NULL) {
		kfree(port->current_rx_req->buf);
		usb_ep_free_request(out, port->current_rx_req);
	}

	if (port->port_usb->send_modem_ctrl_bits)
		port->port_usb->send_modem_ctrl_bits(
					port->port_usb,
					port->cbits_to_host);
	port->pending_rx_bytes = 0;
	port->current_rx_buf = NULL;

	gbridge_free_requests(out, &port->read_queued);
	gbridge_free_requests(out, &port->read_pool);
	gbridge_free_requests(in, &port->write_pool);
	spin_unlock_irqrestore(&port->port_lock, flags);
}

@@ -325,7 +349,6 @@ int gbridge_port_open(struct inode *inode, struct file *file)
	int ret;
	unsigned long flags;
	struct gbridge_port *port;
	struct gserial *gser;

	port = container_of(inode->i_cdev, struct gbridge_port,
							gbridge_cdev);
@@ -350,11 +373,8 @@ int gbridge_port_open(struct inode *inode, struct file *file)

	spin_lock_irqsave(&port->port_lock, flags);
	port->port_open = true;
	gser = port->port_usb;
	spin_unlock_irqrestore(&port->port_lock, flags);
	gbridge_start_io(port);
	if (gser && gser->connect)
		gser->connect(gser);
	gbridge_start_rx(port);

	pr_debug("port(%p) open is success\n", port);

@@ -365,7 +385,6 @@ int gbridge_port_release(struct inode *inode, struct file *file)
{
	unsigned long flags;
	struct gbridge_port *port;
	struct gserial *gser;

	port = file->private_data;
	if (!port) {
@@ -375,14 +394,9 @@ int gbridge_port_release(struct inode *inode, struct file *file)

	pr_debug("closing port(%p)\n", port);
	spin_lock_irqsave(&port->port_lock, flags);
	gser = port->port_usb;
	if (gser && gser->disconnect)
		gser->disconnect(gser);

	port->port_open = false;
	port->cbits_updated = false;
	spin_unlock_irqrestore(&port->port_lock, flags);
	gbridge_stop_io(port);
	pr_debug("port(%p) is closed.\n", port);

	return 0;
@@ -393,12 +407,13 @@ ssize_t gbridge_port_read(struct file *file,
		       size_t count,
		       loff_t *ppos)
{
	int ret;
	unsigned size;
	unsigned long flags;
	struct gbridge_port *port;
	struct usb_request *req;
	struct list_head *pool;
	struct usb_request *current_rx_req;
	size_t pending_rx_bytes, bytes_copied = 0, size;
	u8 *current_rx_buf;

	port = file->private_data;
	if (!port) {
@@ -406,42 +421,85 @@ ssize_t gbridge_port_read(struct file *file,
		return -EINVAL;
	}

	spin_lock_irqsave(&port->port_lock, flags);
	pr_debug("read on port(%p) count:%zu\n", port, count);
	if (list_empty(&port->read_queued)) {
	spin_lock_irqsave(&port->port_lock, flags);
	current_rx_req = port->current_rx_req;
	pending_rx_bytes = port->pending_rx_bytes;
	current_rx_buf = port->current_rx_buf;
	port->current_rx_req = NULL;
	port->current_rx_buf = NULL;
	port->pending_rx_bytes = 0;
	bytes_copied = 0;

	if (list_empty(&port->read_queued) && !pending_rx_bytes) {
		spin_unlock_irqrestore(&port->port_lock, flags);
		pr_debug("%s(): read_queued list is empty.\n", __func__);
		ret = 0;
		goto start_rx;
	}

	/*
	 * Consider below cases:
	 * 1. If available read buffer size (i.e. count value) is greater than
	 * available data as part of one USB OUT request buffer, then consider
	 * copying multiple USB OUT request buffers until read buffer is filled.
	 * 2. If available read buffer size (i.e. count value) is smaller than
	 * available data as part of one USB OUT request buffer, then copy this
	 * buffer data across multiple read() call until whole USB OUT request
	 * buffer is copied.
	 */
	while ((pending_rx_bytes || !list_empty(&port->read_queued)) && count) {
		if (pending_rx_bytes == 0) {
			pool = &port->read_queued;
			req = list_first_entry(pool, struct usb_request, list);
	list_del(&req->list);
			list_del_init(&req->list);
			current_rx_req = req;
			pending_rx_bytes = req->actual;
			current_rx_buf = req->buf;
		}

		spin_unlock_irqrestore(&port->port_lock, flags);
		size = count;
		if (size > pending_rx_bytes)
			size = pending_rx_bytes;

	size = (req->actual < count) ? req->actual : count;
	pr_debug("req->actual:%u count:%zu size:%u\n",
				req->actual, count, size);
	if (req->actual > count)
		pr_err("%s(): (%zu) bytes are being dropped.\n",
				__func__, (req->actual - count));
	ret = copy_to_user(buf, req->buf, size);
	if (ret) {
		pr_err("copy_to_user failed: err %d\n", ret);
		ret = -EFAULT;
	} else {
		pr_debug("copied %d bytes to userspace\n", req->actual);
		spin_lock_irqsave(&port->port_lock, flags);
		ret = size;
		pr_debug("pending_rx_bytes:%zu count:%zu size:%zu\n",
					pending_rx_bytes, count, size);
		size -= copy_to_user(buf, current_rx_buf, size);
		port->nbytes_to_port_bridge += size;
		list_add_tail(&req->list, &port->read_pool);
		bytes_copied += size;
		count -= size;
		buf += size;

		spin_lock_irqsave(&port->port_lock, flags);
		if (!port->is_connected) {
			list_add_tail(&current_rx_req->list, &port->read_pool);
			spin_unlock_irqrestore(&port->port_lock, flags);
			return -EAGAIN;
		}

		/*
		 * partial data available, then update pending_rx_bytes,
		 * otherwise add USB request back to read_pool for next data.
		 */
		if (size < pending_rx_bytes) {
			pending_rx_bytes -= size;
			current_rx_buf += size;
		} else {
			list_add_tail(&current_rx_req->list, &port->read_pool);
			pending_rx_bytes = 0;
			current_rx_req = NULL;
			current_rx_buf = NULL;
		}
	}

	port->pending_rx_bytes = pending_rx_bytes;
	port->current_rx_buf = current_rx_buf;
	port->current_rx_req = current_rx_req;
	spin_unlock_irqrestore(&port->port_lock, flags);

start_rx:
	gbridge_start_rx(port);
	return ret;
	return bytes_copied;
}

ssize_t gbridge_port_write(struct file *file,
@@ -471,7 +529,7 @@ ssize_t gbridge_port_write(struct file *file,
	}
	pool = &port->write_pool;
	req = list_first_entry(pool, struct usb_request, list);
	list_del(&req->list);
	list_del_init(&req->list);
	spin_unlock_irqrestore(&port->port_lock, flags);

	pr_debug("%s: write buf size:%zu\n", __func__, count);
@@ -710,13 +768,12 @@ static ssize_t debug_gbridge_read_stats(struct file *file, char __user *ubuf,
				"nbytes_to_port_bridge:  %lu\n"
				"nbytes_from_port_bridge: %lu\n"
				"cbits_to_modem:  %u\n"
				"cbits_to_host: %u\n"
				"Port Opened: %s\n",
				i, port->nbytes_to_host,
				port->nbytes_from_host,
				port->nbytes_to_port_bridge,
				port->nbytes_from_port_bridge,
				port->cbits_to_modem, port->cbits_to_host,
				port->cbits_to_modem,
				(port->port_open ? "Opened" : "Closed"));
		spin_unlock_irqrestore(&port->port_lock, flags);
	}
@@ -828,6 +885,7 @@ int gbridge_connect(void *gptr, u8 portno)
	port->is_connected = true;
	spin_unlock_irqrestore(&port->port_lock, flags);

	gbridge_start_io(port);
	wake_up(&port->open_wq);
	return 0;
}
@@ -852,25 +910,14 @@ void gbridge_disconnect(void *gptr, u8 portno)
	port = ports[portno];
	gser = gptr;

	gbridge_stop_io(port);
	spin_lock_irqsave(&port->port_lock, flags);
	port->is_connected = false;
	port->port_usb = 0;
	spin_unlock_irqrestore(&port->port_lock, flags);

	/* disable endpoints, aborting down any active I/O */
	usb_ep_disable(gser->out);
	gser->out->driver_data = NULL;
	usb_ep_disable(gser->in);
	gser->in->driver_data = NULL;

	spin_lock_irqsave(&port->port_lock, flags);
	gbridge_free_requests(gser->out, &port->read_pool);
	gbridge_free_requests(gser->in, &port->write_pool);
	port->port_usb = NULL;

	port->nbytes_from_host = port->nbytes_to_host = 0;
	port->nbytes_to_port_bridge = 0;
	spin_unlock_irqrestore(&port->port_lock, flags);

}

static void gbridge_port_free(int portno)