#!/bin/sh
#
# USB-specific hotplug policy agent.
#
# This should handle 2.2.18+, 2.4.*, and 2.5.* 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 [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
#
# This script then adds the variable:
#
#       REMOVER=/var/run/usb/<some string unique to $DEVICE>
#
# This is the path where the script would like to find a remover, if
# the target device needs one. This script is executed on remove if
# it is executable when the remove happens.
#
# 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.)
#
# On systems using Linux 2.4.* kernels, be sure to use the right
# modutils (2.4.2+).  That ensures that hotplugging uses the list
# of modules installed for your kernel, rather than the one that's
# included here for use on systems without MODULE_DEVICE_TABLE
# support.
#
#
# HISTORY:
#
# 20-Nov-2002	some 2.5 updates; handle new 'device' hotplug; turn off
#		'sleep' hack since hcds must all queue control traffic
# 08-Aug-2002	support for multiple usermaps (maxk), minor cleanup
# 18-Jan-2002	fix match algorithm in usb_map_modules()
# 14-Jan-2002	fix work around 2.2 brokeness of $PRODUCT
# 09-Jan-2002	REMOVER for system without usbdevfs
#
# 14-Mar-2001	Cleanup, bitmask the match_flags
# 26-Feb-2001	Cleanup, support comments (Gioele Barabucci)
# 15-Feb-2001	Remove use of "<<" (Adam Richter)
# 23-Jan-2001	Update 2.2 handling; unfortunately there's no "feature
#		test" that can work robustly
# 05-Jan-2001	Quick hack for kernel 2.4.0 and modutils 2.4.1
# 03-Jan-2001	Initial version of "new" hotplug agent, using feedback
#		and contributions from Adam Richter, Ryan VanderBijl,
#		Norbert Preining, Florian Lohoff, David Brownell and
#		others.  To replace the original /etc/usb/policy. (db)
#
# $Id: usb.agent,v 1.43 2004/09/20 22:50:11 kroah Exp $
#

if [ -f /etc/sysconfig/usb ]; then
    . /etc/sysconfig/usb
fi
if [ -f /etc/conf.d/usb ]; then
    . /etc/conf.d/usb
fi

cd /etc/hotplug
. ./hotplug.functions
# DEBUG=yes export DEBUG

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

# used if MAP_CURRENT is missing; for 2.2.x kernels
MAP_DISTMAP=$HOTPLUG_DIR/usb.distmap

#
# used for kernel drivers that don't show up in CURRENT or DISTMAP,
# currently input drivers (joysticks, keyboards, etc).  newer systems
# should use input hotplug events instead.
#
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


# accumulates list of modules we may care about
DRIVERS=""

if [ "$ACTION" = "" ]; then
    mesg Bad USB agent invocation, no action
    exit 1
fi

# starting in kernel 2.5 there are two kinds of USB hotplug events.
# - per-interface; 2.2/2.4 kernels only reported the first one.
#	"new" events have nonzero /sys/$DEVPATH/bInterfaceNumber
# - per-device; "new" events don't have $PRODUCT
SYSFS=/sys
if [ "$PRODUCT" = "" ]; then
    # this is either an error, or we're on a 2.5 system...
    if [ "$DEVPATH" = "" ]; then
	mesg Bad USB agent invocation
	exit 1
    fi

    # sysfs files may already be gone
    if [ $ACTION = 'remove' ]; then
	exit 0
    fi

    # we could be running before usb populated these attributes...
    if [ ! -f $SYSFS/$DEVPATH/bNumConfigurations ]; then
	# FIXME wait till they appear, or N seconds elapse
	sleep 2
    fi

    # this could care about changing the default config, or warning
    # when the user hooked a fast device up so it runs slowly.
    if [ ! -f $SYSFS/$DEVPATH/bNumConfigurations ]; then
	exit 0
    fi
    TMP=$(cat $SYSFS/$DEVPATH/bNumConfigurations)
    if [ $TMP -ne 1 ] && [ "$ACTION" = add ]; then
	mesg Keeping default configuration with $SYSFS/$DEVPATH
    fi

    # NOTE:  it might be good to add an extension hook here rather
    # than ignore these events, but even device-scope tasks such
    # as firmware download can still use the interface-0 event
    # (as they did with 2.2/2.4 hotplug setup scripts).
    exit 0
