#!/bin/bash # # This is a reference implementation for /sbin/hotplug, which works # on most GNU/Linux systems without ANY additional software. # # This implementation delegates to type-specific agents, such as # "/etc/hotplug/network.agent", where they exist. Otherwise it # uses built-in in support for USB and PCI hotplug events, trying # to load a module that appears to handle this device. # # /proc/sys/kernel/hotplug controls the program invoked by the kernel. # /sbin/hotplug is the default value, which you may change. (A null # string prevents the kernel from invoking a hotplug program.) To make # this functionality available, your kernel config must include HOTPLUG # (and currently KMOD). # # # HISTORY: # # 06-Nov-2000 Build in support for modules.{usb,pci}map; Cardbus may # now work, with a kernel patch. /etc/hotplug directory hooks. # 09-Jul-2000 Initial version; kernel USB hotplugging starts, using # the existing USB scripts # # DEBUG=yes export DEBUG PATH=/bin:/sbin:/usr/sbin:/usr/bin if [ -t -o ! -x /usr/bin/logger ]; then mesg () { echo "$@" } else mesg () { /usr/bin/logger -t $0 "$@" } fi usage () { # Only one parameter (event type) is mandatory. # Everything else is type-specific. if [ -t ]; then echo "Usage: $0 type [...]" echo " $0 pci [add|remove]" echo " $0 usb" echo "Environment parameters are also type-specific." # FIXME: list non-builtin agents (/etc/hotplug/*.agent) else mesg "illegal usage $*" fi exit 1 } if [ "$DEBUG" != "" ]; then mesg "arguments ($*)" fi # Only one required argument: event type type being dispatched. # Examples: usb, pci, isapnp, network, ieee1394, printer, disk, ... if [ $# -lt 1 ]; then usage elif [ $1 = help -o $1 = '--help' ]; then usage # Prefer to delegate event handling: # /sbin/hotplug FOO ..args.. ==> /etc/hotplug/FOO.agent ..args.. # elif [ -x /etc/hotplug/$1.agent ]; then shift exec /etc/hotplug/$1.agent "$@" fi #################################################################### # # Builtin agent code -- directly processes modutils (2.3.20+) # output for MODULE_DEVICE_TABLE entries, so that you can hotplug # after installing only this script. # # This requires BASH ("declare -i") and needs some version of # AWK, typically /bin/gawk. Most GNU/Linux distros have these, # but some specialized ones (floppy based, etc) may not. # # NOTE: The match algorithms here aren't any smarter than those # in the kernel; they take the first match available, even if # it's not the "best" (most specific) match. That's NOT really # a feature. Agents written in "real" languages can more easily # support sophisticated driver selection algorithms, prioritize # (or add) administrator-specified bindings, override kernel # defaults, and so on. # # FIXME: can we avoid using '<<' to parse composite variables? # That may require a writable /tmp. AWK=gawk MODDIR=/lib/modules/`uname -r` # ISAPNP_MAP=$MODDIR/modules.isapnpmap PCI_MAP=$MODDIR/modules.pcimap USB_MAP=$MODDIR/modules.usbmap #################################################################### # # Kernel USB params are: # # ACTION=%s [add or remove] # PRODUCT=%x/%x/%s [last string is like '1.2' ] # INTERFACE=%d/%d/%d # TYPE=%d/%d/%d # # And if usbdevfs is configured, also: # # DEVFS=/proc/bus/usb # DEVICE=/proc/bus/usb/%03d/%03d # # If usbdevfs is mounted on /proc/bus/usb, $DEVICE is a file which # can be read to get the device's current configuration descriptor. # declare -i usb_idVendor usb_idProduct usb_bcdDevice declare -i usb_bDeviceClass usb_bDeviceSubClass usb_bDeviceProtocol declare -i usb_bInterfaceClass usb_bInterfaceSubClass usb_bInterfaceProtocol usb_convert_vars () { local XPROD version XPROD=`echo $PRODUCT | $AWK -F/ '{print "0x" $1, "0x" $2, $3 }'` read usb_idVendor usb_idProduct version << EOT $XPROD EOT # FIXME: Parse this right; it's BCD ... version "1.08" breaks here # usb_bcdDevice=$(( ( ${version%.*} * 256 ) + ${version#*.} )) if [ x$TYPE != x ]; then IFS=/ read usb_bDeviceClass usb_bDeviceSubClass usb_bDeviceProtocol << EOT $TYPE EOT else # out-of-range values usb_bDeviceClass=1000 usb_bDeviceSubClass=1000 usb_bDeviceProtocol=1000 fi if [ x$INTERFACE != x ]; then IFS=/ read usb_bInterfaceClass usb_bInterfaceSubClass usb_bInterfaceProtocol << EOT $INTERFACE EOT else # out-of-range values usb_bInterfaceClass=1000 usb_bInterfaceSubClass=1000 usb_bInterfaceProtocol=1000 fi } declare -i USB_ANY USB_ANY=0 # stdin is "modules.usbmap" syntax usb_map_modules () { # convert the usb_device_id fields to integers as we read them local module ignored declare -i idVendor idProduct bcdDevice_lo bcdDevice_hi declare -i bDeviceClass bDeviceSubClass bDeviceProtocol declare -i bInterfaceClass bInterfaceSubClass bInterfaceProtocol # comment line lists (current) usb_device_id field names read ignored # look at each usb_device_id entry while read module idVendor idProduct bcdDevice_lo bcdDevice_hi bDeviceClass bDeviceSubClass bDeviceProtocol bInterfaceClass bInterfaceSubClass bInterfaceProtocol ignored do : checkmatch $module : idVendor $idVendor $usb_idVendor if [ $idVendor -ne $USB_ANY -a $idVendor -ne $usb_idVendor ]; then continue fi : idProduct $idProduct $usb_idProduct if [ $idProduct -ne $USB_ANY -a $idProduct -ne $usb_idProduct ]; then continue fi # : bcdDevice range $bcdDevice_hi $bcdDevice_lo actual $usb_bcdDevice # if [ $bcdDevice_lo -ge $usb_bcdDevice ]; then # continue # fi # if [ $bcdDevice_hi -ne $USB_ANY -a $bcdDevice_hi -ge $usb_bcdDevice ]; then # continue # fi : bDeviceClass $bDeviceClass $usb_bDeviceClass if [ $bDeviceClass -ne $USB_ANY -a $bDeviceClass -ne $usb_bDeviceClass ]; then continue fi : bDeviceSubClass $bDeviceSubClass $usb_bDeviceSubClass if [ $bDeviceSubClass -ne $USB_ANY -a $bDeviceSubClass -ne $usb_bDeviceSubClass ]; then continue fi : bDeviceProtocol $bDeviceProtocol $usb_bDeviceProtocol if [ $bDeviceProtocol -ne $USB_ANY -a $bDeviceProtocol -ne $usb_bDeviceProtocol ]; then continue fi # NOTE: for now, this only checks the first of perhaps # several interfaces for this device. : bInterfaceClass $bInterfaceClass $usb_bInterfaceClass if [ $bInterfaceClass -ne $USB_ANY -a $bInterfaceClass -ne $usb_bInterfaceClass ]; then continue fi : bInterfaceSubClass $bInterfaceSubClass $usb_bInterfaceSubClass if [ $bInterfaceSubClass -ne $USB_ANY -a $bInterfaceSubClass -ne $usb_bInterfaceSubClass ]; then continue fi : bInterfaceProtocol $bInterfaceProtocol $usb_bInterfaceProtocol if [ $bInterfaceProtocol -ne $USB_ANY -a $bInterfaceProtocol -ne $usb_bInterfaceProtocol ]; then continue fi # It was a match! DRIVER=$module : driver $DRIVER break; done } #################################################################### # # Kernel Cardbus/PCI params are: # # PCI_CLASS=%X # PCI_ID=%X/%X # PCI_SLOT=%s # PCI_SUBSYS_ID=%X/%X # # If /proc is mounted, /proc/bus/pci/$PCI_SLOT is almost the name # of the binary device descriptor file ... just change ':' to '/'. # declare -i pci_class declare -i pci_id_vendor pci_id_device declare -i pci_subid_vendor pci_subid_device pci_convert_vars () { XID=`echo $PCI_ID | $AWK -F/ '{print "0x" $1, "0x" $2 }'` read pci_class pci_id_vendor pci_id_device << EOT 0x$PCI_CLASS $XID EOT XID=`echo $PCI_SUBSYS_ID | $AWK -F/ '{print "0x" $1, "0x" $2 }'` read pci_subid_vendor pci_subid_device << EOT $XID EOT } declare -i PCI_ANY PCI_ANY=0xffffffff # stdin is "modules.pcimap" syntax pci_map_modules () { # convert the usb_device_id fields to integers as we read them local module ignored declare -i vendor device declare -i subvendor subdevice declare -i class class_mask declare -i class_temp # comment line lists (current) pci_device_id field names read ignored # look at each pci_device_id entry while read module vendor device subvendor subdevice class class_mask ignored do : checkmatch $module : vendor $vendor $pci_id_vendor if [ $vendor -ne $PCI_ANY -a $vendor -ne $pci_id_vendor ]; then continue fi : device $device $pci_id_device if [ $device -ne $PCI_ANY -a $device -ne $pci_id_device ]; then continue fi : sub-vendor $subvendor $pci_subid_vendor if [ $subvendor -ne $PCI_ANY -a $subvendor -ne $pci_subid_vendor ]; then continue fi : sub-device $subdevice $pci_subid_device if [ $subdevice -ne $PCI_ANY -a $subdevice -ne $pci_subid_device ]; then continue fi class_temp="$pci_class & $class_mask" if [ $class_temp -eq $class ]; then DRIVER=$module : driver $DRIVER break; fi done } #################################################################### # # usage: load_driver type filename description # # (always) modprobes a single driver module, and optionally # invokes a module-specific setup script. # load_driver () { # find the driver using modutils output if [ -f $2 ]; then $1_convert_vars $1_map_modules < $2 else mesg "$2 missing" exit 1 fi if [ x$DRIVER = x ]; then mesg "... no driver for $3" exit 2 fi if [ "$DEBUG" != "" ]; then mesg Module $DRIVER matches $3 fi # maybe the driver needs loading if ! lsmod | grep "^$DRIVER " then if ! modprobe $DRIVER then mesg "... can't load module $DRIVER" exit 3 fi fi # NOTE: this assumes that driver bound to # this device; it'd be better to check ... # run any setup script # OR: /etc/modules.conf "post-install $DRIVER cmd ..." if [ -x /etc/hotplug/$1/$DRIVER ]; then if [ "$DEBUG" != "" ]; then mesg Driver setup: $DRIVER fi exec /etc/hotplug/$1/$DRIVER else exit 0 fi } #################################################################### # # Two basic policy agents are now built in: they try to load the # USB or PCI driver module corresponding to newly added devices. # They're used as backup, in case no smarter tools are available. # if [ $1 = usb ]; then # Until all kernel USB drivers get updated, you need: # http://www.linux-usb.org/policy.html if [ -f /etc/usb/policy ]; then exec /etc/usb/policy fi if [ x$PRODUCT = x ]; then mesg Bad USB invocation exit 1 fi # no more commandline params case x$ACTION in xadd) load_driver usb $USB_MAP "USB product $PRODUCT" # won't return ;; xremove) : USB remove, ignored exit 0;; x*|xhelp) usage $* ;; esac elif [ $1 = pci ]; then # NOTE: PCI includes CardBus if [ x$PCI_SLOT = x ]; then mesg Bad PCI invocation exit 1 fi # one more commandline param case $2 in add) if [ "$DEBUG" != "" -a -x /sbin/lspci ]; then mesg New PCI Device: `/sbin/lspci -s $PCI_SLOT` fi load_driver pci $PCI_MAP "PCI device at slot $PCI_SLOT" # won't return ;; remove) : PCI remove, ignored exit 0;; *|help) usage $* ;; esac fi mesg "$0: event type '$1' unsupported" exit 1