thewatertower.org.uk

Linux: Automatic actions driven by USB events


Making things happen when most common USB devices are connected is pretty common place. For example: getting a /media mountpoint when a USB drive is connected.

I assume, therefore, that the irritating windows-esque 'what do you want to do' GUI is triggered by the same progress. Irritating because it never remembers what to do.

I want to be able to make specific things happening. Of most significance are:

  • Giving my PalmOS Sony Clie and IP address using pppd when I try to hotsync it.

  • Loading a synthesiser and binding it to my USB MIDI keyboard.

Both work satisfactorily on SuSE 9.3, but the hotplug approach seems to have been depreciated by 10.2


The old way: USB Hotplug

Linux USB hotplug allows scripts to be run when a USB event occurs. For example, when I hotsync my PalmOS powered Sony Clie, Linux serves out an IP address using pppd so the Clie can connect across the network to Windows.

Unfortunately, the entire hotplug infrastructure seems to be have been switched out between 2.6.11 and 2.6.18.

It used to comprise:

  • /etc/hotplug/blacklist - which lists the kernel modules which won't load on a USB event ('visor' being present breaks the PalmOS event)

  • /etc/hotplug/usb/<script> - in this case the script is called 'visor'


The new way: udev rules

Looks simple enough, based on my sources:


udev/HAL/submount basics

Functionality relating to hotplugging and device automation appears to be fragmented and / or littered all over the system.

Some of the locations I've found with scripts and configuration include:

   # most of HAL and FDI seems to live here .. and the directories are populated on Suse 10.2
   # references to freedesktop suggest that userspace stuff is more likely to be found here
/usr/share/hal
               /fdi/policy/
               /fdi/information/
               /fdi/pre-probe/
               /device-manager
   # some policy is held here, and empty directories exist for information, pre-probe
/etc/hal/fdi
/etc/hal/fdi/policy/90osvendor
/etc/hal/fdi/policy/10osvendor
   # I suspect that udev is significantly different; for a start, the config aint XML
   # But for the effects seen on the desktop, the distinction is, I think, limited
/etc/udev/rules.d

udev

get udev to rescan sysfs:

udevtrigger

Turn on logging (to /var/log/messages) in /etc/udev/udev.conf

udev_log="debug"

Interact with udev daemon

   # change logging level on the fly (default: err  also: info)
udevcontrol log_priority=debug
   # tell udevd to reload the rules
udevcontrol reload_rules

Monitor udev kernel events etc. real time (works on Ubuntu 8.04, missing on Suse 10.3)

sudo udevadm monitor --env --kernel --udev

Not present on Ubuntu 6.06, try instead:

sudo udevmonitor --env

HAL

Query devices on the system

lshal

Restart HAL daemon

rchal restart

sources


udev rules - sources of information

Information on which to base rules can come from the following sources.

All the non-sysfs values appear to be also written to the environment context. the following rule and script writes the environment out to /tmp and logs to syslog.

/etc/udev/rules.d # cat 39-keystation.rules
# SUBSYSTEM=="sound", KERNEL=="controlC?", ACTION=="remove", RUN+="/etc/udev/scripts.d/unplug udev_rule_39"
ACTION=="remove", RUN+="/etc/udev/scripts.d/trigger udev_rule_39"
ACTION=="add", RUN+="/etc/udev/scripts.d/trigger udev_rule_39"

/etc/udev/rules.d # cat /etc/udev/scripts.d/trigger
#!/bin/sh
env > /tmp/$1.$$
/bin/logger -i -t $1 -- "ACTION $ACTION SUBSYSTEM $SUBSYSTEM DEVNAME $DEVNAME"

/etc/udev/rules.d # cat /tmp/udev_rule_39.10740
SUBSYSTEM=sound
DEVPATH=/devices/pci0000:00/0000:00:07.2/usb1/1-1/1-1:1.0/sound/card1/midiC1D0
MINOR=8
ACTION=add
PWD=/
UDEV_LOG=7
MAJOR=116
UDEVD_EVENT=1
DEVNAME=/dev/snd/midiC1D0
SHLVL=1
SEQNUM=1831
_=/usr/bin/env

Qualifying the rule further with sysfs info requires you to query the right device. Establish this with the following command ..

/etc/udev/rules.d # udevinfo -q path -n /dev/snd/midiC1D0
/devices/pci0000:00/0000:00:07.2/usb1/1-1/1-1:1.0/sound/card1/midiC1D0

.. and then query the device:

/etc/udev/rules.d # udevinfo -a -p /devices/pci0000:00/0000:00:07.2/usb1/1-1/1-1:1.0/sound/card1/midiC1D0

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:07.2/usb1/1-1/1-1:1.0/sound/card1/midiC1D0':
    KERNEL=="midiC1D0"
    SUBSYSTEM=="sound"
    DRIVER==""
    ATTR{dev}=="116:8"

  looking at parent device '/devices/pci0000:00/0000:00:07.2/usb1/1-1/1-1:1.0/sound/card1':
    KERNELS=="card1"
    SUBSYSTEMS=="sound"
    DRIVERS==""
    ATTRS{uevent}==""
..
  looking at parent device '/devices/pci0000:00/0000:00:07.2/usb1/1-1':
    KERNELS=="1-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{product}=="USB Keystation 88es"
    ATTRS{manufacturer}=="M-Audio"
    ATTRS{quirks}=="0x0"
..


udev - bind synthesizer to MIDI keyboard

The first step was to turn on debugging (udevcontrol log_priority=debug) and then use the output to give clues about which part of the udev rules are key.

Some of the notable actions, such as switching the controlC* value to snd/controlC* is done by /etc/udev/rules.d/40-alsa.rules, as is the call to alsactl.

udevd-event[13245]: udev_rules_get_name: rule applied, 'midiC1D0' becomes 'snd/midiC1D0'
udevd-event[13248]: udev_rules_get_name: rule applied, 'controlC1' becomes 'snd/controlC1'
udevd-event[13248]: run_program: '/usr/sbin/alsactl -F restore 1'

/etc/udev/rules.d # egrep 'controlC|alsactl' *
40-alsa.rules:KERNEL=="controlC[0-9]*", NAME="snd/%k"
40-alsa.rules:SUBSYSTEM=="sound", KERNEL=="controlC?", RUN+="/usr/sbin/alsactl -F restore %n"

My goal is to put a rule for when the synthesizer is to be unloaded, and a rule after it when it needs to be started up. It then becomes necessary to narrow down the events; the keyboard generated at least a dozen, and I only want one of them to be picked up.

Having tried different approaches, I finally settled on an iterative approach. I loaded up the trigger script and rule39, see above, and then further qualified it with the device's description. This will ensure other devices don't trigger the same rules.

SYSFS{product}=="*USB Keystation 88es*", ACTION=="remove", RUN+="/etc/udev/scripts.d/trigger udev_rule_39"
SYSFS{product}=="*USB Keystation 88es*", ACTION=="add", RUN+="/etc/udev/scripts.d/trigger udev_rule_39"

I would then look to filter it down to a single event based on the SUBSYSTEM or DEVNAME. Follows are the entries logged out by my trigger script.

udev_rule_39[13229]: ACTION add SUBSYSTEM usb DEVNAME /dev/bus/usb/001/014
udev_rule_39[13237]: ACTION add SUBSYSTEM usb_endpoint DEVNAME /dev/usbdev1.14_ep00
udev_rule_39[13239]: ACTION add SUBSYSTEM usb DEVNAME 
udev_rule_39[13244]: ACTION add SUBSYSTEM sound DEVNAME 
udev_rule_39[13252]: ACTION add SUBSYSTEM sound DEVNAME /dev/snd/midiC1D0
udev_rule_39[13260]: ACTION add SUBSYSTEM sound DEVNAME /dev/snd/controlC1
udev_rule_39[13263]: ACTION add SUBSYSTEM sound DEVNAME /dev/mixer1
udev_rule_39[13264]: ACTION add SUBSYSTEM usb DEVNAME 
udev_rule_39[13269]: ACTION add SUBSYSTEM usb_endpoint DEVNAME /dev/usbdev1.14_ep02
udev_rule_39[13272]: ACTION add SUBSYSTEM usb_endpoint DEVNAME /dev/usbdev1.14_ep81
udev_rule_39[13274]: ACTION add SUBSYSTEM sound DEVNAME /dev/dmmidi1
udev_rule_39[13278]: ACTION add SUBSYSTEM sound DEVNAME /dev/midi1
udev_rule_39[13411]: ACTION remove SUBSYSTEM sound DEVNAME /dev/mixer1
udev_rule_39[13413]: ACTION remove SUBSYSTEM sound DEVNAME /dev/midi1
udev_rule_39[13415]: ACTION remove SUBSYSTEM sound DEVNAME /dev/dmmidi1
udev_rule_39[13417]: ACTION remove SUBSYSTEM sound DEVNAME /dev/snd/midiC1D0
udev_rule_39[13419]: ACTION remove SUBSYSTEM sound DEVNAME /dev/snd/controlC1
udev_rule_39[13424]: ACTION remove SUBSYSTEM usb_endpoint DEVNAME /dev/usbdev1.14_ep81

Its not that simple though. Suspect sysfs doesn't present the same information when a device is being shut down. So, I settled for the following two rules:

