#!/bin/bash
# $Id: pci.agent 313 2005-01-21 15:56:39Z hare $
#
# PCI and CardBus hotplug policy agent.
#
# Kernel Cardbus/PCI params are:
#
#        ACTION=%s [add or remove]
#        DEVPATH=%s [device path in sysfs]
#        PCI_CLASS=%04X
#        PCI_ID=%04X:%04X
#        PCI_SLOT_NAME=%s
#        PCI_SUBSYS_ID=%04X:%04X
#
# If /proc is mounted, /proc/bus/pci/$PCI_SLOT_NAME is almost the name
# of the binary device descriptor file ... just change ':' to '/'.

: ${ACTION?Bad invocation: \$ACTION is not set}
: ${DEVPATH?Bad invocation: \$DEVPATH is not set}
: ${PCI_CLASS?Bad invocation: \$PCI_CLASS is not set}
: ${PCI_ID?Bad invocation: \$PCI_ID is not set}
: ${PCI_SLOT_NAME?Bad invocation: \$PCI_SLOT_NAME is not set}
: ${PCI_SUBSYS_ID?Bad invocation: \$PCI_SUBSYS_ID is not set}

cd /etc/hotplug
. ./hotplug.functions

# generated by modutils, for kernels 2.4.x and newer
MAP_CURRENT=$MODULE_DIR/modules.pcimap

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

LOCKFILE=/var/run/hotplug/pci.net.lock.$PCI_SLOT_NAME

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

queue_nic_events() {
	# return immediately if device is not a NIC (pci class '02'
	test $((0x$PCI_CLASS & 0xf0000)) -eq $((0x20000)) || return 0
	touch $LOCKFILE
	debug_mesg "touching $LOCKFILE"
	if [ "$HOTPLUG_PCI_QUEUE_NIC_EVENTS" = wait ]; then
		trap 'sleep 2; debug_mesg "removing $LOCKFILE"; rm -f $LOCKFILE' EXIT
		sleep 1
	else
		trap 'debug_mesg "removing $LOCKFILE"; rm -f $LOCKFILE' EXIT
	fi
	WAIT_MAX=10 # in case of an error
	while [ $((WAIT_MAX--)) -gt 0 ] ; do
		LF=`ls -tr /var/run/hotplug/pci.net.lock.* | sed -n 1p`
		debug_mesg "checking lock $LF"
		test "$LF" = "$LOCKFILE" && break
		sleep 1
	done
}

filter_drivers() {
     test $# = 0 && return 0
     # debug_mesg "drivertype white/black-list filter: $*"
     # debug_mesg "HPDTW:$HOTPLUG_PCI_DRIVERTYPE_WHITELIST" 
     # debug_mesg "HPDTB:$HOTPLUG_PCI_DRIVERTYPE_BLACKLIST"
    if [    -z "$HOTPLUG_PCI_DRIVERTYPE_WHITELIST" \
         -a -z "$HOTPLUG_PCI_DRIVERTYPE_BLACKLIST" ] ; then
        echo $*
        return 0
    fi
    local P DP YES
    while [ $# -gt 0 ] ; do
        YES=
        DP=`sed -n "s=^.*/kernel/drivers\(.*\)/$1.ko:.*$=\1=p;T;q" \
                $MODULE_DIR/modules.dep`
	test -z "$DP" && YES=yes
	debug_mesg "Driver: $1   DriverPath: ${DP#/}"
        for P in ${HOTPLUG_PCI_DRIVERTYPE_WHITELIST:-\*}; do
            case "$DP" in /$P*) YES=yes ;; esac
        done
        for P in $HOTPLUG_PCI_DRIVERTYPE_BLACKLIST; do
            case "$DP" in /$P*) YES=no ;; esac
        done
        # debug_mesg "`printf "%-8s %-10s %s\n" "$YES" "$1" "$DP"`"
        if [ "$YES" = yes ] ; then
            echo $1
        elif [ "$YES" = no ] ; then
            debug_mesg "drivertype of driver '$1' is listed in" \
                       "HOTPLUG_PCI_DRIVERTYPE_BLACKLIST"
        else
            debug_mesg "drivertype of driver '$1' is not listed in" \
                       "HOTPLUG_PCI_DRIVERTYPE_WHITELIST"
        fi
        shift
    done
}