fi

# we can't "unset IFS" on bash1, so save a copy
DEFAULT_IFS="$IFS"

#
# 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 ()
{
    # work around 2.2.early brokenness
    # munges the usb_bcdDevice such that it is a integer rather
    # than a float: e.g. 1.0 become 0100
    PRODUCT=`echo $PRODUCT | sed -e "s+\.\([0-9]\)$+.\10+" -e "s/\.$/00/" \
                                  -e "s+/\([0-9]\)\.\([0-9][0-9]\)+/0\1\2+" \
			  -e "s+/\([0-9][0-9]\)\.\([0-9][0-9]\)+/\1\2+"`
    set $(echo $PRODUCT | sed -e 's+\([^/]*\)/\([^/]*\)/\(.*\)+\1 \2 \3+')
    usb_idVendor=$((0x$1))
    usb_idProduct=$((0x$2))
    usb_bcdDevice=$((0x$3))

    if [ "$TYPE" != "" ]; then
    	IFS=/
    	set $TYPE ''
	usb_bDeviceClass=$1
        usb_bDeviceSubClass=$2
        usb_bDeviceProtocol=$3
	IFS="$DEFAULT_IFS"
    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
	IFS=/
	set $INTERFACE ''
	usb_bInterfaceClass=$1
	usb_bInterfaceSubClass=$2
    	usb_bInterfaceProtocol=$3
	IFS="$DEFAULT_IFS"
    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 ()
{
    # 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 ;;
	"") 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
}

#
# declare a REMOVER name that the add action can use to create a
# remover, or that the remove action can use to execute a remover.
#
if [ "$DEVPATH" != "" ]; then
  # probably, 2.6.x
  REMOVER=/var/run/usb/$(readlink -f $SYSFS/$DEVPATH | sed -e 's;/;%;g')
elif [ "$DEVICE" != "" ]; then
  # 2.4.x?
  REMOVER=/var/run/usb/$(echo $DEVICE | sed -e 's;/;%;g')
else
  # should not happen?
  REMOVER=/var/run/usb/$(echo "$INTERFACE/$PRODUCT/$TYPE" | sed -e 's;/;%;g')
fi
export REMOVER

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

add)
    # partial workaround for 2.4 uhci/usb-uhci driver problem:  they don't
    # queue control requests, so device drivers can confuse each other if
    # they happen to issue requests at the same time ... it happens easily
    # with slow HID devices and "usbmodules".
    # starting with 2.5 (DEVPATH set), all hcds must queue control traffic.
    if [ "$DEVPATH" = "" ]; then
	sleep 3
    fi

    usb_convert_vars

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

    if [ -e "$REMOVER" ]; then
	rm -f "$REMOVER"
    fi

    # on 2.4 systems, modutils 2.4.2+ maintains MAP_CURRENT
    # ... otherwise we can't rely on it (sigh)
    case "$KERNEL" in
    2.4.*|2.5.*|2.6.*)
	if [ -r $MAP_CURRENT ]; then
	    load_drivers usb $MAP_CURRENT "$LABEL"
	fi;;
    *)
	if [ -r $MAP_DISTMAP ]; then
	    load_drivers usb $MAP_DISTMAP "$LABEL"
	fi;;
    esac
    if [ "$DRIVERS" != "" ]; then
	FOUND=true
    fi

    # cope with special driver module configurations
    # (mostly HID devices, until we have an input.agent)
    # not needed on 2.6 - they are loaded by hotplug
    case "$KERNEL" in
	2.6.* )
	    : nothing
	;;
	* )
	    if [ -r $MAP_HANDMAP ]; then
		load_drivers usb $MAP_HANDMAP "$LABEL"
		if [ "$DRIVERS" != "" ]; then
		    FOUND=true
		fi
	    fi
	;;
    esac

    # 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"
	    if [ "$DRIVERS" != "" ]; then
		FOUND=true
	    fi
	fi
    done

    if [ "$FOUND" = "false" ]; then
	debug_mesg "... no modules for $LABEL"
	exit 2
    fi
    ;;

remove)
    if [ -x $REMOVER ]; then
	$REMOVER
    fi
    rm -f $REMOVER

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

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

esac
