What's its name?
One nice feature of OpenVMS is its logical name capabilities. Using logical names, system entities such as devices, directories, files, queues, etc., can be easily accessed and referred to using meaningful names. For example, I used to have a LAT terminal which was connected to a modem. It was created and defined at system startup with a system-wide logical name of $MODEM. Then, when I needed to use the modem, I would simply type:$ SET HOST/DTE $MODEM
. I didn't need to recall its terminal designation. When I setup my Ubuntu laptop to use my EVDO card (a modem) and other removable devices, I sought a similar mechanism and found it in udev.
udev is a Linux feature for dynamic device management. It dynamically provides a device designation in the device directory when a device is actually present. udev runs as a daemon (udevd) and it fields events from the kernel when a device is added to or removed from the system. It creates and or removes the device's node files in the /dev directory. When udev receives a device event, it searches through a set of configuration rules using available device attributes which uniquely identify the device. Matching rules can provide additional information about the device, specify the device's node name, create symlinks and even launch a program as part of its handling of the device addition or removal event. udev can also be used to rename network interfaces and, as I'll show herein, other devices as well.
During bootstrap or when a device is connected or removed, the udev daemon looks in directory
/etc/udev/rules.d
for configuration files which define the devices and actions which udev should take if and or when the device is present on the particular system. In typical Linux fashion, the files in this directory are named using numeric prefixes. This insures that they collate in the proper order so that the configuration rules are applied in the proper order. Let's take a look:Contents of /etc/udev/rules.d
-rw-r--r-- 1 root root 1547 2008-10-13 09:03 024_hpmud.rules
-rw-r--r-- 1 root root 400 2008-10-24 06:37 05-options.rules
-rw-r--r-- 1 root root 3215 2008-10-24 06:37 20-names.rules
-rw-r--r-- 1 root root 141 2008-10-24 06:37 30-cdrom_id.rules
-rw-r--r-- 1 root root 814 2008-10-24 06:37 40-basic-permissions.rules
-rw-r--r-- 1 root root 3109 2008-10-24 06:37 40-permissions.rules
-rw-r--r-- 1 root root 30 2008-09-25 12:06 45-fuse.rules
-rw-r--r-- 1 root root 115 2008-10-13 09:03 45-hplip.rules
-rw-r--r-- 1 root root 39784 2008-08-01 11:53 45-libmtp8.rules
-rw-r--r-- 1 root root 1704 2008-06-19 05:53 45-libnjb.rules
-rw-r--r-- 1 root root 2656 2008-10-07 19:22 50-libpisock9.rules
-rw-r--r-- 1 root root 6661 2008-10-21 23:27 50-xserver-xorg-input-wacom.rules
-rw-r--r-- 1 root root 115 2008-10-13 09:03 55-hpmud.rules
-rw-r--r-- 1 root root 1431 2008-10-24 06:37 60-persistent-input.rules
-rw-r--r-- 1 root root 3851 2008-11-03 13:12 60-persistent-storage.rules
-rw-r--r-- 1 root root 1322 2008-10-24 06:37 60-persistent-storage-tape.rules
-rw-r--r-- 1 root root 1309 2009-08-06 14:56 60-symlinks.rules
-rw-r--r-- 1 root root 518 2008-10-24 06:37 61-persistent-storage-edd.rules
-rw-r--r-- 1 root root 485 2008-10-26 20:27 62-bluez-hid2hci.rules
-rw-r--r-- 1 root root 1262 2008-10-20 17:14 65-dmsetup.rules
-rw-r--r-- 1 root root 196 2008-10-24 06:37 65-id-type.rules
-rw-r--r-- 1 root root 1642 2009-02-01 15:17 70-persistent-cd.rules
-rw-r--r-- 1 root root 730 2009-01-10 16:48 70-persistent-net.rules
-rw-r--r-- 1 root root 390 2008-10-24 06:37 75-cd-aliases-generator.rules
-rw-r--r-- 1 root root 2403 2008-10-24 06:37 75-persistent-net-generator.rules
-rw-r--r-- 1 root root 563 2008-10-24 06:37 80-programs.rules
-rw-r--r-- 1 root root 171 2008-09-25 02:11 85-alsa.rules
-rw-r--r-- 1 root root 1444 2008-08-06 05:09 85-brltty.rules
-rw-r--r-- 1 root root 84 2008-06-19 04:46 85-hdparm.rules
-rw-r--r-- 1 root root 1855 2008-09-09 04:51 85-hplj10xx.rules
-rw-r--r-- 1 root root 126 2008-09-25 09:07 85-hwclock.rules
-rw-r--r-- 1 root root 954 2008-10-12 18:18 85-ifupdown.rules
-rw-r--r-- 1 root root 950 2007-10-23 13:03 85-pcmcia.rules
-rw-r--r-- 1 root root 82 2008-10-26 04:56 90-hal.rules
-rw-r--r-- 1 root root 2862 2008-10-24 06:37 90-modprobe.rules
-rw-r--r-- 1 root root 234 2008-10-24 06:37 95-udev-late.rules
As you can see, the files collate based on their numerical prefix and the remainder of the filename should detail the function of the udev rules contained within. For the remainder of this discussion, I will focus on the file
60-symlinks.rules
and the rules within.Let's have a look:
Contents of /etc/udev/rules.d/60-symlinks.rules
# This file establishes user-friendly symlinks to devices according to
# Ubuntu policy. See udev(7) for syntax.
#
# The names of the actual devices themselves must not be set here, but
# in 20-names.rules; the permissions and ownership of those devices
# should be set in 40-permissions.rules.
# Compatibility symlinks for /dev/scd* devices
SUBSYSTEMS=="scsi", KERNEL=="sr[0-9]*", SYMLINK+="%k"
# Compatibility symlinks for USB printers
SUBSYSTEMS=="usb", KERNEL=="lp[0-9]*", SYMLINK+="usb%k"
# Compatibility symlink for the CMOS RTC
SUBSYSTEM=="rtc", DRIVERS=="rtc_cmos", SYMLINK+="rtc"
# Create /dev/pilot symlink for Palm Pilots
KERNEL=="ttyUSB*", ATTRS{product}=="Palm Handheld*|Handspring *|palmOne Handheld", SYMLINK+="pilot"
# Reverse mapping for /sys/dev
SUBSYSTEM=="block", SYMLINK+="block/%M:%m"
SUBSYSTEM!="block", SYMLINK+="char/%M:%m"
The above is a listing of the default
60-symlinks.rules
that shipped with Ubuntu 8.10.I don't own a Palm Pilot but it's nice to know that if I did, it would configure as
/dev/pilot
if and or when I'd plug one in. As you can see, this rule looks for information that udev fields from a device event. If the device information matches /dev/ttyUSB*
and the device's product name attributes match one of the three strings, a symbolic link /dev/pilot
will be created. Like I said, I don't own a Palm Pilot; however, I do own several USB devices and I would like to have them configure themselves on my Ubuntu Linux laptop with convenient and recognizable names when they are used.The three devices I will take a look at in this discussion are:
- KeySpan USB<=>Serial Adapter device
- Sprint/Curitel PC5740 EVDO PCMCIA/CardBus device
- Sprint/Sierra Wireless AC597E EVDO ExpressCard device
In order to configure these devices, which all instantiate a
/dev/ttyUSB#
designation when inserted, some other unique attribute of the device is needed. To find this, the following commands can be used to output the device's attributes:
$ udevinfo -a -p $(udevinfo -q path -n /dev/device-designation)
$ udevadm info -a -p $(udevadm info -q path -n /dev/device-designation)
The
udevinfo
command is being replaced by the udevadm
and the info
parameter in newer releases of Ubuntu Linux.Before delving any deeper, let's look at why defining unique names for devices can be so beneficial and helpful. I mentioned earlier about the devices which I have and that I want to configured using udev. You might as why I just can't live with the designations that are created when the device is plugged in. Let's take a look.
If I plug in the USB<=>serial adapter, the system instantiates the following:
$ ls -aFl /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 2009-08-13 14:11 /dev/ttyUSB0
Now, let's remove that device and plug in the Sprint Sierra Wireless card. Looking again, we find:
$ ls -aFl /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 2009-08-13 14:12 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 2009-08-13 14:12 /dev/ttyUSB1
crw-rw---- 1 root dialout 188, 2 2009-08-13 14:12 /dev/ttyUSB2
Yes, one device instantiates three separate ttys. Now, let's see what happens when I also plug in the USB<=>serial adapter.
$ ls -aFl /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 2009-08-13 14:12 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 2009-08-13 14:12 /dev/ttyUSB1
crw-rw---- 1 root dialout 188, 2 2009-08-13 14:12 /dev/ttyUSB2
crw-rw---- 1 root dialout 188, 3 2009-08-13 14:13 /dev/ttyUSB3
The USB<=>serial adapter is now
/dev/ttyUSB3
whereas earlier, without the wireless card inserted, it was /dev/ttyUSB0
. Furthermore, if the devices are plugged in in the opposite fashion — the wireless card and then the USB<=>serial adapter — there is a completely different set of designations for the wireless card.
$ ls -aFl /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 2009-08-13 14:22 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 2009-08-13 14:23 /dev/ttyUSB1
crw-rw---- 1 root dialout 188, 2 2009-08-13 14:23 /dev/ttyUSB2
crw-rw---- 1 root dialout 188, 3 2009-08-13 14:23 /dev/ttyUSB3
This renaming of the devices based upon which device is first installed can be problematic for scripts that are used to automate functions on the system. Thus, the features of udev renaming and symbolic linking can be employed to take the tedium and guesswork out of specifying a particular device. A unique name, regardless of where or when the device is connected, can be created and used for all future reference to the device.
Hopefully, the aforementioned examples illustrate the need for more information about the device, other than its designation in the
/dev
directory, for matching up device events for renaming the device with udev. So, let's explore a little further the attributes of a device. The easiest will be explored first &mdash, the USB<=>serial — because it presents itself as only one device when it is connected.Using either
udevinfo
or the udevadm info
command, insert the device and get the device's attributes.udevinfo -a -p $(udevinfo -q path -n /dev/ttyUSB0)
Udevinfo starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1:1.0/ttyUSB0/tty/ttyUSB0':
KERNEL=="ttyUSB0"
SUBSYSTEM=="tty"
DRIVER==""
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1:1.0/ttyUSB0/tty':
KERNELS=="tty"
SUBSYSTEMS==""
DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1:1.0/ttyUSB0':
KERNELS=="ttyUSB0"
SUBSYSTEMS=="usb-serial"
DRIVERS=="keyspan_1"
ATTRS{port_number}=="0"
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1:1.0':
KERNELS=="4-1:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="keyspan"
ATTRS{bInterfaceNumber}=="00"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bNumEndpoints}=="05"
ATTRS{bInterfaceClass}=="ff"
ATTRS{bInterfaceSubClass}=="00"
ATTRS{bInterfaceProtocol}=="00"
ATTRS{modalias}=="usb:v06CDp0121d0100dcFFdscFFdpFFicFFisc00ip00"
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1':
KERNELS=="4-1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="a0"
ATTRS{bMaxPower}=="100mA"
ATTRS{urbnum}=="12"
ATTRS{idVendor}=="06cd"
ATTRS{idProduct}=="0121"
ATTRS{bcdDevice}=="0100"
ATTRS{bDeviceClass}=="ff"
ATTRS{bDeviceSubClass}=="ff"
ATTRS{bDeviceProtocol}=="ff"
ATTRS{bNumConfigurations}=="2"
ATTRS{bMaxPacketSize0}=="8"
ATTRS{speed}=="12"
ATTRS{busnum}=="4"
ATTRS{devnum}=="6"
ATTRS{version}==" 1.10"
ATTRS{maxchild}=="0"
ATTRS{quirks}=="0x0"
ATTRS{authorized}=="1"
ATTRS{manufacturer}=="Keyspan, a division of InnoSys Inc."
ATTRS{product}=="Keyspan USA-19H "
looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4':
KERNELS=="usb4"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="e0"
ATTRS{bMaxPower}==" 0mA"
ATTRS{urbnum}=="124"
ATTRS{idVendor}=="1d6b"
ATTRS{idProduct}=="0001"
ATTRS{bcdDevice}=="0206"
ATTRS{bDeviceClass}=="09"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bNumConfigurations}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{speed}=="12"
ATTRS{busnum}=="4"
ATTRS{devnum}=="1"
ATTRS{version}==" 1.10"
ATTRS{maxchild}=="2"
ATTRS{quirks}=="0x0"
ATTRS{authorized}=="1"
ATTRS{manufacturer}=="Linux 2.6.27-14-server uhci_hcd"
ATTRS{product}=="UHCI Host Controller"
ATTRS{serial}=="0000:00:1d.0"
ATTRS{authorized_default}=="1"
looking at parent device '/devices/pci0000:00/0000:00:1d.0':
KERNELS=="0000:00:1d.0"
SUBSYSTEMS=="pci"
DRIVERS=="uhci_hcd"
ATTRS{vendor}=="0x8086"
ATTRS{device}=="0x2830"
ATTRS{subsystem_vendor}=="0x1179"
ATTRS{subsystem_device}=="0xff64"
ATTRS{class}=="0x0c0300"
ATTRS{irq}=="23"
ATTRS{local_cpus}=="ffffffff,ffffffff"
ATTRS{local_cpulist}=="0-63"
ATTRS{modalias}=="pci:v00008086d00002830sv00001179sd0000FF64bc0Csc03i00"
ATTRS{broken_parity_status}=="0"
ATTRS{msi_bus}==""
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""
I chose the items depicted in red in the above as the attributes I want to match in the udev rules to create a symbolic link for the device when it is inserted. Of course, the ttyUSB designation will be the same for all of the devices I previously mentioned but it can be used to focus the match. What is needed now is some unique attribute of the device. In this example, I chose the product name. Using this information, I can parallel the rule for the Palm Pilot that existed in the
60-symlinks.rules
file.
# Create /dev/KeySpan symlink for KeySpan USB -> serial device
KERNEL=="ttyUSB*", ATTRS{product}=="Keyspan*", SYMLINK+="KeySpan"
Now, when I plug in the USB<=>serial adapter, udev will see the event, execute the various rules files, find the symlink rule and create
/dev/KeySpan
. Regardless of the order of device insertion, the USB<=>serial adapter will always be known as /dev/KeySpan
.Earlier, I showed the results of inserting the Sierra Wireless card. Three devices were created. This particular device has three separate functions. One is, of course, as a modem for connection to Sprint for EVDO internet. Another device is used for obtaining power level information from the device. If there is appropriate software for the device, it can report the wireless signal level. Yet another provides the GPS functionality in this device. I have no real interest in the GPS features and I can live without knowing the signal level at any given time; however, I do want to be able to connect to the modem and get on the internet.
I experimented using
"cu -l /dev/ttyUSB#
with each of the devices to determine which of the three was the modem. I hit it straight away.
$ cu -l /dev/ttyUSB0
Connected.
ATI
Manufacturer: Sierra Wireless, Inc.
Model: AC597E Rev 1.0 (1)
Revision: p2102900,60608 [Apr 19 2007 10:39:04]
APPL: SWI6800V2_FP.00.29 2007/04/19 16:55:48
BOOT: SWI6800V2_FP.00.29 2007/04/19 16:55:48
QCOM: SWI6800V2_FP.00.29 2007/04/19 16:55:48
SWID: SWI6800V2_FP.00.29 2007/04/19 16:55:48 [GENERIC_00]
USBD: SWI6800V2_FP.00.29 2007/04/19 16:55:48 [GENERIC_00]
USB VID: 0x1199 PID: 0x0021
ESN: 0x87654321
+GCAP: +CIS707-A, CIS-856, CIS-856-A, +MS, +ES, +DS, +FCLASS
OK
The first device is not always the modem. In fact, the other device I mentioned, also an EVDO wireless modem, the Curitel PC5740 has its modem function on the second device designated
/dev/ttyUSB1
.Now that I know which device is the modem, I need to take a look at the attributes of the device as udev sees them.
udevinfo -a -p $(udevinfo -q path -n /dev/ttyUSB0)
Udevinfo starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/pci0000:00/0000:00:1d.2/usb6/6-1/6-1:1.0/ttyUSB0/tty/ttyUSB0':
KERNEL=="ttyUSB0"
SUBSYSTEM=="tty"
DRIVER==""
looking at parent device '/devices/pci0000:00/0000:00:1d.2/usb6/6-1/6-1:1.0/ttyUSB0/tty':
KERNELS=="tty"
SUBSYSTEMS==""
DRIVERS==""
looking at parent device '/devices/pci0000:00/0000:00:1d.2/usb6/6-1/6-1:1.0/ttyUSB0':
KERNELS=="ttyUSB0"
SUBSYSTEMS=="usb-serial"
DRIVERS=="sierra"
ATTRS{port_number}=="0"
looking at parent device '/devices/pci0000:00/0000:00:1d.2/usb6/6-1/6-1:1.0':
KERNELS=="6-1:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="sierra"
ATTRS{bInterfaceNumber}=="00"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bNumEndpoints}=="07"
ATTRS{bInterfaceClass}=="ff"
ATTRS{bInterfaceSubClass}=="ff"
ATTRS{bInterfaceProtocol}=="ff"
ATTRS{modalias}=="usb:v1199p0021d0002dc00dsc00dp00icFFiscFFipFF"
ATTRS{interface}=="Data Interface"
looking at parent device '/devices/pci0000:00/0000:00:1d.2/usb6/6-1':
KERNELS=="6-1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="e0"
ATTRS{bMaxPower}==" 0mA"
ATTRS{urbnum}=="66"
ATTRS{idVendor}=="1199"
ATTRS{idProduct}=="0021"
ATTRS{bcdDevice}=="0002"
ATTRS{bDeviceClass}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bNumConfigurations}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{speed}=="12"
ATTRS{busnum}=="6"
ATTRS{devnum}=="2"
ATTRS{version}==" 1.10"
ATTRS{maxchild}=="0"
ATTRS{quirks}=="0x0"
ATTRS{authorized}=="1"
ATTRS{manufacturer}=="Sierra Wireless, Incorporated"
ATTRS{product}=="Sierra Wireless AirCard 597E Modem"
looking at parent device '/devices/pci0000:00/0000:00:1d.2/usb6':
KERNELS=="usb6"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="e0"
ATTRS{bMaxPower}==" 0mA"
ATTRS{urbnum}=="30"
ATTRS{idVendor}=="1d6b"
ATTRS{idProduct}=="0001"
ATTRS{bcdDevice}=="0206"
ATTRS{bDeviceClass}=="09"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bNumConfigurations}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{speed}=="12"
ATTRS{busnum}=="6"
ATTRS{devnum}=="1"
ATTRS{version}==" 1.10"
ATTRS{maxchild}=="2"
ATTRS{quirks}=="0x0"
ATTRS{authorized}=="1"
ATTRS{manufacturer}=="Linux 2.6.27-14-server uhci_hcd"
ATTRS{product}=="UHCI Host Controller"
ATTRS{serial}=="0000:00:1d.2"
ATTRS{authorized_default}=="1"
looking at parent device '/devices/pci0000:00/0000:00:1d.2':
KERNELS=="0000:00:1d.2"
SUBSYSTEMS=="pci"
DRIVERS=="uhci_hcd"
ATTRS{vendor}=="0x8086"
ATTRS{device}=="0x2832"
ATTRS{subsystem_vendor}=="0x1179"
ATTRS{subsystem_device}=="0xff64"
ATTRS{class}=="0x0c0300"
ATTRS{irq}=="18"
ATTRS{local_cpus}=="ffffffff,ffffffff"
ATTRS{local_cpulist}=="0-63"
ATTRS{modalias}=="pci:v00008086d00002832sv00001179sd0000FF64bc0Csc03i00"
ATTRS{broken_parity_status}=="0"
ATTRS{msi_bus}==""
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""
Again, the ttyUSB designation will not help to focus in on this particular device. What is needed is something unique about the device to use in the udev matching criteria. For this device and the other wireless card, I chose to rename the device as well as create a symbolic link device name. Using the fields depicted in red in the above, the udev rule I formulated becomes:
# Create /dev/EVDO* symlinks for Sierra EVDO modem
KERNEL=="ttyUSB*", ATTRS{product}=="Sierra*", NAME="EVDO%s{port_number}"
NAME=="EVDO0", SYMLINK+="modem"
The first part of this rule renames each
/dev/ttyUSB#
to /dev/EVDO0 or 1 or 2
. Regardless of where the device's first ttyUSB designation starts, the first device is always /dev/EVDO0
. The second line of this rule creates a symbolic link device name of /dev/modem
linked to the /dev/EVDO0
. Now, all future references for connecting to the Sprint Wireless internet can be made through /dev/modem
. The rule for the Curitel card is very similar to that of the Sierra Wireless, save that the modem is the second device. For this card, I use the following rule in the 60-symlinks.rules
file.
# Create /dev/EVDO* symlinks for Curitel EVDO modem
KERNEL=="ttyUSB*", ATTRS{product}=="Curitel*", NAME="EVDO%s{bInterfaceNumber}"
NAME=="EVDO01", SYMLINK+="modem"
The Curitel did not possess a port number attribute, so I used the bInterfaceNumber attribute. This attribute, however, takes on the values 00, 01 and 02 with this device. Thus, the
/dev/modem
symbolic link device name is linked to /dev/EVDO01
. However, the symbolic link is still /dev/modem
.Now, with all of the modifications made to the
60-symlinks.rules
file, plugging these devices in should cause them to be configured with the new names and or symbolic device links that were programmed into the file's rules. Let's see.
$ ls -aFl /dev/E* /dev/K*
crw-rw---- 1 root dialout 188, 0 2009-08-13 17:06 /dev/EVDO0
crw-rw---- 1 root dialout 188, 1 2009-08-13 17:06 /dev/EVDO1
crw-rw---- 1 root dialout 188, 2 2009-08-13 17:06 /dev/EVDO2
lrwxrwxrwx 1 root root 7 2009-08-13 17:17 /dev/KeySpan -> ttyUSB3
Exactly what the doctor ordered!