December 08, 2005

udev: eliminating scripts, setting GROUP, understanding variables

In response to a few queries, here's some more information about udev.

%p or $devpath or DEVPATH is the path under /sys, but without the leading "/sys." E.g., the device path will be /sys/block/sdc, and DEVPATH will be "/block/sdc." $kernel or %k is the path *under* dev, e.g., bus/usb/002/017, so /dev/%k is the actual path to the device name.

Using Program. PROGRAM runs *before* the anything is done in the rule. If PROGRAM fails, the entire rule fails. When I was making errors in PROGRAM -- for example, when I was using the wrong SUBSYTEM, or I didn't give a full path name to umount -- then the SYMLINK wasn't created because PROGRAM returned an error.

So, let's say I want to mount a flash drive. I don't need an external script; I can use:

SUBSYSTEM=="block", ACTION=="add", SYSFS{product}=="Flash Disk", SYMLINK="ums", ENV{flashdrive}="%p", RUN+="/bin/mount -t vfat -o user,sync,umask=0000 /dev/%k /ums"

Note that you must give the full path name to "mount," otherwise RUN will assume it's a udev script and try to find it in /lib/udev or some other udev-specific location.

If I'd used "PROGRAM" instead of run, then udev would attempt to execute mount before adding the /dev entry, PROGRAM would return a non-zero exit code, and the rule would not execute and the /dev entry would not be made. (Well certainly the SYMLINK would not be made; it's possible the /dev entry is made, but I don't think it is.)

The man page mentions the ENV, but what are the contents of this mysterious variable? They can be viewed by using "udevmonitor --env" -- it gives a printout of each ENV variable as it's used/set. I suspect that there are more than the ones we see...

Aras Vaichas explains that "SUBSYSTEMs are what you find in /sys/class/ /sys/block/ /sys/bus/, e.g. usb, scsi, serio, etc." You can see the SUBSYSTEMs by using "udevmonitor --evn" and looking for SUBSYTEM. The PHYSDEVDRIVER variable is the associated kernel module.

Finally, setting GROUP for a device you've hotplugged. Instead of using an external script as was the case with hotplug, you can use a simple rule instead. This is how I set GROUP when I mount my USB-enabled camera:

SUBSYSTEM=="usb_device", SYSFS{idVendor}=="04a9", SYSFS{idProduct}=="3051", GROUP="camera"

The trick is knowing that it's the "usb_device" SUBSYSTEM that creates the USB device entry that gtkam and libgphoto2 use, e.g., /dev/usb/002/017. Finding out what SUBSYSTEM to use is a trail-and-error process, but udevmonitor --env does display the /dev entry when it's created, and that's a very good start.

Posted by Moshe Yudkowsky at 08:42 AM

December 01, 2005

Migration from devfs to udev

I recently migrated from devfs to udev; although many users simply make the transition without any problems, I could't boot up without using devfs. Here's a mini "how to" that explains how to fix the basic migration from devfs to udev if you find that you can't boot.

Here's a summary of the problem I encountered and how I resolved it.

Problem: as I booted up, I got the usual deluge of reports; then I got three error messages:

/sbin/init: 432 cannot create /dev/null
/sbin/init: 433 cannot open dev/console
Kernel panic -- not syncing: Attempted to kill init!

After a bit of research and some asking around, I found that the answer was as follows.

