Greg --

Several IO Networks EdgePort users have requested the ability
to explicitly assign serial ports to USB serial devices, regardless
of the order the devices are plugged in.

For example, in a retail application with both an EdgePort 8 and an
EdgePort 4/s they want to be sure the USB serial port numbers and
device names are always assigned the same way, even if the devices
are unplugged and then plugged back in or if the system gets rebooted.

This is particularly a problem with devices like the EdgePort 4/s
which appears as two two port devices behind a hub.  Apparantly
under certain circumstances the two devices get enumerated in
different orders, so sometimes the first two ports get switched
with the last two ports.

The solution Peter Berger, David Iacovelli, and I came up with
allows each USB serial port to be assigned to a USB device by
serial number, vendor/product id, or device path.  We used a
module parameter to usbserial, an array of strings, with one
string for each USB serial port that specifies the device that
should get that port.  The first character of the string specifies
the type of the pattern and there are limited wild cards.

A patch for 2.4.26 and a brief document describing the feature
is attached.

This is a simple solution that meets all the needs expressed so
far, but I am not completely happy with it, for several reasons:

* The wildcard matching for serial numbers is limited and flawed--
  but it is very simple and useful for cases like the EdgePort 4/s.
  An escape for the wildcard would fix one flaw, but there are
  no kernel routines I know about for better pattern matching.

* The port assignment is fixed when usbserial is loaded.  A /proc
  interface would fix this.

* More importantly, it seems this should be handled by a hotplug
  script somehow.

  The problem with using hotplug is first that the hotplug script
  is called after the USB serial ports have been assigned.  So we
  would need to add a new call out to the hotplugging scripts with
  a new action, "assign_ports" or something like that.  Then we would
  still need a very similar mechanism in usbserial, with a /proc
  interface, to allow the hotplug script to tell usbserial how the
  USB serial ports should be assigned.  This would get complicated.

If you or others think these sort of changes should be made, let
me know and I will do them.

Thanks for any comments,
-- Al
--- usbserial.c.orig    Mon May 10 17:32:30 2004
+++ usbserial.c Mon May 10 23:17:28 2004
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1999 - 2002 Greg Kroah-Hartman ([EMAIL PROTECTED])
  * Copyright (C) 2000 Peter Berger ([EMAIL PROTECTED])
- * Copyright (C) 2000 Al Borchers ([EMAIL PROTECTED])
+ * Copyright (C) 2000 Al Borchers ([EMAIL PROTECTED])
  *
  *     This program is free software; you can redistribute it and/or
  *     modify it under the terms of the GNU General Public License version
@@ -14,6 +14,10 @@
  *
  * See Documentation/usb/usb-serial.txt for more information on using this driver
  *
+ * 1.5 (5/10/2004) [EMAIL PROTECTED]
+ *  Added the ability to reserve serial ports by serial number,
+ *  by vendor/product ids, or by device path.
+ *
  * (10/10/2001) gkh
  *     usb_serial_disconnect() now sets the serial->dev pointer is to NULL to
  *     help prevent child drivers from accessing the device since it is now
@@ -298,6 +302,7 @@
 #include <linux/list.h>
 #include <linux/smp_lock.h>
 #include <linux/usb.h>
+#include <linux/ctype.h>
 
 #ifdef CONFIG_USB_SERIAL_DEBUG
        static int debug = 1;
@@ -311,7 +316,7 @@
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v1.4"
+#define DRIVER_VERSION "v1.5"
 #define DRIVER_AUTHOR "Greg Kroah-Hartman, [EMAIL PROTECTED], 
http://www.kroah.com/linux-usb/";
 #define DRIVER_DESC "USB Serial Driver core"
 
@@ -385,6 +390,7 @@
 static struct termios *                serial_termios[SERIAL_TTY_MINORS];
 static struct termios *                serial_termios_locked[SERIAL_TTY_MINORS];
 static struct usb_serial       *serial_table[SERIAL_TTY_MINORS];       /* initially 
all NULL */
+static char                    *reserve_ports[SERIAL_TTY_MINORS];      /* initially 
all NULL */
 
 
 static LIST_HEAD(usb_serial_driver_list);
@@ -395,8 +401,107 @@
        return serial_table[minor];
 }
 
