#!/bin/bash
# $Id: usb.rc 303 2005-01-20 08:59:56Z hare $
# vim: syntax=sh
#
# usb.rc	This brings the USB subsystem up and down safely.
#
# Best invoked via /etc/init.d/hotplug or equivalent, with
# writable /tmp, /usr mounted, and syslogging active.
#
# Bus management is basically unrelated to runlevel changes; it
# relates to physical topology, including possibly hotpluggable
# busses (USB, Cardbus) or controllers.  If a bus is physically
# present, it should normally be available.
# 
# USB-dependant systems (iMacs, "legacy free" x86 systems, and so on)
# should statically link USB keyboard support into the kernel (USB core,
# EHCI/OHCI/UHCI/..., hid, input, keybdev; and likely mousedev) so the
# system console can't be removed by accident.


PATH=/sbin:/bin:/usr/sbin:/usr/bin

cd /etc/hotplug

if [ -f hotplug.functions ]; then
	. hotplug.functions
fi


#
# "COLD PLUG" ... recovery from partial USB init that may have happened
# before the OS could really handle hotplug, perhaps because /sbin or
# $HOTPLUG_DIR wasn't available or /tmp wasn't writable.  When/if the
# /sbin/hotplug program is invoked then, hotplug event notifications
# get dropped.  To make up for such "cold boot" errors, we synthesize
# all the hotplug events we expect to have seen already.  They can be
# out of order, and some might be duplicates.
#
# Note that on 2.5 the init filesystem may have loaded some of the more
# essential usb drivers (maybe usb-storage for a boot disk, and hid),
# but we may still need to load less important ones or invoke setup
# scripts that couldn't run earlier.
#
usb_boot_events_nosysfs ()
{
	#
	# don't expect usbmodules to exist!!  and remove the
	# dependency (below) on usbfs to decide whether we should
	# be synthesizing coldplug events.
	#

	# synthesize hotplug events if we can 
	# we need (non-bash) programs to parse descriptors.
	LISTER="`which usbmodules`"
	if [ "$LISTER" = "" -o ! -x "$LISTER" -o "`echo /proc/bus/usb/*/*`" = "/proc/bus/usb/*/*" ]; then
		echo $"** can't synthesize root hub events"
		return
	fi

	# these notifications will be handled by usbmodules
	# NOTE: we're not providing a full set of hotplug
	# parameters for USB.  that's why "usbmodules" is a
	# requirement: it reads usbfs to get the others.
	# (it's included in usbutils 0.8 and later)
	#
	# FIXME usbmodules, or something, should set real
	# PRODUCT and DEVICE strings so /etc/hotplug/usb/*
	# scripts can rely on them ...
	for DEVICE in /proc/bus/usb/*/*
	do
		echo -n .
		$ENV sh -c "cd / ; ACTION=add PRODUCT=0/0/0 DEVFS=/proc/bus/usb DEVICE=$DEVICE /etc/hotplug/usb.agent >/dev/null 2>&1 &"
	done
}

usb_boot_events_sysfs ()
{
	#
	# /sys/bus/usb/devices/* gives all of the info we need.
	# Interface hotplug events come from the "*:*" files,
	# and device events (do them first) come from the others. 
	#
	cd /sys/bus/usb/devices
	for i in *
	do
		if [ ! -d "$i" ] ; then return ; fi
		MYINTERFACE=
		MYDEVICE=
		case "$i" in
			*:*)
			# this is an interface
			MYDEVICE="`echo $i | sed 's@:.*@@'`"
			if test ! -d "$MYDEVICE" ; then
				continue
			fi
			# all sysfs files contain hex data
			# hex
			PRODUCT="0/0/0"
			# decimal
			INTERFACE="0/0/0"
			# decimal
			TYPE="0/0/0"
			if test -f "$i/bInterfaceClass" ; then
			read bInterfaceClass <  $i/bInterfaceClass
			read bInterfaceSubClass <  $i/bInterfaceSubClass
			read bInterfaceProtocol <  $i/bInterfaceProtocol
			bInterfaceClass=$((0x$bInterfaceClass))
			bInterfaceSubClass=$((0x$bInterfaceSubClass))
			bInterfaceProtocol=$((0x$bInterfaceProtocol))
			INTERFACE="$bInterfaceClass/$bInterfaceSubClass/$bInterfaceProtocol"
			fi
			if test -f "$MYDEVICE/idVendor" ; then
			read idVendor <  $MYDEVICE/idVendor
			read idProduct <  $MYDEVICE/idProduct
			read bcdDevice <  $MYDEVICE/bcdDevice
			PRODUCT="`printf %x/%x/%x 0x$idVendor 0x$idProduct 0x$bcdDevice`"
			fi
			if test -f "$MYDEVICE/bDeviceClass" ; then
			read bDeviceClass <  $MYDEVICE/bDeviceClass
			read bDeviceSubClass <  $MYDEVICE/bDeviceSubClass
			read bDeviceProtocol <  $MYDEVICE/bDeviceProtocol
			bDeviceClass=$((0x$bDeviceClass))
			bDeviceSubClass=$((0x$bDeviceSubClass))
			bDeviceProtocol=$((0x$bDeviceProtocol))
			TYPE="$bDeviceClass/$bDeviceSubClass/$bDeviceProtocol"
			fi
			DEVPATH="`cd $i ; /bin/pwd | sed -e 's@^/sys@@'`"
			echo -n "*"
			$ENV sh -c "cd / ; DEVPATH=$DEVPATH ACTION=add INTERFACE=$INTERFACE PRODUCT=$PRODUCT TYPE=$TYPE $HOTPLUG_BIN usb > /dev/null 2>&1 &" 
			;;
			*)
			# this is a device
			DEVPATH="`cd $i ; /bin/pwd | sed -e 's@^/sys@@'`"
			echo -n .
			$ENV sh -c "cd / ; DEVPATH=$DEVPATH ACTION=add $HOTPLUG_BIN usb > /dev/null 2>&1 &" 
			;;
		esac
	done
}
usb_boot_events ()
{
	local ENV
	if [ -x /usr/bin/env ] ; then
		ENV='env -i - '
	else
		ENV=
	fi
	if [ -d /sys/bus/usb/devices ] ; then
		usb_boot_events_sysfs
	else
		usb_boot_events_nosysfs
	fi
}

maybe_start_usb ()
{
    local COUNT SYNTHESIZE
    COUNT=0

    # if USB is partially initted then synthesize "cold plug" events. the
    # kernel probably dropped many "hot plug" events, and those it didn't
    # drop likely couldn't trigger all the setup actions (kicking daemons,
    # dropping config records, and so on).

    # if it's not initted at all (no hcds loaded) no synthesized events
    # will be needed, we'll see real ones.  knowing that there are no
    # hcds available through version-portable logic is a nightmare, so
    # assume we synthesize unless "usbfs" is clearly not initted (which
    # we currently need when synthesizing, anyway).

    SYNTHESIZE=true
    if [ ! -d /proc/bus/usb ]; then
	# USB is modular.
	# pci.rc is supposed to load the hostcontroller drivers,
	# this will trigger all events
	# just load the core and be done with it
	modprobe -q usbcore >/dev/null 2>&1
	SYNTHESIZE=false
    fi

    if [ -d /proc/bus/usb ]; then
	# if it's not mounted, try to mount it
	COUNT="`ls /proc/bus/usb | grep -c '.*'`"
	if [ $COUNT -lt 2 ]; then
	    if grep -q "[	 ]/proc/bus/usb[	 ]" /etc/fstab ; then
		mount /proc/bus/usb
	    else
		# NOTE: name was changed to "usbfs" from "usbdevfs"
		# NOTE: some versions don't create empty 'devices' files
		mount -t usbfs usbfs /proc/bus/usb
	    fi
	fi
    fi

    # hotplug events didn't fire during booting;
    # cope with devices that enumerated earlier
    # and may not have been fully configured.
    if [ "$SYNTHESIZE" = true ]; then
    	usb_boot_events
    fi

    # we did everything we could ...
    return 0
}

maybe_stop_usb ()
{
    # call this multiple times if you had to take down components of the
    # USB subsystem by hand; it cleans up whatever can
    # be cleaned up, letting the system quiesce further.

    # NOTE:  this list of "all USB modules" is unfortunate, but it seems
    # inevitable until modutils supports the notion of drivers with use
    # counts of zero that shouldn't be removed until after their device
    # gets removed.  Maybe in 2.5 ... of necessity, the list is partial.

    # disconnect all controllers we can, and kernel drivers
    # HCDs first, so most drivers reduce their use counts.
    # IMPORTANT: do the unload only during 'rchotplug restart'
    if [ "$1" = "unload_drivers" ] ; then
    rmmod ehci-hcd >/dev/null 2>&1
    rmmod ohci-hcd >/dev/null 2>&1
    rmmod uhci-hcd >/dev/null 2>&1
    rmmod usb-ohci >/dev/null 2>&1
    rmmod usb-uhci >/dev/null 2>&1
    rmmod     uhci >/dev/null 2>&1
    fi

    # user mode code may keep usbfs busy for a while yet ...

    # OK, usbcore won't actually be removed unless there happen to be
    # no USB drivers loaded, and usbfs isn't mounted.  let's force
    # removal of autocleanable modules before trying to rmmod usbcore
    rmmod -as
# note: module-init-tools 0.8a doesn't handle "-as" flags

    # Now let's workaround the fact that some USB modules never increase
    # their module use counts, so that "rmmod -a" won't unload them.
    # (And we can't use "modprobe --autoclean" anyway.)
    rmmod acm              >/dev/null 2>&1
    rmmod audio            >/dev/null 2>&1
    rmmod auerswald        >/dev/null 2>&1
    rmmod belkin_sa        >/dev/null 2>&1
    rmmod bluetooth        >/dev/null 2>&1
    rmmod catc             >/dev/null 2>&1
    rmmod CDCEther         >/dev/null 2>&1
    rmmod cpia_usb         >/dev/null 2>&1
    rmmod cyberjack        >/dev/null 2>&1
    rmmod dabusb           >/dev/null 2>&1
    rmmod dc2xx            >/dev/null 2>&1
    rmmod digi_acceleport  >/dev/null 2>&1
    rmmod dsbr100          >/dev/null 2>&1
    rmmod emi26            >/dev/null 2>&1
    rmmod empeg            >/dev/null 2>&1
    rmmod ftdi_sio         >/dev/null 2>&1
    rmmod hci_usb          >/dev/null 2>&1
    if [ "$1" = "unload_drivers" ] ; then
    rmmod hid              >/dev/null 2>&1
    fi
    rmmod hpusbscsi        >/dev/null 2>&1
    rmmod ibmcam           >/dev/null 2>&1
    rmmod iforce           >/dev/null 2>&1
    rmmod io_edgeport      >/dev/null 2>&1
    rmmod ipaq             >/dev/null 2>&1
    rmmod ir-usb           >/dev/null 2>&1
    rmmod irda-usb         >/dev/null 2>&1
    rmmod kaweth           >/dev/null 2>&1
    rmmod keyspan          >/dev/null 2>&1
    rmmod keyspan_pda      >/dev/null 2>&1
    rmmod kl5kusb105       >/dev/null 2>&1
    rmmod mct_u232         >/dev/null 2>&1
    rmmod mdc800           >/dev/null 2>&1
    rmmod microtek         >/dev/null 2>&1
    rmmod omninet          >/dev/null 2>&1
    rmmod ov511            >/dev/null 2>&1
    rmmod pegasus          >/dev/null 2>&1
    rmmod pl2303           >/dev/null 2>&1
    rmmod printer          >/dev/null 2>&1
    rmmod pwc pwcx         >/dev/null 2>&1
    rmmod rio500           >/dev/null 2>&1
    rmmod rtl8150          >/dev/null 2>&1
    rmmod scanner          >/dev/null 2>&1
    rmmod se401            >/dev/null 2>&1
    rmmod stv680           >/dev/null 2>&1
    rmmod usbkbd           >/dev/null 2>&1
    rmmod usbmouse         >/dev/null 2>&1
    rmmod usbnet           >/dev/null 2>&1
    rmmod usbtest          >/dev/null 2>&1
    rmmod usb-storage      >/dev/null 2>&1
    rmmod uss720           >/dev/null 2>&1
    rmmod vicam            >/dev/null 2>&1
    rmmod visor            >/dev/null 2>&1
    rmmod wacom            >/dev/null 2>&1
    rmmod whiteheat        >/dev/null 2>&1

    # remove the helper modules that some usb modules need
    rmmod usbserial        >/dev/null 2>&1
    rmmod usbvideo         >/dev/null 2>&1

    # ok, hope that user mode drivers/managers closed their fds.
    umount /proc/bus/usb >/dev/null 2>&1

    rmmod usbcore >/dev/null 2>&1

    # we did everything we could ...
    return 0;
}

# See how we were called.
case "$1" in
  start)
	maybe_start_usb
        ;;
  stop)
	#	maybe_stop_usb $2
        ;;
  status)
	;;
  status-verbose)
	echo $"USB Status for kernel: " `uname -srm`
	echo ''

	if [ -f /proc/bus/usb/devices ]; then
	    # as noted above:  this fails on older kernels,
	    # where usbfs created files differently.
	    COUNT=`ls /proc/bus/usb | grep -c '.*'`
	    if [ $COUNT -ge 2 ]; then
		COUNT=`expr $COUNT - 2`
		echo $"USB up; bus count is $COUNT"
		grep "^[TPSI]:" /proc/bus/usb/devices
	    else
		echo $"usbfs partially up; no busses"
	    fi
	    echo ''

	    echo $"USB Drivers Loaded: "
	    if [ -f /proc/bus/usb/drivers ]; then
		cat /proc/bus/usb/drivers
	    fi
	    if [ -d /sys/bus/usb ]; then
		ls -1 /sys/bus/usb/drivers
	    fi
	else
	    echo $"usbfs is unavailable. "
	    if [ -f /proc/modules ] && fgrep -q usbcore /proc/modules; then
		echo $"USB module is loaded. "
	    else
		echo $"USB may be statically linked. "
	    fi
	    echo $"If khubd is running, that shows USB is available."
	fi
	echo ''

	if [ -f /proc/sys/kernel/hotplug ]; then
	    echo $"khubd/hotplug thread: "
	else
	    echo $"khubd thread:"
	fi
	ps -l | grep -m1 '.*'
	ps -Al | egrep 'khubd' | grep -v grep
	echo ''

	lsmod
	echo ''

	# /proc/devices too? "usb", "input", and others ...

	;;
  restart)
	# always invoke by absolute path, else PATH=$PATH:
	$0 stop unload_drivers && $0 start
	;;
  *)
        echo $"Usage: $0 {start|stop|status|restart}"
        exit 1
esac
