USB: serial: sierra driver memory reduction
authorElina Pasheva <epasheva@sierrawireless.com>
Wed, 4 Nov 2009 18:25:48 +0000 (10:25 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 11 Dec 2009 19:55:19 +0000 (11:55 -0800)
This patch deals with reducing the memory footprint for sierra driver.
This optimization is aimed for embedded software customers.

Some sierra modems can expose upwards of 7 USB interfaces, each possibly
offering different services. In general, interfaces used for the
exchange of wireless data require much higher throughput, hence require
more memory (i.e. more URBs) than lower performance interfaces. URBs
used for the IN direction are pre-allocated by the driver and this patch
introduces a way to configure the number of IN URBs allocated on a
per-interface basis. Interfaces with lower throughput requirements
receive fewer URBs, thereby reducing the RAM memory consumed by the
driver.

NOTE1: This driver has always pre-allocated URBs for the IN direction.

NOTE2: The number of URBs pre-allocated for the low-performance
interfaces has already been extensively tested in previous versions of
this driver.

We also added the capability to log function calls by adding DEBUG flag.
Please note that  this flag is commented out because this is the default
state
for it.

Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/serial/sierra.c

index 5019325..c5c41ae 100644 (file)
@@ -16,8 +16,9 @@
   Portions based on the option driver by Matthias Urlichs <smurf@smurf.noris.de>
   Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org>
 */
-
-#define DRIVER_VERSION "v.1.3.8"
+/* Uncomment to log function calls */
+/* #define DEBUG */
+#define DRIVER_VERSION "v.1.7.16"
 #define DRIVER_AUTHOR "Kevin Lloyd, Elina Pasheva, Matthew Safar, Rory Filer"
 #define DRIVER_DESC "USB Driver for Sierra Wireless USB modems"
 
 #define SWIMS_USB_REQUEST_SetPower     0x00
 #define SWIMS_USB_REQUEST_SetNmea      0x07
 
-#define N_IN_URB       8
-#define N_OUT_URB      64
+#define N_IN_URB_HM    8
+#define N_OUT_URB_HM   64
+#define N_IN_URB       4
+#define N_OUT_URB      4
 #define IN_BUFLEN      4096
 
 #define MAX_TRANSFER           (PAGE_SIZE - 512)
@@ -124,6 +127,23 @@ static int is_blacklisted(const u8 ifnum,
        return 0;
 }
 
+static int is_himemory(const u8 ifnum,
+                               const struct sierra_iface_info *himemorylist)
+{
+       const u8  *info;
+       int i;
+
+       if (himemorylist) {
+               info = himemorylist->ifaceinfo;
+
+               for (i=0; i < himemorylist->infolen; i++) {
+                       if (info[i] == ifnum)
+                               return 1;
+               }
+       }
+       return 0;
+}
+
 static int sierra_calc_interface(struct usb_serial *serial)
 {
        int interface;
@@ -186,6 +206,20 @@ static int sierra_probe(struct usb_serial *serial,
        return result;
 }
 
+/* interfaces with higher memory requirements */
+static const u8 hi_memory_typeA_ifaces[] = { 0, 2 };
+static const struct sierra_iface_info typeA_interface_list = {
+       .infolen = ARRAY_SIZE(hi_memory_typeA_ifaces),
+       .ifaceinfo = hi_memory_typeA_ifaces,
+};
+
+static const u8 hi_memory_typeB_ifaces[] = { 3, 4, 5, 6 };
+static const struct sierra_iface_info typeB_interface_list = {
+       .infolen = ARRAY_SIZE(hi_memory_typeB_ifaces),
+       .ifaceinfo = hi_memory_typeB_ifaces,
+};
+
+/* 'blacklist' of interfaces not served by this driver */
 static const u8 direct_ip_non_serial_ifaces[] = { 7, 8, 9, 10, 11 };
 static const struct sierra_iface_info direct_ip_interface_blacklist = {
        .infolen = ARRAY_SIZE(direct_ip_non_serial_ifaces),
@@ -286,8 +320,10 @@ struct sierra_port_private {
        struct usb_anchor active;
        struct usb_anchor delayed;
 
+       int num_out_urbs;
+       int num_in_urbs;
        /* Input endpoints and buffers for this port */
-       struct urb *in_urbs[N_IN_URB];
+       struct urb *in_urbs[N_IN_URB_HM];
 
        /* Settings for the port */
        int rts_state;  /* Handshaking pins (outputs) */
@@ -460,7 +496,7 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port,
        spin_lock_irqsave(&portdata->lock, flags);
        dev_dbg(&port->dev, "%s - outstanding_urbs: %d\n", __func__,
                portdata->outstanding_urbs);
-       if (portdata->outstanding_urbs > N_OUT_URB) {
+       if (portdata->outstanding_urbs > portdata->num_out_urbs) {
                spin_unlock_irqrestore(&portdata->lock, flags);
                dev_dbg(&port->dev, "%s - write limit hit\n", __func__);
                return 0;
@@ -665,7 +701,7 @@ static int sierra_write_room(struct tty_struct *tty)
        /* try to give a good number back based on if we have any free urbs at
         * this point in time */
        spin_lock_irqsave(&portdata->lock, flags);
-       if (portdata->outstanding_urbs > N_OUT_URB * 2 / 3) {
+       if (portdata->outstanding_urbs > (portdata->num_out_urbs * 2) / 3) {
                spin_unlock_irqrestore(&portdata->lock, flags);
                dev_dbg(&port->dev, "%s - write limit hit\n", __func__);
                return 0;
@@ -680,7 +716,7 @@ static void sierra_stop_rx_urbs(struct usb_serial_port *port)
        int i;
        struct sierra_port_private *portdata = usb_get_serial_port_data(port);
 
-       for (i = 0; i < ARRAY_SIZE(portdata->in_urbs); i++)
+       for (i = 0; i < portdata->num_in_urbs; i++)
                usb_kill_urb(portdata->in_urbs[i]);
 
        usb_kill_urb(port->interrupt_in_urb);
@@ -695,7 +731,7 @@ static int sierra_submit_rx_urbs(struct usb_serial_port *port, gfp_t mem_flags)
        struct sierra_port_private *portdata = usb_get_serial_port_data(port);
 
        ok_cnt = 0;
-       for (i = 0; i < ARRAY_SIZE(portdata->in_urbs); i++) {
+       for (i = 0; i < portdata->num_in_urbs; i++) {
                urb = portdata->in_urbs[i];
                if (!urb)
                        continue;
@@ -791,7 +827,7 @@ static void sierra_close(struct usb_serial_port *port)
                /* Stop reading urbs */
                sierra_stop_rx_urbs(port);
                /* .. and release them */
-               for (i = 0; i < N_IN_URB; i++) {
+               for (i = 0; i < portdata->num_in_urbs; i++) {
                        sierra_release_urb(portdata->in_urbs[i]);
                        portdata->in_urbs[i] = NULL;
                }
@@ -818,7 +854,7 @@ static int sierra_open(struct tty_struct *tty, struct usb_serial_port *port)
 
 
        endpoint = port->bulk_in_endpointAddress;
-       for (i = 0; i < ARRAY_SIZE(portdata->in_urbs); i++) {
+       for (i = 0; i < portdata->num_in_urbs; i++) {
                urb = sierra_setup_urb(serial, endpoint, USB_DIR_IN, port,
                                        IN_BUFLEN, GFP_KERNEL,
                                        sierra_indat_callback);
@@ -869,7 +905,9 @@ static int sierra_startup(struct usb_serial *serial)
 {
        struct usb_serial_port *port;
        struct sierra_port_private *portdata;
+       struct sierra_iface_info *himemoryp = NULL;
        int i;
+       u8 ifnum;
 
        dev_dbg(&serial->dev->dev, "%s\n", __func__);
 
@@ -886,13 +924,40 @@ static int sierra_startup(struct usb_serial *serial)
                portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
                if (!portdata) {
                        dev_dbg(&port->dev, "%s: kmalloc for "
-                               "sierra_port_private (%d) failed!.\n",
+                               "sierra_port_private (%d) failed!\n",
                                __func__, i);
                        return -ENOMEM;
                }
                spin_lock_init(&portdata->lock);
                init_usb_anchor(&portdata->active);
                init_usb_anchor(&portdata->delayed);
+               ifnum = i;
+               /* Assume low memory requirements */
+               portdata->num_out_urbs = N_OUT_URB;
+               portdata->num_in_urbs  = N_IN_URB;
+
+               /* Determine actual memory requirements */
+               if (serial->num_ports == 1) {
+                       /* Get interface number for composite device */
+                       ifnum = sierra_calc_interface(serial);
+                       himemoryp =
+                           (struct sierra_iface_info *)&typeB_interface_list;
+                       if (is_himemory(ifnum, himemoryp)) {
+                               portdata->num_out_urbs = N_OUT_URB_HM;
+                               portdata->num_in_urbs  = N_IN_URB_HM;
+                       }
+               }
+               else {
+                       himemoryp =
+                           (struct sierra_iface_info *)&typeA_interface_list;
+                       if (is_himemory(i, himemoryp)) {
+                               portdata->num_out_urbs = N_OUT_URB_HM;
+                               portdata->num_in_urbs  = N_IN_URB_HM;
+                       }
+               }
+               dev_dbg(&serial->dev->dev,
+                       "Memory usage (urbs) interface #%d, in=%d, out=%d\n",
+                       ifnum,portdata->num_in_urbs, portdata->num_out_urbs );
                /* Set the port private data pointer */
                usb_set_serial_port_data(port, portdata);
        }