+static int match_serial_number(char *rsv, struct usb_device *dev)
+{
+       int len_sn;
+       int len_rsv = strlen(rsv);
+       char sn[256];
+
+       if (dev->descriptor.iSerialNumber == 0)
+               return 0;
+
+       usb_string(dev, dev->descriptor.iSerialNumber, sn, 256);
+       len_sn = strlen(sn);
+
+       if (*rsv == '*')
+               return (strncmp(sn+len_sn-len_rsv+1, rsv+1, len_rsv-1) == 0);
+       else if (len_rsv > 0 && *(rsv+len_rsv-1) == '*')
+               return (strncmp(sn, rsv, len_rsv-1) == 0);
+       else
+               return (strcmp(sn, rsv) == 0);
+}
+
+static int match_vendor_product(char *rsv, struct usb_device *dev)
+{
+       char *p = rsv + 1;
+
+       if (*rsv == '\0'
+       || (*rsv != '*' && simple_strtol(rsv,&p,16) != dev->descriptor.idVendor))
+               return 0;
+
+       if (*p != ':' && *p != '/')
+               return 0;
+       ++p;
+
+       if (*p == '\0'
+       || (*p != '*' && simple_strtol(p,NULL,16) != dev->descriptor.idProduct))
+               return 0;
+
+       return 1;
+}
 
