#!/bin/bash
# $Id: usb.agent 302 2005-01-19 16:41:20Z hare $
#
# USB-specific hotplug policy agent.
#
# This should handle 2.6.* USB hotplugging,
# with a consistent framework for adding device and driver
# specific handling.
#
# Normally, adding a usb device will modprobe a driver.  If there
# is a /etc/hotplug/usb/$DRIVER script set up, it will also run,
# handling tasks like loading firmware or starting daemons.
#
# Kernel USB hotplug params include:
#
#        ACTION=%s [add or remove]
#        DEVPATH=%s [/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:
#
#        DEVICE=/proc/bus/usb/%03d/%03d
#
# If usbfs is mounted on /proc/bus/usb, $DEVICE is a file which
# can be read to get the device's current configuration descriptor.
# (The "usbmodules" utility does that.)  Or it can be used by a
# user mode driver to interact with the usb device.  USB hotplug
# does *not* require usbfs (or sysfs) to work, although on 2.4
# some devices work better if "usbmodules" can help.
#
# For Linux 2.5+ kernels, there's no need for "usbmodules".  For
# two reasons:  first, hotplug is invoked for each interface, not
# just the first one.  Second, sysfs exposes descriptors so they
# are easy to use for "coldplug" event simulation.  (But sysfs is
# not a replacement for the driver I/O capabilities in usbfs.)
#

: ${ACTION?Bad invocation: \$ACTION is not set}
# : ${DEVPATH?Bad invocation: \$DEVPATH is not set}
# : ${PRODUCT?Bad invocation: \$PRODUCT is not set}
# : ${INTERFACE?Bad invocation: \$INTERFACE is not set}
# : ${TYPE?Bad invocation: \$TYPE is not set}

SYSFS=needed
cd /etc/hotplug
. ./hotplug.functions

if [ -e /sbin/hwscanqueue ]; then
    HWSCAN=/sbin/hwscanqueue
elif [ -e /usr/sbin/hwscanqueue ]; then
    HWSCAN=/usr/sbin/hwscanqueue
fi

# generated by modutils, for current 2.4.x (and later) kernels
#
MAP_CURRENT=$MODULE_DIR/modules.usbmap

# used for kernel drivers that don't show up in MAP_CURRENT
# or to overwrite default from MAP_CURRENT
#
MAP_HANDMAP=$HOTPLUG_DIR/usb.handmap

# used to run config scripts for user mode drivers (jPhoto, gPhoto2,
# rio500 tools, etc) ... instead of naming kernel modules, it names
# config scripts.  those could change $DEVICE permissions, etc.
#
# for purely user mode drivers, scripts $HOTPLUG_DIR/usb/NAME should be
# installed with usermap files in $HOTPLUG_DIR/usb/NAME.usermap instead
# of continuing to use/modify $MAP_USERMAP
#
MAP_USERMAP=$HOTPLUG_DIR/usb.usermap


# Each modules.usbmap format line corresponds to one entry in a
# MODULE_DEVICE_TABLE(usb,...) declaration in a kernel file.
#
# Think of it as a database column with up to three "match specs"
# to associate kernel modules with particular devices or classes
# of device.  The match specs provide a reasonably good filtering
# mechanism, but some driver probe() routines need to provide
# extra filtering.
#

