#!/bin/bash
#
# /sbin/hwup
#
# Configuring hardware (Preliminary version)
# $Id: hwup 1465 2006-03-20 20:00:13Z zoz $
#

# /usr/bin/env
# set -x  -v
# echo --  --------------------------------------------------------------------

usage () {
	echo "Usage: hw{up,down,status} [<config>] <hwdesc> [-o <options>]"
	echo "Options are:"
	echo "    auto     : we were called from an automated process"
	echo "    hotplug  : like auto, special for hotplug (udev)"
	echo "    fast     : skip getcfg, works not for all subsystems"
	exit $R_USAGE
}

get_config_fast() {
	local DP
	set -- $(IFS="-"; echo $HWDESC)
	if [ "$2" = "devpath" ]; then
		HWD_BUSNAME=$1
		shift
	fi
	if [ "$1" = "devpath" ]; then
		HWD_BUSID=${2##*/}
		HWD_DEVICEPATH=${SYSFS}${2#${SYSFS}}
		DP="`cd -P $HWD_DEVICEPATH 2>/dev/null &&  pwd`"
		HWD_DEVICEPATH=${DP:-$HWD_DEVICEPATH}
		for cfg in hwcfg-*bus-${HWD_BUSNAME}-${HWD_BUSID}; do
		    if test -f $cfg; then
			HWD_CONFIG=${cfg##hwcfg-}
		    fi
		done 
		return 0
	else
		return 1
	fi
}

get_config_getcfg() {
	eval `/sbin/getcfg -d . -f hwcfg- -- $HWDESC 2>/dev/null`
	# This is a workaround for getcfg that does not get a devicepath from a
	# buspath or device link. 
	local devpath
	if [ -z "$HWD_DEVICEPATH" -a -n "$HWD_DEVPATH" ] ; then
		devpath=`cd -P ${SYSFS}${HWD_DEVPATH#$SYSFS}; pwd`
		if [ -d "$devpath" ] ; then
			eval `/sbin/getcfg -d . -f hwcfg- -- devpath-$devpath`
		fi
	fi
	# Normally we are interested only in the last bus. (It is not of
	# interest that below a scsi bus is a pci bus. Otherwise use
	# HWD_BUSNAME_<n>, HWD_BUSID_<n>.)
	if [ -n "$HWD_BUS_N" -a "$HWD_BUS_N" -gt 0 ] ; then
		eval export HWD_BUSNAME=\$HWD_BUSNAME_$((HWD_BUS_N-1))
		eval export HWD_BUSID=\$HWD_BUSID_$((HWD_BUS_N-1))
	fi
	HWD_CONFIG=$HWD_CONFIG_0
}

is_known_subsystem() {
	case "$1" in
		ccw)              return 0 ;;
		ccwgroup)         return 0 ;;
		iucv)             return 0 ;;
		ide)              return 0 ;;
		ieee1394)         return 0 ;;
		input)            return 0 ;;
		macio)            return 0 ;;
		pci)              return 0 ;;
		pci_express)      return 0 ;;
		pcmcia)           return 0 ;;
		pcmcia_socket)    return 0 ;;
		pnp)              return 0 ;;
		scsi)             return 0 ;;
		scsi_host)        return 0 ;;
		usb)              return 0 ;;
		vio)              return 0 ;;
		static)           return 0 ;; # for static device configs
		*)                return 1 ;;
	esac
}

