summaryrefslogtreecommitdiffstats
path: root/drivers/usb/serial/generic.c
diff options
context:
space:
mode:
authorJohan Hovold <jhovold@gmail.com>2010-05-05 23:57:37 +0200
committerGreg Kroah-Hartman <gregkh@suse.de>2010-05-20 13:21:42 -0700
commit27c7acf22047fbe4ec4cc36b7c2610dba227697c (patch)
treec0a8f217fc2d7a302b4d2e084bb126e825006ca4 /drivers/usb/serial/generic.c
parent4272568b3dd8dbad36014a107c0fbbef6400c917 (diff)
downloadop-kernel-dev-27c7acf22047fbe4ec4cc36b7c2610dba227697c.zip
op-kernel-dev-27c7acf22047fbe4ec4cc36b7c2610dba227697c.tar.gz
USB: serial: reimplement generic fifo-based writes
Reimplement fifo-based writes in the generic driver using a multiple pre-allocated urb scheme. In contrast to multi-urb writes, no allocations (of urbs or buffers) are made during run-time and there is less pressure on the host stack queues as currently only two urbs are used (implementation is generic and can handle more than two urbs as well, though). Initial tests using ftdi_sio show that the implementation achieves the same (maximum) throughput at high baudrates as multi-urb writes. The CPU usage is much lower than for multi-urb writes for small write requests and only slightly higher for large (e.g. 2k) requests (due to extra copy via fifo?). Also outperforms multi-urb writes for small write requests on an embedded arm-9 system, where multi-urb writes are CPU-bound at high baudrates (perf reveals that a lot of time is spent in the host stack enqueue function -- could perhaps be a bug as well). Keeping the original write_urb, buffer and flag for now as there are other drivers depending on them. Signed-off-by: Johan Hovold <jhovold@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/serial/generic.c')
-rw-r--r--drivers/usb/serial/generic.c59
1 files changed, 39 insertions, 20 deletions
diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c
index 1a134f9..3ae1784 100644
--- a/drivers/usb/serial/generic.c
+++ b/drivers/usb/serial/generic.c
@@ -1,6 +1,7 @@
/*
* USB Serial Converter Generic functions
*
+ * Copyright (C) 2010 Johan Hovold (jhovold@gmail.com)
* Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
*
* This program is free software; you can redistribute it and/or
@@ -143,6 +144,7 @@ static void generic_cleanup(struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
unsigned long flags;
+ int i;
dbg("%s - port %d", __func__, port->number);
@@ -150,6 +152,8 @@ static void generic_cleanup(struct usb_serial_port *port)
/* shutdown any bulk transfers that might be going on */
if (port->bulk_out_size) {
usb_kill_urb(port->write_urb);
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i)
+ usb_kill_urb(port->write_urbs[i]);
spin_lock_irqsave(&port->lock, flags);
kfifo_reset_out(&port->write_fifo);
@@ -258,46 +262,56 @@ err_urb:
* usb_serial_generic_write_start - kick off an URB write
* @port: Pointer to the &struct usb_serial_port data
*
- * Returns the number of bytes queued on success. This will be zero if there
- * was nothing to send. Otherwise, it returns a negative errno value
+ * Returns zero on success, or a negative errno value
*/
static int usb_serial_generic_write_start(struct usb_serial_port *port)
{
- int result;
- int count;
+ struct urb *urb;
+ int count, result;
unsigned long flags;
+ int i;
+ if (test_and_set_bit_lock(USB_SERIAL_WRITE_BUSY, &port->flags))
+ return 0;
+retry:
spin_lock_irqsave(&port->lock, flags);
- if (port->write_urb_busy || !kfifo_len(&port->write_fifo)) {
+ if (!port->write_urbs_free || !kfifo_len(&port->write_fifo)) {
+ clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
spin_unlock_irqrestore(&port->lock, flags);
return 0;
}
- port->write_urb_busy = 1;
+ i = (int)find_first_bit(&port->write_urbs_free,
+ ARRAY_SIZE(port->write_urbs));
spin_unlock_irqrestore(&port->lock, flags);
+ urb = port->write_urbs[i];
count = port->serial->type->prepare_write_buffer(port,
- &port->write_urb->transfer_buffer,
- port->bulk_out_size, NULL, 0);
- usb_serial_debug_data(debug, &port->dev, __func__,
- count, port->write_urb->transfer_buffer);
- port->write_urb->transfer_buffer_length = count;
-
- /* send the data out the bulk port */
- result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+ &urb->transfer_buffer,
+ port->bulk_out_size, NULL, 0);
+ urb->transfer_buffer_length = count;
+ usb_serial_debug_data(debug, &port->dev, __func__, count,
+ urb->transfer_buffer);
+ result = usb_submit_urb(urb, GFP_ATOMIC);
if (result) {
dev_err(&port->dev, "%s - error submitting urb: %d\n",
__func__, result);
- /* don't have to grab the lock here, as we will
- retry if != 0 */
- port->write_urb_busy = 0;
+ clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
return result;
}
+ clear_bit(i, &port->write_urbs_free);
spin_lock_irqsave(&port->lock, flags);
port->tx_bytes += count;
spin_unlock_irqrestore(&port->lock, flags);
- return count;
+ /* Try sending off another urb, unless in irq context (in which case
+ * there will be no free urb). */
+ if (!in_irq())
+ goto retry;
+
+ clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
+
+ return 0;
}
/**
@@ -461,6 +475,7 @@ void usb_serial_generic_write_bulk_callback(struct urb *urb)
unsigned long flags;
struct usb_serial_port *port = urb->context;
int status = urb->status;
+ int i;
dbg("%s - port %d", __func__, port->number);
@@ -472,9 +487,13 @@ void usb_serial_generic_write_bulk_callback(struct urb *urb)
port->tx_urbs--;
spin_unlock_irqrestore(&port->lock, flags);
} else {
+ for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i)
+ if (port->write_urbs[i] == urb)
+ break;
+
spin_lock_irqsave(&port->lock, flags);
port->tx_bytes -= urb->transfer_buffer_length;
- port->write_urb_busy = 0;
+ set_bit(i, &port->write_urbs_free);
spin_unlock_irqrestore(&port->lock, flags);
if (status) {
@@ -576,7 +595,7 @@ int usb_serial_generic_resume(struct usb_serial *serial)
c++;
}
- if (port->write_urb) {
+ if (port->bulk_out_size) {
r = usb_serial_generic_write_start(port);
if (r < 0)
c++;
OpenPOWER on IntegriCloud