There's a difference between devfs and udev. Devfs makes the
"console" and "null" files available by the time the files are needed
during the boot. udev starts later in the boot cycle, so you must supply your own copy of /dev/console and /dev/null. (There's a facility in udev that's supposed to create these files in advance (/etc/udev/links.conf in the Debian package), but it didn't work in my case.)

Therefore, before trying to boot using udev only, make certain you have a "console" and "null" file in /dev. Of course, if your system is running, you have these files now -- but the question is, were they part of the /dev directory, or did devfs put it there? Here's how to check.

* First, mount the root directory someplace other than /, so that the /dev
directory can be examined as a standalone -- so it can be seen as it looks without stuff that devfs puts there. As root, issue the command:

# mount -o bind / /mnt

(where /mnt is some random mouting point).


* Next, look at /mnt/dev. You need to have two special files there, console and null:

crw------- root root 5, 1 Nov 28 16:50 console
crw-rw-rw- root root 1, 3 Nov 28 16:51 null

If you have them already, you're all set. On my system, I had "null" and it was *not* a "c" (special character) file; it was an ordinary file. I got rid of that file and issued the following commands to create console and null:


# mknod -m 660 console c 5 1
# mknod -m 660 null c 1 3


* Unmount the / file system:

# umount /mnt.

* Make certain you have udev! Linux 2.6.14-2 requires udev to boot, but you are responsible for actually installing udev.

* Once /dev/console and /dev/null exist, you can boot using udev only. (For example, on my system, a boot using udev only is done by removing the "devfs=mount" command from lilo.) There's other stuff to do to convert completely to udev, but this gets you started.

Posted by Moshe Yudkowsky at 06:54 AM

January 10, 2005

Hotplug for USB Mass Storage (UMS)

For some reason, getting USB mass storage (UMS) to work with hotplugging on Linux seems to be a bit of a mystery -- as far as I can tell, there's no documentation at all for the user. In this "how-to" note I'm going to discuss how I managed to get it working on my system. This same script works for my UMS-style iRiver MP3 player.

I have hotplugging installed on my Debian 2.6.9 version Linux system. Inside the /etc/hotplug directory is one file to examine, one file to alter, and one file to add.

Hotplugging works as follows. When I plug something into the USB bus, it generates an "add" event, and when I remove something it generates a "remove" event. The usb.agent file runs on either of these events, and is called with a list of information about the event:

idVendor idProduct bcdDevice_lo bcdDevice_hi bDeviceClass bDeviceSubClass bDeviceProtocol bInterfaceClass bInterfaceSubClass bInterfaceProtocol driver_info

The usb script also has access to these variables:

# Kernel USB hotplug params include:
# ACTION=%s [add or remove]
# DEVPATH=%s [in 2.5 kernels, /sys/$DEVPATH]
# PRODUCT=%x/%x/%x
# INTERFACE=%d/%d/%d [ for interface 0, if TYPE=0/*/* ]
# TYPE=%d/%d/%d
# And if usbfs (originally called usbdevfs) is configured, also:
# DEVFS=/proc/bus/usb [gone in 2.5]
# DEVICE=/proc/bus/usb/%03d/%03d

There's no formal documentation, as far as I can tell, what these mean, even though they are crucial to get hotplugging to work. Don't despair -- this note supplies these missing links.

The usb.agent executable now decides what to do next. It looks in usb.distmap and usb.usermap to determine if there's anything to be done.

The entries in usb.usermap and usb.distmap are as follows:


usb module match_flags idVendor idProduct bcdDevice_lo bcdDevice_hi bDeviceClass bDeviceSubClass bDeviceProtocol bInterfaceClass bInterfaceSubClass bInterfaceProtocol driver_info

"usb_module" is the name of an executable to run *if* the current event matches the information in the rest of the line. What has to match? "match_flags" tells you which items from the list must match:


USB_MATCH_VENDOR=$((0x0001))
USB_MATCH_PRODUCT=$((0x0002))
USB_MATCH_DEV_LO=$((0x0004))
USB_MATCH_DEV_HI=$((0x0008))
USB_MATCH_DEV_CLASS=$((0x0010))
USB_MATCH_DEV_SUBCLASS=$((0x0020))
USB_MATCH_DEV_PROTOCOL=$((0x0040))
USB_MATCH_INT_CLASS=$((0x0080))
USB_MATCH_INT_SUBCLASS=$((0x0100))
USB_MATCH_INT_PROTOCOL=$((0x0200))

To pick a non-random example: Let's say I have a UMS flash drive and I want to run a particular script every time it's plugged in. I used "match vendor" and "match product", which makes match_flags = 0x3, and if that UMS device is plugged in, a particular script runs.

The real mystery is how find the idVendor, idProduct, etc. I found that by using lsusb -v:

Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x0ea0 Ours Technology, Inc.
idProduct 0x2168 Transcend JetFlash 2.0
bcdDevice 2.00
iManufacturer 1
iProduct 2
iSerial 3
bNumConfigurations 1

so the line in usb.usermap now looks like this:

ums (module to run) 0x3 (what to match) 0x0ea0 (vendor ID) 0x2168 (product ID)

and the rest all zeros. In actuality, the line looks like:

ums 0x0003 0x0ea0 0x2168 0x0000 0x0000 0x00 0x00 0x00 0x00 0x00 0x00 0x00000000

If I were feeling clever, and I may feel clever some day, I might decide to make a universal flash, mass storage mounting hotplug system. To do that, I'd use the rest of the lsusb -v information:


bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 3
bInterfaceClass 8 Mass Storage
bInterfaceSubClass 6 SCSI
bInterfaceProtocol 80 Bulk (Zip)
iInterface 0

Clearly, could use bInterfaceClass to select "mass storage" and write a module of that type. But I digress.

So, when I plug in my UMS device, usb.agent looks into user.usermap, finds the matching idVendor and idProduct, sees that it need only match those two, and attempts to run the module named on that line. That module is called "ums," and it resides in /etc/hotplug/usb.

"ums" accomplishes two tasks: (a) it mounts the flash drive and (b) has a script to umount the flash drive on disconnect.

Here's the ums script, part by part. First I create a log file:

MOUNTDIR="/ums"
LOG=/var/log/usb-hotplug.log

Next I try to find where this usb device is mounted, which is *really hard* to do. On my system, on each hotplug, devfs and sd_mod conspire to mount the drive on a different part of the SCSI chain. My goal is to find the SCSI device and mount it.

I log the DEVICE and DEVPATH that's inherited from the usb.agent script:


# DEVICE seems pretty useless
# need to search for "host*" in DEVPATH
echo "$(date) $(time) $DEVICE, $DEVPATH requested" >> $LOG

Next, I search along DEVPATH to find a "host[0-9][0-9]" directory. DEVPATH looks like "/devices/pci0000:00/0000:00:10.3/usb4/4-2/4-2:1.0" , and it must be preceeded by "/sys" to point to a real directory structure. If I find a directory called, e.g., host21, I've seen on my system that this means that the flash drive is associated to /dev/scsi/host21/bus0/target0/lun0/disc.

NEWDEV=""
((count=10))
while [ -z $NEWDEV ]
do
# look for host* on DEVPATH
NEWDEV=$(find /sys${DEVPATH} -name "host*") # find scsi host name
NEWDEV=$(basename $NEWDEV) # find just that host name
[[ $count -le 0 ]] && break # stop if too many attempts
((count -=1))
echo "$(date) $(time) $DEVPATH was not ready, try again" >> $LOG
sleep 1
done


This goes in a loop. The USB event happens immediately, but it takes time for sd_mod to create the SCSI entry, and so if it's not ready first time around I wait a second and try again, up to 10 times. It's never taken more than once.

NEWDEV=/dev/scsi/${NEWDEV}/bus0/target0/lun0/disc

The name of the SCSI drive that corresponds to the usb flash drive

((count=10))
if [ ! -b $NEWDEV ]
then
echo "$(date) $(time) wait for $NEWDEV to show as SCSI device" >> $LOG
sleep 1
((count -= 1))
[[ $count -le 0 ]] && break
fi

Wait to make certain that the drive is created by the SCSI software.
Mount the drive if it exists:

if [ -b $NEWDEV ]
then
{
echo "$(date) $(time) mounting $NEWDEV" >> $LOG
# mount if plugged in
mount -t vfat -o user,sync,umask=0000 $NEWDEV $MOUNTDIR >>$LOG 2>&1
}

If it doesn't exist, write a faliure to the log file:


else
echo "$(date) $(time) $DEVICE requested, unable to mount or find $NEWDEV" >> $LOG
reason=$(file $NEWDEV)
echo "Reason: $reason" >> $LOG
fi

usb.agent expects a script named $REMOVER to exist which explains what to do when the device is unplugged. Create this script, which in our case will umount the drive, by echoing instructions into the file $REMOVER:

# create REMOVER script to umount when it unplugs
:> $REMOVER
echo "umount -f $MOUNTDIR >> $LOG" >> $REMOVER
echo "echo \"$(date) $(time) unmounting /ums\" >> $LOG" >> $REMOVER
chmod +x $REMOVER

If you're curious, REMOVER will be found in /var/run/usb.

The reason for this roundabout way of finding the SCSI disk is because there doesn't really seem to be any other way of extracting the information. There's no file that I've found in the USB chain that says "this USB device is represented by the SCSI deviced named 'X'."

Log output:

Mon Jan 10 10:10:50 CST 2005 /proc/bus/usb/004/023, /devices/pci0000:00/0000:00:10.3/usb4/4-2/4-2:1.0 requested
Mon Jan 10 10:10:50 CST 2005 /devices/pci0000:00/0000:00:10.3/usb4/4-2/4-2:1.0 was not ready, try again
Mon Jan 10 10:10:51 CST 2005 mounting /dev/scsi/host21/bus0/target0/lun0/disc

At the same time, from the messages log:

Jan 10 10:10:50 bagpipes usb.agent[12238]: usb-storage: already loaded
Jan 10 10:10:50 bagpipes kernel: SCSI device sdb: 128000 512-byte hdwr sectors (66 MB)
Jan 10 10:10:50 bagpipes kernel: sdb: assuming Write Enabled
Jan 10 10:10:50 bagpipes usb.agent[12238]: ums: loaded successfully
Jan 10 10:10:50 bagpipes kernel: /dev/scsi/host21/bus0/target0/lun0:
Jan 10 10:10:50 bagpipes kernel: Attached scsi removable disk sdb at scsi21, channel 0, id 0, lun 0
Jan 10 10:10:51 bagpipes scsi.agent[12240]: disk at /devices/pci0000:00/0000:00:10.3/usb4/4-2/4-2:1.0/host21/21:0:0:0

Posted by Moshe Yudkowsky at 10:49 AM

October 27, 2004

Modules, Modprobe, and Modutils: Migration Issues from Linux 2.4 to 2.6

When I migrated from Linux 2.4 to 2.6, I had some trouble with modules loading. In particular, when I booted the machine, my bcm5700.ko module didn't get control of my Ethernet interface; instead, a module called "eth1394" did instead. And then another module, tg3, took a whack at the interface as well; tg3 is the default ethernet driver, but Broadcom recommends their own bcm5700 module instead.

When my Ethernet insterface didn't work at all after booting into 2.6.8, I used "lsmod" to determine what modules were installed, and after loading and unloading modules, starting and stopping networking, and reading /var/log/messages to see how the boot went, discovered that bcm5700 wasn't loading but did work if loaded. This was very puzzling, because /etc/modules.conf was very explicit in trying to load bcm5700 for the eth0 interface.

I went to the /etc/modutils directly and tried to get the eth1394 (which is listed as "ip1394" in the log for unknown reasons) to stop loading. I used:

alias eth1393 off
alias tg3 off

to get these two modules to never load. (The commands went into a file called "networking-A7V8X" that I use to control kernel modules related to networking.) I rebuilt /etc/modules.conf using update-modules, rebooted the machine — and the modules loaded anyway.

In other words, /etc/modules.conf was being ignored by modprobe during boot time.

Eventually I stumbled across the answer by reading the man page for modprobe instead of just skimming it. Modprobe has changed between 2.4 and 2.6. The old information my /etc/modutils and /etc/modules.conf is not read under 2.6, and the Debian upgrade process won't automatically migrate my custom configurations from 2.4 to 2.6.

2.6 uses a /etc/modprobe.d directory, and it does not need a "/etc/modprobe.conf" file; it reads all the appropriate *.conf files and compiles the information at boot time. Very handy, actually.

To solve the problem of the eth1394 and tg3 grabbing the Ethernet interface, I used the "alias x off" commands I list above, placed them in a file in /etc/modprobe.d, and rebooted. This time the system functioned as expected and the Ethernet system used the proper bcm5700.ko driver.

I realize that there are other ways to force a particular driver/module to associate with a particular physical interface (use the "mapping" keyword in /etc/network/interfaces), but this one works for me as I don't need eth1394 for anything at the moment.

Posted by Moshe Yudkowsky at 08:51 PM

Migrating Broadcom bcm5700 from 2.4 to 2.6

Here's a technical note on how to migrate from a Broadcomm bcm5700 Ethernet driver under Linux 2.4.x (bcm5700.o) to a 2.6.x driver (bcm5700.ko). I did this migration on a Debian system based on an Asus A7V8X, and I've found that the documentation on how to do this is a little scanty.

To start, get the source of the Broadcom driver:

apt-get install bcm5700-source

This will install itself in /usr/src as a gzip file. As root, go to the /usr/src directory and unpack the file:

tar zxf bcm5700-source.tar.gz

This creates a directory modules, under which will be a directory bcm5700 which contains the source code.

The next step is to compile the bcm5700 source code. You don't need to be running 2.6.x to do this. You do need the current headers of your target kernel image. E.g., if you want bcm5700 to work for 2.6.8-1-k7, you will need the kernel headers for 2.6.8-1-k7. You don't need the source for the entire kernel.

To get the kernel headers, use

apt-get kernel-headers-2.6.8-1-k7

or whatever is appropriate for your target kernel. The headers will appear in your /usr/src directory as well.

You need to make /usr/src/linux point to these kernel headers. On my system, /usr/src/linux is simply a symlink to a real directory. I removed the old symlink (rm linux) and used

ln -s kernel-headers-2.6.8-1-k7 linux

to create the symlink. To compile the bcm5700.ko module, go to the /usr/src/linux directory and type

make-kpkg modules

This will make a Debian package in the /usr/src directory. You can then use

dpkg -i bcm5700-module-2.6.8_7.3.5-3+10.00.Custom_i386.deb

(or whatever the Debian is called on your sytsem) to install the driver. However, I could not figure out how to persuade make-kpkg to give the proper version to the Debian package; as you can see above it thinks it made a driver for 2.6.8, not 2.6.8-1-k7. This means that when I did the dpkg -i to install the Debian package, Debian failed partway through and put the bcm5700.ko file into /lib/modules/2.6.8, instead of it's proper place in /lib/modules/2.6.8-1-k7. No worries -- I copied the driver from there to /lib/modules/2.6.8-1-k7/kernel/drivers/net/bcm5700.ko, and I was ready to go.

That's it for creating the driver module. Fairly straightforward.

However, that doesn't mean that the driver actually loaded when I booted up the sytsem &mdash something else grabbed control of the Ethernet interface. See my next technical note, about migrating from Linux 2.4 and its system of /etc/modules.conf to Linux 2.6 and its system of /etc/modprobe.d, for details on how to resolve this little problem.

Posted by Moshe Yudkowsky at 08:29 PM