get_subsystem() {
	if is_known_subsystem $SUBSYSTEM; then
		echo $SUBSYSTEM
		return 0
	fi
	set -- $(IFS="-"; echo $HWDESC)
	if is_known_subsystem $1 ; then
		echo $1
		return 0;
	fi
	test "$2" == bus && shift
	if [ "$1" == bus ] && is_known_subsystem $2; then
		echo $2
		return 0;
	fi
	local devpath
	if [ -L ${SYSFS}${DEVPATH}/bus ] ; then
		devpath=${SYSFS}${DEVPATH}
	else
		test "$2" == devpath && shift
		if [ "$1" == devpath ] ; then
			shift
			devpath="`IFS=-; echo "$*"`"
			devpath=${devpath#$SYSFS}
		fi
	fi
	if [ -L ${SYSFS}${devpath}/bus ] ; then
		local bus
		bus="`cd -P ${SYSFS}${devpath}/bus; pwd`"
		bus=${bus##*/}
		if is_known_subsystem $bus; then
			echo $bus
			return 0
		fi
	fi
	local classpath=${devpath#/class/}
	if [ "$devpath" != "$classpath" ] ; then
		classpath=${classpath%%/*}
		if is_known_subsystem $classpath; then
			echo $classpath
			return 0
		fi
	fi
	if [ -n "$CONFIG" ] ; then
		if is_known_subsystem ${CONFIG%%-*}; then
			echo ${CONFIG%%-*}
			return 0
		fi
	fi
	return 1
}

# This checks for information that should be available for all kind of
# subsystems. If will not fail if some information is missing since the needs
# of each subsystem differ to much. That may change in future.
# The current implementation of this function is evil and ugly. Lets get rid of
# getcfg soon and write a proper function. But for SL10.0 its to late. (zoz)
get_basic_information() {
	if [ "$FAST" = yes ] ; then
		get_config_fast
	else	
		get_config_getcfg
	fi
	# We need a nonempty HWD_DEVICEPATH.
	if [ -z "$HWD_DEVICEPATH" -a -n "$DEVPATH" ] ; then
		HWD_DEVICEPATH=$SYSFS$DEVPATH
	fi
	# HWD_DEVTYPE is currently only used to call special scripts. So we
	# might eliminate it and use $SUBSYSTEM instead.
	if [ -z "$HWD_DEVTYPE" ] ; then
		HWD_DEVTYPE=$SUBSYSTEM
	fi
	# We might check the following variables:
	# HWD_BUSNAME
	# HWD_BUSID
	# HWD_ID         # in hwup-iucv
}

modprobe_modalias() {
	local retval
	if [ -L "$HWD_DEVICEPATH/driver" ] ; then
		info_mesg "Device already bound to a driver"
		return 0
	fi
	if [ -z "$MODALIAS" ]; then
		MODALIAS="`cat $HWD_DEVICEPATH/modalias 2>/dev/null`"
	fi
	if [ -z "$MODALIAS" ]; then
		err_mesg "No module alias available"
		return 1
	fi
	info_mesg "executing modprobe $MODALIAS"
	if [ "$LOAD_UNSUPPORTED_MODULES_AUTOMATICALLY" != yes ] ; then
		local MODPROBE_OPTS=--skip-unsupported
	fi
	if [ "$LOG_LEVEL" -lt 6 ] ; then
		modprobe -v $MODPROBE_OPTS $MODALIAS
		retval=$?
	else
		local message retval
		message="`modprobe -v $MODPROBE_OPTS $MODALIAS 2>&1`"
		retval=$?
		if [ -n "$message" ] ; then
			mesg "$message"
		elif [ "$retval" == 0 ] ; then
			info_mesg "modules already loaded:" \
			          `modprobe --show-depends $MODALIAS \
                        	   | sed 's=^.*/\(.*\).ko=\1='`
		fi
	fi
	if [  ! -L "$HWD_DEVICEPATH/driver" \
	     -a -n "$HWD_BUSNAME" -a -n "$HWD_BUSID" ] ; then
		info_mesg "Device still not bound to a driver"
		if [ -e "$DRIVER_FILE" ]; then
			if [ -z "$DRIVER" ]; then
				. $DRIVER_FILE 
			fi
			rm $DRIVER_FILE
		fi
		if [ -n "$DRIVER" -a "$DRIVER" != skip ] ; then
			info_mesg "Binding device to driver '$DRIVER'"
			SYSFS_DRIVER_PATH="/sys/bus/$HWD_BUSNAME/drivers/$DRIVER"
			echo -n $HWD_BUSID > $SYSFS_DRIVER_PATH/bind
			test "$DRIVER" == ipw3945 && ipw3945_start_daemon 
		fi
	fi
	return $retval
}

# Driver ipw3945 needs a userspace daemon that finishes initialisation. Without
# that daemon we don't get network interfaces.
# ipw3945d is started via udev as soon as module ipw3945 is loaded. But if
# someone (e.g. yast) calls hwdown and hwup the device will be unbound from the
# driver in hwdown and then bound again in hwup. Unfortunately there are no
# udev events if a device gets bound by a driver. So we do it ourself after
# binding it. 
ipw3945_start_daemon() {
	info_mesg "Starting daemon ipw3945d"
	/lib/udev/ipw3945d.sh
}

R_INTERNAL=1      # internal error, e.g. no config or missing scripts
cd /etc/sysconfig/hardware || exit $R_INTERNAL
test -f ./config && . ./config
test -f scripts/functions && . scripts/functions || exit $R_INTERNAL

######################################################################
# Commandline parsing
#
# hw{up,down,status} [<config>] <hwdesc> [-o <options>]
SCRIPTNAME=${0##*/}
info_mesg $*
HWDESC=$1
case "$HWDESC" in ""|-h|*help*) usage; esac
shift
if [ -n "$1" -a "$1" != "-o" ] ; then
	CONFIG=$HWDESC
	HWDESC=$1
fi
shift
test "$1" = "-o" && shift
OPTIONS=$@
MODE=manual
HOTPLUG=no
while [ $# -gt 0 ]; do
	case $1 in
		auto)    MODE=auto ;;
		hotplug) MODE=auto
		         HOTPLUG=yes ;;
		fast)    FAST=yes;;
		*)       info_mesg "unknown option $1 ignored" ;;
	esac
	shift
done


######################################################################
# Determine subsystem
#
SUBSYSTEM=`get_subsystem`
if [ $? != 0 ] ; then
	err_mesg "Cannot handle subsystem '$SUBSYSTEM'"
	exit $R_USAGE
fi
test -r ./scripts/functions.$SUBSYSTEM && . ./scripts/functions.$SUBSYSTEM


######################################################################
# Now check if basic information is available. At first that info common to all
# subsystems. Then we can call the specific helper for the subsystem.
get_basic_information
if [ $? != 0 ] ; then
	err_mesg "Cannot get basic information"
	return $R_USAGE # FIXME add R_NO_INFO to functions.common
fi
if [ "`type -t get_${SUBSYSTEM}_information`" == function ] ; then
	get_${SUBSYSTEM}_information
	if [ $? != 0 ] ; then
		err_mesg "Cannot get specific information for '$SUBSYSTEM'"
		return $R_USAGE # FIXME add R_NO_INFO to functions.common
	fi
fi
DRIVER_FILE=$RUN_FILES_BASE/driver-$HWD_BUSNAME-$HWD_BUSID


######################################################################
# Get the right configuration file
if [ -z "$CONFIG" -a -n "$HWD_CONFIG" ] ; then
	CONFIG="$HWD_CONFIG"
fi
# This should go to get_static_information
if [ -z "$CONFIG" ] ; then
	case ${HWDESC%%-*} in
		static|boot) CONFIG=$HWDESC;;
	esac