-static struct usb_serial *get_free_serial (int num_ports, int *minor)
+static int match_device_path(char *rsv, struct usb_device *dev)
+{
+       int port;
+       char *port_str = rsv + strlen(rsv);
+       struct usb_device *pdev, *cdev;
+
+       cdev = dev;
+       pdev = dev->parent;
+       while (pdev) {
+
+               if (port_str <= rsv)
+                       return 0;
+
+               for (port=0; port < pdev->maxchild; port++)
+                       if (pdev->children[port] == cdev)
+                               break;
+               ++port;         /* change 0 based port numbers to 1 based */
+
+               while (port_str>rsv && *(port_str-1) != '/')
+                       --port_str;
+
+               if (*port_str != '*' && simple_strtol(port_str,NULL,10) != port)
+                       return 0;
+
+               --port_str;
+
+               cdev = pdev;
+               pdev = pdev->parent;
+       }
+
+       while (port_str>rsv && *(port_str-1) != '/')
+               --port_str;
+
+       if (*port_str != '*' && simple_strtol(port_str,NULL,10) != dev->bus->busnum)
+               return 0;
+
+       if (port_str == rsv || (port_str == rsv+1 && *rsv == '/'))
+               return 1;
+
+       return 0;
+}
+
+/* return 1 if the device matches the reserve port entry, 0 otherwise */
+/* a NULL reserve port entry always matches */
+static int match(char *rsv, struct usb_device *dev, int first)
+{
+       if (rsv == NULL)
+               return 1;       /* unspecified, always matches */
+
+       switch (toupper(*rsv)) {
+               case 'S': return match_serial_number(rsv+1,dev);
+               case 'V': return match_vendor_product(rsv+1,dev);
+               case 'D': return match_device_path(rsv+1,dev);
+               case '^': return !first;
+               case '-': return 0;
+               case '*': return 1;
+               default: return 0;
+       }
+}
+
+static struct usb_serial *get_free_serial (int num_ports, int *minor,
+       struct usb_device *dev)
 {
        struct usb_serial *serial = NULL;
        int i, j;
@@ -406,12 +511,12 @@
 
        *minor = 0;
        for (i = 0; i < SERIAL_TTY_MINORS; ++i) {
-               if (serial_table[i])
+               if (serial_table[i] || !match(reserve_ports[i],dev,1))
                        continue;
 
                good_spot = 1;
                for (j = 1; j <= num_ports-1; ++j)
-                       if (serial_table[i+j])
+                       if (serial_table[i+j] || !match(reserve_ports[i+j],dev,0))
                                good_spot = 0;
                if (good_spot == 0)
                        continue;
@@ -1234,7 +1339,7 @@
 #endif
                num_ports = type->num_ports;
 
-       serial = get_free_serial (num_ports, &minor);
+       serial = get_free_serial (num_ports, &minor, dev);
        if (serial == NULL) {
                err("No more free serial devices");
                return NULL;
@@ -1605,6 +1710,11 @@
 MODULE_PARM(debug, "i");
 MODULE_PARM_DESC(debug, "Debug enabled or not");
 
+#define usb_serial_cat_str_num_str(a,b,c) _usb_serial_cat_str_num_str(a,b,c)
+#define _usb_serial_cat_str_num_str(a,b,c) a #b c
+MODULE_PARM(reserve_ports, usb_serial_cat_str_num_str("1-",SERIAL_TTY_MINORS,"s"));
+MODULE_PARM_DESC(reserve_ports, usb_serial_cat_str_num_str("Reserve tty devices for 
these serial numbers, vendor/product ids, or device paths, 1-",SERIAL_TTY_MINORS," 
strings"));
+
 #ifdef CONFIG_USB_SERIAL_GENERIC
 MODULE_PARM(vendor, "h");
 MODULE_PARM_DESC(vendor, "User specified USB idVendor");
USB Serial Port Assignment

Overview

The usbserial.c driver has been enhanced with a way to
explicitly assign serial ports to USB serial devices.

Before this enhancement, the ports were assigned based
on the order the USB serial devices were enumerated,
but this changed depending on when the devices were
plugged in, or how the system was booted.  Devices
like the EdgePort 4/s were particularly troublesome,
since they are really two two port devices and these
two devices were enumerated in different orders, randomly
changing the port assignments of the four ports on the
device.

The usbserial module now has a module parameter named
"reserve_ports".  This is an array of strings, one for
each USB serial port, beginning at /dev/ttyUSB0 and
continuing on up.  The string is a pattern that
reserves the port for a particular device by serial
number, by vendor/product id, or by device path.


Syntax

Initially the strings NULL for all ports, and these
NULL strings leave the port available for any device.

The first character of the string determines the type
of the pattern:

  S -- serial number
  V -- vendor/product id
  D -- device path
  ^ -- this port is available for a multiport device
       that matched the previous pattern
  - -- this port is not available for any device
  * -- this port is available for any device

The rest of the string is interpreted according to
the first character:

Serial Number
  The serial number is an ASCII string that is matched
  against the serial number reported by the device.  An
  "*" can be used at the beginning or end of this string
  as a wildcard to match the beginning or ending of any
  serial number.

Vendor/Product ID
  The vendor/product ids are two hex numbers separated by
  a ":" or "/".  Either number can be replaced by a "*"
  as a wildcard to match any vendor or product id.  The
  vendor and product ids should not have "0x" at the
  beginning.

Device Path
  The device path is a "/" separated path of decimal integers.
  The first integer is the bus number, the rest are the USB
  port numbers of sequence of hubs leading to the device.  Any
  integer in the path can be replaced by a "*" as a wildcard
  to match any port.

  For example, a device path like "D1/2/3" means the device
  attached to the 3rd port on the hub attached to the 2nd
  port on the virtual hub on bus 1.  When a device is attached
  the usb subsystem prints an informational message through
  syslog that gives the device path--be sure syslog is logging
  informational messages and look for the "new device connect"
  message to see the path.


Specifying the Module Parameters

The reserve_ports module parameter can be given on the insmod
or modprobe command line, or it can be given in the
/etc/modules.conf file, like this

  add options usbserial 'reserve_ports=V1608:242,S*-0,V1608:242,S*-1'


Examples

The three examples that follow all assign the first four USB serial
ports, /dev/ttyUSB0 through /dev/ttyUSB3, to an EdgePort 4/s.

  add options usbserial 'reserve_ports=SA32399053-0,^,SA32399053-1,^'

This example specifies the serial number of the device.  The
EdgePort 4/s looks like two different devices, one with a serial
number ending in "-0" for the first two ports and another with
serial number ending in "-1" for the second two serial ports.

  add options usbserial 'reserve_ports=D1/2/*/2,D1/2/*/2,D1/2/*/1,D1/2/*/1'

This example specifies the device path for an EdgePort 4s attached
to any port of a hub attached to the computer's 2nd USB port.  Since
the EdgePort 4/s looks like two devices behind a hub, the path to
the EdgePort 4/s shows one extra port; USB port 2 is used for the
first two ports, and USB port 1 is used for the second two ports.

  add options usbserial 'reserve_ports=V1608:242,S*-0,V1608:242,S*-1'

This example specifies the vendor and product id and the trailing
part of the serial number.  Since EdgePort 4/s looks like two
two port devices, the device with the first two ports must match 
two entries in the reserve_ports array; in this example that means
the the vendor id must be 0x1608, the product id must be 0x0242,
and the serial number must end in "-0".

Reply via email to