#!/sbin/bash # # The probulator: Handles kernel hotplug and unplug events # # $Id: probulator,v 1.12 2004/08/20 20:05:33 das-cvs Exp $ # # {{{ Mozilla Public License (MPL) version 1.1 # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is a script to handle kernel hotplug events. # # The Initial Developer of the Original Code is # David Shea . # Portions created by the Initial Developer are Copyright (C) 2004 # the Initial Developer. All Rights Reserved. # # Contributor(s): # David L. Cantrell Jr. # # ***** END LICENSE BLOCK ***** # }}} # location to store files to remember which device uses which module STATEDIR=/var/run/probulator # Pass directory for lock files on $1, file to lock on $2 # Creates a lock file $1/.$2.$$ mutex_lock() { max_tries=5 tries=0 while [ $tries -lt $max_tries ]; do /sbin/touch "$1/.$2.$$" if [ "$(echo $1/.$2.*)" = "$1/.$2.$$" ]; then return 0 fi /sbin/rm "$1/.$2.$$" # using $RANDOM to try to reduce the risk of deadlock /sbin/sleep $(($RANDOM % 5)) tries=$(($tries + 1)) done return 1 } mutex_unlock() { /sbin/rm "$1/.$2.$$" } # pass in a list of modules to load as arguments load_modules() { # load the modules before doing potentially slow refcount updates for module in $* ; do # first line is header, after that module names are first column loaded="$(/sbin/lsmod | /sbin/sed 1d | \ /sbin/grep "^${module}"'[[:space:]]')" if [ -z "$loaded" ]; then if /sbin/modprobe "$module"; then modlist="$modlist $module" fi else modlist="$modlist $module" eval $(echo ${module} | /sbin/sed 's/-/_/g')_loaded=1 fi done echo $modlist > "$devfile" # update reference counts for module in $modlist ; do modfile="$STATEDIR/$module" if mutex_lock "$STATEDIR" $module ; then if [ -f "$modfile" ]; then read modnum < "$modfile" # Creating a new count file, but there's already a use # from outside of the probulator, so start at 2 so # it's never unloaded elif [ ! -z "$(eval echo \ \$$(echo ${module} | /sbin/sed 's/-/_/g')_loaded)" ]; then modnum=1 else modnum=0 fi echo $(($modnum+1)) > "$modfile" mutex_unlock "$STATEDIR" $module fi done } # set $devfile before calling, takes no arguments remove_modules() { if [ -f "$devfile" ]; then read modlist < "$devfile" for module in $modlist ; do modfile="$STATEDIR/$module" if mutex_lock "$STATEDIR" $module ; then if [ -f "$modfile" ]; then read modnum < "$modfile" else modnum=1 fi modnum=$(($modnum - 1)) if [ $modnum -eq 0 ]; then /sbin/modprobe -r "$module" /sbin/rm "$modfile" else echo "$modnum" > "$modfile" fi mutex_unlock "$STATEDIR" $module fi done /sbin/rm "$devfile" fi } # make the state directory if it's not there if [ ! -d "$STATEDIR" ] ; then /sbin/mkdir -p "$STATEDIR" fi case "$1" in usb) # replaces /'s with %'s to make the path into a legal filename filename="$(echo "$DEVICE" | /sbin/sed 's!/!%!g')" devfile="${STATEDIR}/${filename}" if ! mutex_lock "$STATEDIR" "$filename" ; then exit 1 fi case "$ACTION" in add) if [ -f "$devfile" ]; then mutex_unlock "$STATEDIR" "$filename" exit 0 fi if [ -e "$DEVICE" ] ; then load_modules $(/sbin/usbmodules --device "$DEVICE") fi ;; remove) remove_modules ;; esac mutex_unlock "$STATEDIR" "$filename" ;; pci) filename="pci_${PCI_SLOT_NAME}" devfile="${STATEDIR}/${filename}" if ! mutex_lock "$STATEDIR" "$filename" ; then exit 1 fi case "$ACTION" in add) if [ -f "$devfile" ]; then mutex_unlock "$STATEDIR" "$filename" exit 0 fi pcimap=/boot/modules/$(uname -r)/modules.pcimap # convert hex numbers to the lowercase used in $pcimap and # split the : in PCI_ID into separate variables, eval "$(echo "$PCI_ID" | /sbin/sed -e 'y/ABCDEF/abcdef/'\ -e 's/\([^:]*\):\([^:]*\)/vendor=\1 ; device=\2/')" # if [ -r "$pcimap" ]; then # lines are of the form " ..." # look for the line with our vendor and device, take modname # The kernel people play a bit fast-and-loose with leading 0's, # hence the need for sed and horribleness vendor="$(echo $vendor | /sbin/sed 's/^0*//')" device="$(echo $device | /sbin/sed 's/^0*//')" load_modules "$(/sbin/sed -n \ "s/^\([^#[:space:]][^[:space:]]*\)[[:space:]]*\ 0x0*${vendor}[[:space:]]*0x0*${device}[[:space:]].*/\1/p" "$pcimap")" fi ;; remove) remove_modules ;; esac mutex_unlock "$STATEDIR" "$filename" ;; ieee1394) # Linux currently supports 5 Firewire things, which is nice. It makes # this module loading thing easy. All we do here is match the vendor, # specifier, and version and then load that module. --dc filename="ieee1394_${GUID}" devfile="${STATEDIR}/${filename}" if ! mutex_lock "$STATEDIR" "$filename" ; then exit 1 fi case "$ACTION" in add) if [ -f "$devfile" ]; then mutex_unlock "$STATEDIR" "$filename" exit 0 fi ieee1394map=/boot/modules/$(uname -r)/modules.ieee1394map if [ -r "${ieee1394map}" ]; then # lines are # # do the same thing as the PCI thing above, but different fields # vendor ID might not actually mean anything; match all 0's, too VENDOR_ID="$(echo $VENDOR_ID | /sbin/sed 's/^0*//')" SPECIFIER_ID="$(echo $SPECIFIER_ID | /sbin/sed 's/^0*//')" VERSION="$(echo $VERSION | /sbin/sed 's/^0*//')" load_modules "$(/sbin/sed -n \ "s/^\([^#[:space:]][^[:space:]]*\)[[:space:]]*\ [^[:space:]]*[[:space:]]*0x0*\(${VENDOR_ID}\)\{0,1\}[[:space:]]*\ [^[:space:]]*[[:space:]]*0x0*${SPECIFIER_ID}[[:space:]]*\ 0x0*${VERSION}[[:space:]]*$/\1/p" "$ieee1394map")" fi ;; remove) remove_modules ;; esac mutex_unlock "$STATEDIR" "$filename" ;; firmware) # Not doing any locking here, since no persistent state to lock on, # and there's no firmware remove event, so we can't make one up. # dear God I hope that two firmware events don't get sent for the # same card firmware="/usr/share/kernel/firmware/$FIRMWARE" case "$ACTION" in add) # Firmware gets echoed into a file in /proc or /sysfs, depending # on the kernel version case "$(uname -r)" in 2.4.*) FW_DEST="/proc/$DEVPATH" ;; 2.6.*) FW_DEST="/sys/$DEVPATH" ;; *) exit 1 ;; esac # The firmware loader is pretty stupid. The kernel tells us where # to put it, but we have to let it know when we're starting # and when we're done if [ -f "$firmware" ]; then echo 1 > "$FW_DEST"/loading cat "$firmware" > "$FW_DEST"/data echo 0 > "$FW_DEST"/loading else echo -1 > "$FW_DEST"/loading fi ;; esac ;; *) # unsupported exit 1 ;; esac exit 0