fi
info_mesg "HWDESC='$HWDESC'"
info_mesg "CONFIG='$CONFIG'"


######################################################################
# Now source the configuration file
#
# First remove all variables starting with MODULE because there are variables
# starting with MODULE and unknown suffix in the config file. Otherwise
# environment variables would irritate the code that looks for all MODULE*
# variables.
unset ${!MODULE*}
if [ -n "$CONFIG" -a -r "./hwcfg-$CONFIG" ] ; then
	. "./hwcfg-$CONFIG"
fi
if [ "$LOG_LEVEL" -ge 6 ] ; then
	info_mesg ------------------------------------------------------------
	for var in MODE HOTPLUG SUBSYSTEM MODALIAS \
	           ${!HWD_*} ${!MODULE*} ${!STARTMODE*} \
	           ${!SCRIPTUP_*} ${!SCRIPTDOWN_*} ${!PRE_*} ${!POST_*}; do
		info_mesg ${var}=${!var}
	done
	if [ "`type -t show_${SUBSYSTEM}_information`" == function ] ; then
		show_${SUBSYSTEM}_information
	fi
	info_mesg ------------------------------------------------------------
fi


######################################################################
# What shell we do if there is no configuration data?
# - fail
# - get it automatically
# - ask the user
if [ "$SCRIPTNAME" = hwup -a \
     \( -z "$CONFIG" -o ! -r "hwcfg-$CONFIG" -o -n "$NODATA" \) ] ; then
