usb: serial: Add flow control between wwan and tty drivers
wwan driver is dropping the data if tty is throttled or unable to
accept more data. Add flow control between wwan and tty to fix
random data dropping.
CRs-Fixed: 373449
Change-Id: I524ee5e505e2e7e88df8fd669ae65bada4b235bf
Signed-off-by: Vamsi Krishna <vskrishn@codeaurora.org>
diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c
index 1f6d915..a749a6d 100644
--- a/drivers/usb/serial/qcserial.c
+++ b/drivers/usb/serial/qcserial.c
@@ -284,6 +284,8 @@
.write = usb_wwan_write,
.write_room = usb_wwan_write_room,
.chars_in_buffer = usb_wwan_chars_in_buffer,
+ .throttle = usb_wwan_throttle,
+ .unthrottle = usb_wwan_unthrottle,
.attach = usb_wwan_startup,
.disconnect = usb_wwan_disconnect,
.release = qc_release,
diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h
index 9811a82..98b399f 100644
--- a/drivers/usb/serial/usb-wwan.h
+++ b/drivers/usb/serial/usb-wwan.h
@@ -24,6 +24,8 @@
extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
const unsigned char *buf, int count);
extern int usb_wwan_chars_in_buffer(struct tty_struct *tty);
+extern void usb_wwan_throttle(struct tty_struct *tty);
+extern void usb_wwan_unthrottle(struct tty_struct *tty);
#ifdef CONFIG_PM
extern int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message);
extern int usb_wwan_resume(struct usb_serial *serial);
@@ -33,7 +35,7 @@
#define N_IN_URB 5
#define N_OUT_URB 5
-#define IN_BUFLEN 65536
+#define IN_BUFLEN 16384
#define OUT_BUFLEN 65536
struct usb_wwan_intf_private {
@@ -55,6 +57,10 @@
int opened;
struct usb_anchor submitted;
struct usb_anchor delayed;
+ struct list_head in_urb_list;
+ spinlock_t in_lock;
+ ssize_t n_read;
+ struct work_struct in_work;
/* Settings for the port */
int rts_state; /* Handshaking pins (outputs) */
diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c
index 0c58554..bf30c0b 100644
--- a/drivers/usb/serial/usb_wwan.c
+++ b/drivers/usb/serial/usb_wwan.c
@@ -279,15 +279,78 @@
}
EXPORT_SYMBOL(usb_wwan_write);
+static void usb_wwan_in_work(struct work_struct *w)
+{
+ struct usb_wwan_port_private *portdata =
+ container_of(w, struct usb_wwan_port_private, in_work);
+ struct list_head *q = &portdata->in_urb_list;
+ struct urb *urb;
+ unsigned char *data;
+ struct tty_struct *tty;
+ struct usb_serial_port *port;
+ int err;
+ ssize_t len;
+ ssize_t count;
+ unsigned long flags;
+
+ spin_lock_irqsave(&portdata->in_lock, flags);
+ while (!list_empty(q)) {
+ urb = list_first_entry(q, struct urb, urb_list);
+ port = urb->context;
+ if (port->throttle_req || port->throttled)
+ break;
+
+ tty = tty_port_tty_get(&port->port);
+ if (!tty)
+ continue;
+
+ list_del_init(&urb->urb_list);
+
+ spin_unlock_irqrestore(&portdata->in_lock, flags);
+
+ len = urb->actual_length - portdata->n_read;
+ data = urb->transfer_buffer + portdata->n_read;
+ count = tty_insert_flip_string(tty, data, len);
+ tty_flip_buffer_push(tty);
+ tty_kref_put(tty);
+
+ if (count < len) {
+ dbg("%s: len:%d count:%d n_read:%d\n", __func__,
+ len, count, portdata->n_read);
+ portdata->n_read += count;
+ port->throttled = true;
+
+ /* add request back to list */
+ spin_lock_irqsave(&portdata->in_lock, flags);
+ list_add(&urb->urb_list, q);
+ spin_unlock_irqrestore(&portdata->in_lock, flags);
+ return;
+ }
+ portdata->n_read = 0;
+
+ usb_anchor_urb(urb, &portdata->submitted);
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ usb_unanchor_urb(urb);
+ if (err != -EPERM)
+ pr_err("%s: submit read urb failed:%d",
+ __func__, err);
+ }
+
+ usb_mark_last_busy(port->serial->dev);
+ spin_lock_irqsave(&portdata->in_lock, flags);
+ }
+ spin_unlock_irqrestore(&portdata->in_lock, flags);
+}
+
static void usb_wwan_indat_callback(struct urb *urb)
{
int err;
int endpoint;
struct usb_wwan_port_private *portdata;
struct usb_serial_port *port;
- struct tty_struct *tty;
- unsigned char *data = urb->transfer_buffer;
int status = urb->status;
+ unsigned long flags;
dbg("%s: %p", __func__, urb);
@@ -295,38 +358,30 @@
port = urb->context;
portdata = usb_get_serial_port_data(port);
- if (status) {
- dbg("%s: nonzero status: %d on endpoint %02x.",
- __func__, status, endpoint);
- } else {
- tty = tty_port_tty_get(&port->port);
- if (tty) {
- if (urb->actual_length) {
- tty_insert_flip_string(tty, data,
- urb->actual_length);
- tty_flip_buffer_push(tty);
- } else
- dbg("%s: empty read urb received", __func__);
- tty_kref_put(tty);
- }
+ usb_mark_last_busy(port->serial->dev);
- /* Resubmit urb so we continue receiving */
- if (status != -ESHUTDOWN) {
- usb_anchor_urb(urb, &portdata->submitted);
- err = usb_submit_urb(urb, GFP_ATOMIC);
- if (err) {
- usb_unanchor_urb(urb);
- if (err != -EPERM) {
- printk(KERN_ERR "%s: resubmit read urb failed. "
- "(%d)", __func__, err);
- /* busy also in error unless we are killed */
- usb_mark_last_busy(port->serial->dev);
- }
- } else {
- usb_mark_last_busy(port->serial->dev);
- }
- }
+ if (!status && urb->actual_length) {
+ spin_lock_irqsave(&portdata->in_lock, flags);
+ list_add_tail(&urb->urb_list, &portdata->in_urb_list);
+ spin_unlock_irqrestore(&portdata->in_lock, flags);
+ schedule_work(&portdata->in_work);
+
+ return;
+ }
+
+ dbg("%s: nonzero status: %d on endpoint %02x.",
+ __func__, status, endpoint);
+
+ if (status != -ESHUTDOWN) {
+ usb_anchor_urb(urb, &portdata->submitted);
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ usb_unanchor_urb(urb);
+ if (err != -EPERM)
+ pr_err("%s: submit read urb failed:%d",
+ __func__, err);
+ }
}
}
@@ -401,6 +456,31 @@
}
EXPORT_SYMBOL(usb_wwan_chars_in_buffer);
+void usb_wwan_throttle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+
+ port->throttle_req = true;
+
+ dbg("%s:\n", __func__);
+}
+EXPORT_SYMBOL(usb_wwan_throttle);
+
+void usb_wwan_unthrottle(struct tty_struct *tty)
+{
+ struct usb_serial_port *port = tty->driver_data;
+ struct usb_wwan_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+
+ dbg("%s:\n", __func__);
+ port->throttle_req = false;
+ port->throttled = false;
+
+ schedule_work(&portdata->in_work);
+}
+EXPORT_SYMBOL(usb_wwan_unthrottle);
+
int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port)
{
struct usb_wwan_port_private *portdata;
@@ -560,6 +640,9 @@
}
init_usb_anchor(&portdata->delayed);
init_usb_anchor(&portdata->submitted);
+ INIT_WORK(&portdata->in_work, usb_wwan_in_work);
+ INIT_LIST_HEAD(&portdata->in_urb_list);
+ spin_lock_init(&portdata->in_lock);
for (j = 0; j < N_IN_URB; j++) {
buffer = kmalloc(IN_BUFLEN, GFP_KERNEL);
@@ -624,14 +707,25 @@
int i, j;
struct usb_serial_port *port;
struct usb_wwan_port_private *portdata;
-
- dbg("%s", __func__);
+ struct urb *urb;
+ struct list_head *q;
+ unsigned long flags;
/* Now free them */
for (i = 0; i < serial->num_ports; ++i) {
port = serial->port[i];
portdata = usb_get_serial_port_data(port);
+ cancel_work_sync(&portdata->in_work);
+ /* TBD: do we really need this */
+ spin_lock_irqsave(&portdata->in_lock, flags);
+ q = &portdata->in_urb_list;
+ while (!list_empty(q)) {
+ urb = list_first_entry(q, struct urb, urb_list);
+ list_del_init(&urb->urb_list);
+ }
+ spin_unlock_irqrestore(&portdata->in_lock, flags);
+
for (j = 0; j < N_IN_URB; j++) {
usb_free_urb(portdata->in_urbs[j]);
kfree(portdata->in_buffer[j]);