usb_convert_vars ()
{
    set -- `IFS=/; echo $PRODUCT`
    usb_idVendor=$((0x$1))
    usb_idProduct=$((0x$2))
    usb_bcdDevice=$((0x$3))

    if [ "$TYPE" != "" ]; then
        set -- `IFS=/; echo $TYPE`
        usb_bDeviceClass="$1"
        usb_bDeviceSubClass="$2"
        usb_bDeviceProtocol="$3"
    elif [ -r $SYSFS/$DEVPATH/bDeviceClass ]; then
        usb_bDeviceClass=$((0x$(cat $SYSFS/$DEVPATH/bDeviceClass)))
        usb_bDeviceSubClass=$((0x$(cat $SYSFS/$DEVPATH/bDeviceSubClass)))
        usb_bDeviceProtocol=$((0x$(cat $SYSFS/$DEVPATH/bDeviceProtocol)))
    else
        # out-of-range values
        usb_bDeviceClass=1000
        usb_bDeviceSubClass=1000
        usb_bDeviceProtocol=1000
    fi

    if [ "$INTERFACE" != "" ]; then
        set -- `IFS=/; echo $INTERFACE`
        usb_bInterfaceClass="$1"
        usb_bInterfaceSubClass="$2"
        usb_bInterfaceProtocol="$3"
    elif [ -r $SYSFS/$DEVPATH/bInterfaceClass ]; then
        usb_bInterfaceClass=$((0x$(cat $SYSFS/$DEVPATH/bInterfaceClass)))
        usb_bInterfaceSubClass=$((0x$(cat $SYSFS/$DEVPATH/bInterfaceSubClass)))
        usb_bInterfaceProtocol=$((0x$(cat $SYSFS/$DEVPATH/bInterfaceProtocol)))
    else
        # out-of-range values
        usb_bInterfaceClass=1000
        usb_bInterfaceSubClass=1000
        usb_bInterfaceProtocol=1000
    fi
}

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))

#
# stdin is "modules.usbmap" syntax
# on return, all matching modules were added to $DRIVERS
#
usb_map_modules ()
{
    # convert the usb_device_id fields to integers as we read them
    local line module

    # look at each usb_device_id entry
    # collect all matches in $DRIVERS

    while read line
    do
        # comments are lines that start with "#" ...
        # be careful, they still get parsed by bash!
        case "$line" in
            \#*|"") continue ;;
        esac

        set -- $line

        module=$1
        match_flags=$(($2))

        idVendor=$(($3))
        idProduct=$(($4))
        bcdDevice_lo=$(($5))
        bcdDevice_hi=$(($6))

        bDeviceClass=$(($7))
        bDeviceSubClass=$(($8))
        bDeviceProtocol=$(($9))

        shift 9
        bInterfaceClass=$(($1))
        bInterfaceSubClass=$(($2))
        bInterfaceProtocol=$(($3))

        : checkmatch $module

        : idVendor $idVendor $usb_idVendor
        if [ $USB_MATCH_VENDOR -eq $((match_flags & USB_MATCH_VENDOR)) ] &&
           [ $idVendor -ne $usb_idVendor ]; then
            continue
        fi

        : idProduct $idProduct $usb_idProduct
        if [ $USB_MATCH_PRODUCT -eq $((match_flags & USB_MATCH_PRODUCT)) ] &&
           [ $idProduct -ne $usb_idProduct ]; then
            continue
        fi

        : bcdDevice range $bcdDevice_hi $bcdDevice_lo actual $usb_bcdDevice
        if [ $USB_MATCH_DEV_LO -eq $((match_flags & USB_MATCH_DEV_LO)) ] &&
           [ $usb_bcdDevice -lt $bcdDevice_lo ]; then
            continue
        fi

        # bcdDevice_lo <= bcdDevice <= bcdDevice_hi
        if [ $USB_MATCH_DEV_HI -eq $((match_flags & USB_MATCH_DEV_HI)) ] &&
           [ $usb_bcdDevice -gt $bcdDevice_hi ]; then
            continue
        fi

        : bDeviceClass $bDeviceClass $usb_bDeviceClass
        if [     $USB_MATCH_DEV_CLASS \
             -eq $((match_flags & USB_MATCH_DEV_CLASS)) ] &&
           [ $bDeviceClass -ne $usb_bDeviceClass ]; then
            continue
        fi
        : bDeviceSubClass $bDeviceSubClass $usb_bDeviceSubClass
        if [     $USB_MATCH_DEV_SUBCLASS \
             -eq $((match_flags & USB_MATCH_DEV_SUBCLASS)) ] &&
           [ $bDeviceSubClass -ne $usb_bDeviceSubClass ]; then
            continue
        fi
        : bDeviceProtocol $bDeviceProtocol $usb_bDeviceProtocol
        if [     $USB_MATCH_DEV_PROTOCOL \
             -eq $((match_flags & USB_MATCH_DEV_PROTOCOL)) ] &&
           [ $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 [     $USB_MATCH_INT_CLASS \
             -eq $((match_flags & USB_MATCH_INT_CLASS)) ] &&
           [ $bInterfaceClass -ne $usb_bInterfaceClass ]; then
            continue
        fi
        : bInterfaceSubClass $bInterfaceSubClass $usb_bInterfaceSubClass
        if [     $USB_MATCH_INT_SUBCLASS \
             -eq $((match_flags & USB_MATCH_INT_SUBCLASS)) ] &&
           [ $bInterfaceSubClass -ne $usb_bInterfaceSubClass ]; then
            continue
        fi
        : bInterfaceProtocol $bInterfaceProtocol $usb_bInterfaceProtocol
        if [     $USB_MATCH_INT_PROTOCOL \
             -eq $((match_flags & USB_MATCH_INT_PROTOCOL)) ] &&
           [ $bInterfaceProtocol -ne $usb_bInterfaceProtocol ]; then
            continue
        fi

        # It was a match!
        DRIVERS="$module $DRIVERS"
        : drivers $DRIVERS
    done
}

#
# What to do with this USB hotplug event?
#
case $ACTION in

    add)
        # update current state
	[ "$HWSCAN" ] && $HWSCAN --usb $DEVPATH

        # If hwup does initialize the device there is nothing more to do
        if [ -x /sbin/hwup ] ; then
            /sbin/hwup  ${1:+$1-}devpath-$DEVPATH -o hotplug && exit
        fi

        usb_convert_vars

        FOUND=""
        if [ -f $SYSFS/$DEVPATH/manufacturer ]; then
            LABEL="USB `cat $SYSFS/$DEVPATH/manufacturer`"
            LABEL="$LABEL `cat $SYSFS/$DEVPATH/product`"
        else
            LABEL="USB product $PRODUCT"
        fi

        # cope with special driver module configurations first. So we can
        # overwrite default settings in MAP_CURRENT provide missing information
        if [ -r $MAP_HANDMAP ]; then
            load_drivers usb $MAP_HANDMAP "$LABEL"
            FOUND="$FOUND${DRIVERS:+ $DRIVERS}"
        fi

        if [ -r $MAP_CURRENT -a "$FOUND" == "" ]; then
            load_drivers usb $MAP_CURRENT "$LABEL"
            FOUND="$FOUND${DRIVERS:+ $DRIVERS}"
        fi

        # some devices have user-mode drivers (no kernel module, but config)
        # or specialized user-mode setup helpers
        MODPROBE=:
        for MAP in $MAP_USERMAP $HOTPLUG_DIR/usb/*.usermap
        do
            if [ -r $MAP ]; then
                load_drivers usb $MAP "$LABEL"
                FOUND="$FOUND${DRIVERS:+ $DRIVERS}"
            fi
        done

        if [ -z "$FOUND" ]; then
            # mesg "... no modules for $LABEL"
            exit 2
        fi
        ;;

    remove)

        # If hwdown shuts down the device there is nothing more to do
        if [ -x /sbin/hwdown ] ; then
            /sbin/hwdown  ${1:+$1-}devpath-$DEVPATH -o hotplug && exit
        fi

        if [ -x /usr/sbin/updfstab ]; then
            /usr/sbin/updfstab
        fi

        # update current state
        [ "$HWSCAN" ] && $HWSCAN --usb $DEVPATH
        ;;

    *)
        debug_mesg USB $ACTION event not supported
        exit 1
        ;;

esac