# cat /etc/udev/rules.d/10-keystation.rules
   # stop rule
KERNEL=="midiC[D0-9]*",SUBSYSTEM=="sound", ACTION=="remove", RUN+="/etc/udev/scripts.d/keystation stop"
   # start rule
KERNEL=="midiC[D0-9]*", SYSFS{product}=="*USB Keystation 88es*", ACTION=="add", RUN+="/etc/udev/scripts.d/keystation start"

     # 9.4.09
     # KERNEL== string appears wrong, at least on ubuntu 6.06. this was obvious from udevmonitor output

# cat /etc/udev/rules.d/10-keystation.rules
   # stop rule
KERNEL=="midiC[0-9]D*",SUBSYSTEM=="sound", ACTION=="remove", RUN+="/etc/udev/scripts.d/keystation stop"
   # start rule
KERNEL=="midiC[0-9]D*", SYSFS{product}=="*USB Keystation 88es*", ACTION=="add", RUN+="/etc/udev/scripts.d/keystation start"

Getting this working on Ubuntu 6.06

First, I had to enable 'universe' for the package manager; there were entries remarked out in /etc/apt/sources.list

# apt-get update
# apt-get install fluidsynth

    # required for sfarkxtc; decompressor for sound fonts

# apt-get install libstdc++5


udev - ppp for Palm (Suse 10.2)

Based on Carsten's page, I set up some initial rules. Goal is to run a script off the back of the palm pilot turning up, so to see what I have to work with, I dump the environment.

/ # cat /etc/udev/rules.d/10-visor.rules
BUS=="usb", SYSFS{product}=="Palm Handheld*", KERNEL=="ttyUSB[13579]", SYMLINK+="pilot"
BUS=="usb", SYSFS{product}=="Palm Handheld*", KERNEL=="ttyUSB[13579]", RUN+="/home/ben/udev_pilot.sh"
/ # cat /home/ben/udev_pilot.sh
#!/bin/sh
MyProcID=$$;export MyProcID
env > /tmp/$$_udev.out
MyName=`basename $0`
logger -t "$MyName[$$]" "completed"

Something I discovered using hotplug was that the Palm creates two devices; Carsten's rule is only interested in the odd numbered one, which I'll stick with.

Syslog output is as follows:

kernel: usb 1-1: new full speed USB device using uhci_hcd and address 4
kernel: usb 1-1: new device found, idVendor=054c, idProduct=009a
kernel: usb 1-1: new device strings: Mfr=1, Product=2, SerialNumber=0
kernel: usb 1-1: Product: Palm Handheld
kernel: usb 1-1: Manufacturer: Sony
kernel: usb 1-1: configuration #1 chosen from 1 choice
kernel: usb 1-1: palm_os_4_probe - error -32 getting connection info
kernel: visor 1-1:1.0: Handspring Visor / Palm OS converter detected
kernel: usb 1-1: Handspring Visor / Palm OS converter now attached to ttyUSB0
kernel: usb 1-1: Handspring Visor / Palm OS converter now attached to ttyUSB1
hwup: usb-devpath-/devices/pci0000:00/0000:00:1d.0/usb1/1-1 -o hotplug
hwup: HWDESC='usb-devpath-/devices/pci0000:00/0000:00:1d.0/usb1/1-1'
hwup: CONFIG=''
hwup: Device already bound to a driver
hwup: usb-devpath-/devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.0 -o hotplug
hwup: HWDESC='usb-devpath-/devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.0'
hwup: CONFIG=''
hwup: Device already bound to a driver
udev_pilot.sh[6478]: completed
udev_pilot.sh[6482]: completed
kernel: usb 1-1: USB disconnect, address 4
kernel: visor ttyUSB0: Handspring Visor / Palm OS converter now disconnected from ttyUSB0
kernel: visor ttyUSB1: Handspring Visor / Palm OS converter now disconnected from ttyUSB1
kernel: visor 1-1:1.0: device disconnected

Looks like I get two USB events for the price of one. First is for a usb-serial device ..

SUBSYSTEM=usb-serial
DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.0/ttyUSB1
ACTION=add
PWD=/
UDEV_LOG=3
UDEVD_EVENT=1
SHLVL=1
MyProcID=6478
PHYSDEVBUS=usb-serial
SEQNUM=1662
_=/usr/bin/env

.. second is for a tty device; a sub-device, methinks.

SUBSYSTEM=tty
DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb1/1-1/1-1:1.0/ttyUSB1/ttyUSB1
MINOR=1
ACTION=add
PWD=/
UDEV_LOG=3
MAJOR=188
DEVLINKS=/dev/pilot
UDEVD_EVENT=1
DEVNAME=/dev/ttyUSB1
SHLVL=1
MyProcID=6482
SEQNUM=1663
_=/usr/bin/env

Reference back to my hotplug solution showed it working off /dev/ttyUSB0 (I'll settle for its twin) so I think I want the second. Daniel's doc says there's a SUBSYSTEM key, so I'll modify the rule ...

BUS=="usb", SYSFS{product}=="Palm Handheld*", KERNEL=="ttyUSB[13579]", SUBSYSTEM=="tty", RUN+="/home/ben/udev_pilot.sh"

Sure enough, I just get one execution now, the second.

Combined with relevant code from the old hotplug script, I got it working, within the PPP "server", with the following modifications. NB - I've moved the script to /etc/udev/scripts.d/udev_pilot, after creating the scripts.d directory.

First, PPP can't work with the odd devices, only the even ones.

kernel: PPP generic driver version 2.4.2
pppd[7217]: pppd 2.4.4 started by root, uid 0
kernel: visor ttyUSB1: Device lied about number of ports, please use a lower one.
pppd[7217]: Failed to open /dev/ttyUSB1: No such device
#snip#
kernel: visor ttyUSB1: Device lied about number of ports, please use a lower one.
pppd[7217]: Failed to open /dev/ttyUSB1: No such device
pppd[7217]: Exit.

So I change the rule to have eyes only for the evens.

BUS=="usb", SYSFS{product}=="Palm Handheld*", KERNEL=="ttyUSB[02468]", SYMLINK+="pilot"
BUS=="usb", SYSFS{product}=="Palm Handheld*", KERNEL=="ttyUSB[02468]", SUBSYSTEM=="tty", RUN+="/etc/udev/scripts.d/udev_pilot"

Second, the procedure for setting up routing external to the PPP server appeared to have failed. However, it seems that while I can't ping the address from elsewhere on the network, it has connectivity just fine.

echo 1 > /proc/sys/net/ipv4/ip_forward
/usr/sbin/iptables -t nat -A POSTROUTING -s 192.168.1.140/255.255.255.255 -o eth0 -j MASQUERADE

Investigating, I found another way to achieve the same goal, and also how to query iptables.

/ # echo 1 > /proc/sys/net/ipv4/conf/all/forwarding
/ # iptables -t nat -A POSTROUTING -s 192.168.1.140/255.255.255.255 -d ! 192.168.1.140/255.255.255.255 -o eth0 -j MASQUERADE
/ # iptables -t nat -L | grep MASQUERADE -B 2
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE 0 -- 192.168.1.140 !192.168.1.140

ppp for Palm - compression errors

Somethings don't change. I was getting these errors with hotplug, and IIRC it can be pretty disruptive.

kernel: PPP: VJ decompression error

Edit /etc/ppp/options and add the following line. I put it a little after 'noauth'.

novj

udev - ppp for Palm (Ubuntu 8.04)

Off the shelf, the TTY devices aren't being created.

Have established also that the 'visor' driver is blacklisted, which appears to prevent it being loaded by modprobe from within udev rules. This, I think, would happen in /etc/udev/rules.d/90-modprobe.rules, via /sbin/modprobe -Q $env{MODALIAS}. This is effectively what SuSE does, only via the /sbin/hwup script.

Remarked out the 'blacklist visor' entry in /etc/modprobe.d/libpisock9, and it gets this far:

kernel: usb 4-1: new full speed USB device using uhci_hcd and address 2
kernel: usb 4-1: configuration #1 chosen from 1 choice
kernel: usbcore: registered new interface driver usbserial
kernel: /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: USB Serial support registered for generic
kernel: usbcore: registered new interface driver usbserial_generic
kernel: /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: USB Serial Driver core
kernel: /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: USB Serial support registered for Handspring Visor / Palm OS
kernel: /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: USB Serial support registered for Sony Clie 3.5
kernel: /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: USB Serial support registered for Sony Clie 5.0
kernel: visor: probe of 4-1:1.0 failed with error -5
kernel: usbcore: registered new interface driver visor
kernel: /build/buildd/linux-2.6.24/drivers/usb/serial/visor.c: USB HandSpring Visor / Palm OS driver
kernel: usb 4-1: USB disconnect, address 2

The links below suggest that visor is best loaded at boot, so its in /etc/modules now - and back in the blacklist. However, it appears that the issue is in the Kernel; potentially fixed in 2.6.25 or 2.6.26. So it looks like my Palms will be getting IP addresses from SuSE for while.


Printed and hosted by Prater Raines Ltd, 98 Sandgate High Street, Folkestone CT20 3BY.
Published and promoted by Ben Prescott, 14, St James's Square, Bournemouth, BH5 2BX. All rights reserved.
The views expressed are solely those of the author, not of the service provider.