pci_check_class() {
    local OK=no USE SKIP TAIL PCICLASS
    HOTPLUG_PCI_CLASSES_WHITELIST=$(echo ${HOTPLUG_PCI_CLASSES_WHITELIST} |
                                    sed y/abcdef/ABCDEF/ )
    HOTPLUG_PCI_CLASSES_BLACKLIST=$(echo ${HOTPLUG_PCI_CLASSES_BLACKLIST} |
                                    sed y/abcdef/ABCDEF/ )
    PCICLASS=$(echo ${PCI_CLASS} | sed y/abcdef/ABCDEF/) 
    test ${#PCICLASS} -le 5 && PCICLASS="0${PCICLASS}"

    for USE in $HOTPLUG_PCI_CLASSES_WHITELIST; do
        TAIL=${PCICLASS##$USE}
        if [ ${#TAIL} -le 4 ] ; then
            OK=yes
        fi
    done
    if [ "$OK" = no -a -n "$HOTPLUG_PCI_CLASSES_WHITELIST" ] ; then
        debug_mesg "skipped `lspci -s ${PCI_SLOT_NAME/0000:/}`" \
                   "(not in the WHITELIST)"
        return 1
    fi
    # Skip some classes
    for SKIP in $HOTPLUG_PCI_CLASSES_BLACKLIST; do
        # The numbers in HOTPLUG_PCI_CLASSES_BLACKLIST must not specify
        # prog-if. Therfore check if at least the first four digits match.
        TAIL=${PCICLASS##$SKIP}
        # debug_mesg $PCICLASS $SKIP $TAIL
        if [ ${#TAIL} -le 4 ] ; then
            debug_mesg "skipped `lspci -s ${PCI_SLOT_NAME/0000:/}`" \
                       "(is in the BLACKLIST)"
            return 1
        fi
    done
    return 0
}

#
# Each modules.pcimap format line corresponds to one entry in a
# MODULE_DEVICE_TABLE(pci,...) 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.
#

# inputs to the match algorithm, from kernel by way of /sbin/hotplug
declare -i pci_class
declare -i pci_id_vendor pci_id_device
declare -i pci_subid_vendor pci_subid_device

pci_convert_vars ()
{
    pci_class=0x$PCI_CLASS

    set -- `IFS=:; echo $PCI_ID`
    pci_id_vendor=0x$1
    pci_id_device=0x$2

    set -- `IFS=:; echo $PCI_SUBSYS_ID`
    pci_subid_vendor=0x$1
    pci_subid_device=0x$2

    pci_revision=`lspci -ns ${DEVPATH##*/} 2>/dev/null |
                  sed -n "s/^.*(rev *\([0-9]*\).*$/\1/p"`
}

declare -i PCI_ANY=0xffffffff

# stdin is "modules.pcimap" syntax
# on return, ONE matching module was added to $DRIVERS
#
pci_map_modules ()
{
    # convert the usb_device_id fields to integers as we read them
    local line module ignored
    declare -i vendor device
    declare -i subvendor subdevice
    declare -i class class_mask
    declare -i class_temp

    HWINFO=yes
    # look at each pci_device_id entry
    # collect one match in $DRIVERS
    while read line
    do
        HWINFO=no
        # comments are lines that start with "#" ...
        # be careful, they still get parsed by bash!
        case "$line" in
            \#*|"") continue ;;
        esac

        set -- $line
        module=$1
        vendor=$(($2))
        device=$(($3))
        subvendor=$(($4))
        subdevice=$(($5))
        class=$(($6))
        class_mask=$(($7))

        : checkmatch $module

        : vendor $vendor $pci_id_vendor
        if [ $vendor -ne $PCI_ANY -a $vendor -ne $pci_id_vendor ]; then
            continue
        fi
        : device $device $pci_id_device
        if [ $device -ne $PCI_ANY -a $device -ne $pci_id_device ]; then
            continue
        fi
        : sub-vendor $subvendor $pci_subid_vendor
        if [ $subvendor -ne $PCI_ANY -a $subvendor -ne $pci_subid_vendor ]; then
            continue
        fi
        : sub-device $subdevice $pci_subid_device
        if [ $subdevice -ne $PCI_ANY -a $subdevice -ne $pci_subid_device ]; then
            continue
        fi

        class_temp="$pci_class & $class_mask"
        if [ $class_temp -eq $class ]; then
            DRIVERS="$module $DRIVERS"
            : drivers $DRIVERS
            break
        fi
    done

    if [ "$HWINFO" = yes ] ; then
        DRIVERS=`hwinfo --db "pci ${pci_id_vendor:+vendor=$pci_id_vendor} 
                         ${pci_id_device:+device=$pci_id_device} 
                         ${pci_subid_vendor:+subvendor=$pci_subid_vendor}  
                         ${pci_subid_device:+subdevice=$pci_subid_device} 
                         ${pci_revision:+revision=$pci_revision}
                         ${PCI_CLASS:+class=0x$PCI_CLASS}"`
    fi

    DRIVERS=`filter_drivers $DRIVERS`

}


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

    add)

        if [ "$HOTPLUG_PCI_QUEUE_NIC_EVENTS" != no ] ; then
            queue_nic_events
        fi

        # 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

        if [    -n "HOTPLUG_PCI_CLASSES_WHITELIST" \
             -o -n "HOTPLUG_PCI_CLASSES_BLACKLIST" ] ; then
            pci_check_class || exit
        fi
        
        pci_convert_vars
        LABEL="PCI slot $PCI_SLOT_NAME"
        FOUND=""

        if [ -x /usr/sbin/hwinfo ] ; then
            load_drivers pci hwinfo "$LABEL"
            FOUND="$FOUND${DRIVERS:+ $DRIVERS}"
        else
            # cope with special driver module configurations first. So we can
            # overwrite default settings in MAP_CURRENT provide missing info
            if [ -r $MAP_HANDMAP ]; then
                load_drivers pci $MAP_HANDMAP "$LABEL"
                FOUND="$FOUND${DRIVERS:+ $DRIVERS}"
            fi
            # Now look into main source of information
            if [ -r $MAP_CURRENT -a "$FOUND" == "" ]; then
                load_drivers pci $MAP_CURRENT "$LABEL"
                FOUND="$FOUND${DRIVERS:+ $DRIVERS}"
            fi
        fi

	# update hardware database
        [ "$HWSCAN" ] && $HWSCAN --pci $DEVPATH

	# Generate hwcfg file
	VPID="$(printf %04x $pci_id_vendor)-$(printf %04x $pci_id_device)"
	if [ $pci_subid_vendor -ne 0 ]; then
	    VPID="${VPID}-$(printf %04x $pci_subid_vendor)-$(printf %04x $pci_subid_device)"
	fi

	debug_mesg "Create hwcfg-vpid-${VPID}"
	create_hwcfg_file vpid-${VPID} "$DRIVERS"
        ;;

    remove)

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

        # update the local hardware components database (/var/lib/hardware)
        [ "$HWSCAN" ] && $HWSCAN --pci $DEVPATH

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

esac