#	test "$HOTPLUG" != yes && \
#		err_mesg "No configuration found for $HWDESC"
#	exit $R_NOCONFIG
	if [ "`type -t pre_init_${SUBSYSTEM}`" == function ] ; then
		info_mesg calling pre_init_${SUBSYSTEM}
		pre_init_${SUBSYSTEM}
	fi
	modprobe_modalias
	if [ "`type -t post_init_${SUBSYSTEM}`" == function ] ; then
		info_mesg calling post_init_${SUBSYSTEM}
		post_init_${SUBSYSTEM}
	fi
	exit 0
fi


######################################################################
# Check if we are supposed to initialize the device
#
# empty $STARTMODE means 'auto'
case "$STARTMODE" in
	off)
		mesg "$SCRIPTNAME: used configuration has STARTMODE=$STARTMODE."
		exit 0;
		;;
	manual)
		if [ "$MODE" != manual ] ; then
			mesg "$SCRIPTNAME: used configuration has" \
			     "STARTMODE=$STARTMODE, but we are called '$MODE'."
			exit 0;
		fi
		;;
esac

######################################################################
# execute individual prestart or predown scripts if available
#
if [ "$SCRIPTNAME" = hwup ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like PRE_UP_SCRIPT="~root/bin/foo"
	for PUS in `eval echo $PRE_UP_SCRIPT scripts/$PRE_UP_SCRIPT`; do
		if [ -x "$PUS" -a ! -d "$PUS" ] ; then
			info_mesg "executing additional start script $PUS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PUS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi
if [ "$SCRIPTNAME" = hwdown ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like PRE_DOWN_SCRIPT="~root/bin/foo"
	for PDS in `eval echo $PRE_DOWN_SCRIPT scripts/$PRE_DOWN_SCRIPT`; do
		if [ -x "$PDS" -a ! -d "$PDS" ] ; then
			info_mesg "executing additional stop script $PDS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PDS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi

######################################################################
# Call a specialized down script, depending on the event
#
# have a look at 'Call a specialized down script' above
if [ "$SCRIPTNAME" = hwdown ]; then
	# Call an event-specific script if one exists
	if [ "$HWD_DEVTYPE" ]; then
		SCRIPT_DOWN=`eval echo \\$SCRIPTDOWN_$HWD_DEVTYPE`
	fi
	# Call a generic script if no event-specific exists
	if [ -z "$SCRIPT_DOWN" ]; then
		SCRIPT_DOWN=`eval echo \\$SCRIPTDOWN`
	fi
	if test -n "$SCRIPT_DOWN" && test -x "./scripts/$SCRIPT_DOWN" ; then
		info_mesg "Calling scripts/$SCRIPT_DOWN '$CONFIG'" \
		          "$HWDESC ${OPTIONS:+-o $OPTIONS}"
		./scripts/$SCRIPT_DOWN "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
	fi
fi


######################################################################
# Call subsystem specific pre_intialisation function if there is one
#
if [ "`type -t pre_init_${SUBSYSTEM}`" == function ] ; then
	info_mesg calling pre_init_${SUBSYSTEM}
	pre_init_${SUBSYSTEM}
fi


######################################################################
# Load all modules
#
declare -i M=0 N=0
for MODVAR in ${!MODULE*}; do
	: MODVAR=$MODVAR
	# There is a silly design bug: $MODULE_OPTIONS and $MODULE_UNLOAD
	# must not be interpreted as additional MODULE*
	case "$MODVAR" in
		MODULE_OPTIONS*|MODULE_UNLOAD*) continue ;;
	esac
	INDEX=${MODVAR#MODULE}

	eval ML[$M]=\$MODULE$INDEX
	eval test -z "\${ML[$M]}" && continue
	eval MO[$M]=\$MODULE_OPTIONS$INDEX
	eval MU[$M]=\${MODULE_UNLOAD$INDEX:-$MODULE_UNLOAD}
	: $((M++))
done

if [ "$SCRIPTNAME" = hwup ] ; then
	while [ $N -lt $M ] ; do
		if [ -d /sys/module/${ML[$N]} ] ; then
			mesg "hwup: module '${ML[$N]}' already present in kernel"
			: $((N++))
			continue
		fi
		if [ -d /sys/bus/$HWD_BUSNAME/drivers/${ML[$N]} ] ; then
			mesg "hwup: driver '${ML[$N]}' already present in kernel"
			: $((N++))
			continue
		fi

		mesg "hwup: Loading module '${ML[$N]}'" \
		        "${MO[$N]:+with options '${MO[$N]}' }" \
		        "for device '$HWDESC'"
		/sbin/modprobe ${ML[$N]} ${MO[$N]}
		# we exit here if we cannot modprobe the module
		test $? != 0 && exit 1
		: $((N++))
	done
fi

# Check if we should/can (un)bind devices
# ccw devices are often grouped. We had to (un)bind all or not at all.
case "$HWD_BUSNAME" in
	ccw|"") NOBIND=yes ;;
esac
test -z "$HWD_BUSID" && NOBIND=yes
if [ "$DRIVER" == skip ] ; then
	info_mesg "Binding/Releasing device '$HWD_BUSID' skipped (DRIVER==skip)"
	NOBIND=yes
fi

if [ "$SCRIPTNAME" = hwup -a "$NOBIND" != yes ] ; then
	# we need  to get the driver's name here, so we do the following:
	# 1) if DRIVER exists in config file, we use it
	# 2) else we iterate the module list, and use the first usable drivername
	# 3) else we check if we saved the driver during hwdown and use it

	if [ -z "$DRIVER" ]; then
		N=0
		# 2)
		while [ $N -lt $M -a -z "$DRIVER" ] ; do
			ls -d /sys/bus/$HWD_BUSNAME/drivers/${ML[$N]} &>/dev/null
			if [ $? = 0 ]; then
				DRIVER=${ML[$N]}
			fi
			: $((N++))
		done

		# 3)
		if [ -e "$DRIVER_FILE" ]; then
			if [ -z "$DRIVER" ]; then
				. $DRIVER_FILE 
			fi
			rm $DRIVER_FILE
		fi
	fi

	if [ -n "$DRIVER" ]; then
		SYSFS_DRIVER_PATH="/sys/bus/$HWD_BUSNAME/drivers/$DRIVER"
		if [ -L $SYSFS_DRIVER_PATH/$HWD_BUSID ] ; then
			info_mesg "Device '$HWD_BUSID' is already bound to driver '$DRIVER'"
		else
			mesg "hwup: binding device $HWD_BUSID to driver $DRIVER."
			echo -n $HWD_BUSID > $SYSFS_DRIVER_PATH/bind
			test "$DRIVER" == ipw3945 && ipw3945_start_daemon 
		fi
	else
		info_mesg "hwup error: could not get driver name"
	fi
fi

if [ "$SCRIPTNAME" = hwdown -a "$HOTPLUG" != yes -a "$NOBIND" != yes ] ; then

	SYSFS_DRIVER_PATH=`ls -d /sys/bus/$HWD_BUSNAME/drivers/*/$HWD_BUSID 2>/dev/null`
	SYSFS_DRIVER_PATH=${SYSFS_DRIVER_PATH%/$HWD_BUSID}

	if [ -n "$SYSFS_DRIVER_PATH" ]; then
		# unbind the device from the module
		mesg "hwup: unbinding device $HWD_BUSID from driver $HWD_DRIVER."
		echo -n $HWD_BUSID > $SYSFS_DRIVER_PATH/unbind
		# save driver name
		echo "DRIVER=$HWD_DRIVER" > $DRIVER_FILE
	else
		info_mesg "hwup: unbinding device $HWD_BUSID failed."
	fi

fi

if [ "$SCRIPTNAME" = hwstatus ] ; then
	SYSFS_DRIVER_PATH=`ls -d /sys/bus/$HWD_BUSNAME/drivers/*/$HWD_BUSID \
	                      2>/dev/null`
	SYSFS_DRIVER_PATH=${SYSFS_DRIVER_PATH%/$HWD_BUSID}
	SYSFS_MODULE_PATH=$(cd $SYSFS_DRIVER_PATH 2>/dev/null &&
	                    cd module 2>/dev/null; pwd -P)
	SYSFS_MODULE=${SYSFS_MODULE_PATH##*/}
	echo DRIVER=${SYSFS_DRIVER_PATH##*/}
	if lsmod | grep -qs "\<$SYSFS_MODULE\>"; then
		echo MODULE=${SYSFS_MODULE}
	fi
	: # to be implemented
fi


######################################################################
# Call subsystem specific post_intialisation function if there is one
#
if [ "`type -t post_init_${SUBSYSTEM}`" == function ] ; then
	info_mesg calling post_init_${SUBSYSTEM}
	post_init_${SUBSYSTEM}
fi


######################################################################
# Call a specialized up script, depending on the event
#
# have a look at 'Call a specialized down script' above
if [ "$SCRIPTNAME" = hwup ]; then
	# Call an event-specific script if one exists
	if [ "$HWD_DEVTYPE" ]; then
		SCRIPT_UP=`eval echo \\$SCRIPTUP_$HWD_DEVTYPE`
	fi
	# Call a generic script if no event-specific exists
	if [ -z "$SCRIPT_UP" ]; then
		SCRIPT_UP=`eval echo \\$SCRIPTUP`
	fi
	if test -n "$SCRIPT_UP" && test -x "./scripts/$SCRIPT_UP" ; then
		info_mesg "Calling scripts/$SCRIPT_UP '$CONFIG'" \
		          "$HWDESC ${OPTIONS:+-o $OPTIONS}"
		./scripts/$SCRIPT_UP "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
	fi
fi

######################################################################
# execute individual poststart or postdown scripts if available
#
if [ "$SCRIPTNAME" = hwup ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like POST_UP_SCRIPT="~root/bin/foo"
	for PUS in `eval echo $POST_UP_SCRIPT scripts/$POST_UP_SCRIPT`; do
		if [ -x "$PUS" -a ! -d "$PUS" ] ; then
			info_mesg "executing additional start script $PUS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PUS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi
if [ "$SCRIPTNAME" = hwdown ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like POST_DOWN_SCRIPT="~root/bin/foo"
	for PDS in `eval echo $POST_DOWN_SCRIPT scripts/$POST_DOWN_SCRIPT`; do
		if [ -x "$PDS" -a ! -d "$PDS" ] ; then
			info_mesg "executing additional stop script $PDS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PDS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi
