#! /bin/sh
#
# ctc_configure
#
# Configures a CTC device
#
# Usage:
#   ctc_configure <read channel> <write channel> <online> [<protocol>]
#
#	read/write channel = x.y.ssss where
#			     x is always 0 until IBM creates something that uses that number
#			     y is the logical channel subsystem (lcss) number. Most often this is 0, but it could be non-zero
#			     ssss is the four digit subchannel address of the device, in hexidecimal, with leading zeros.
#	online = 0 to take the device offline
#		 1 to bring the device online
#	protocol = 0 Compatibility with peers other than OS/390®, or z/OS, for example, a z/VM TCP service machine. This is the default.
#		   1 Enhanced package checking for Linux peers.
#		   3 For compatibility with OS/390 or z/OS peers.
#		   4 For MPC connections to VTAM on traditional mainframe operating systems.
#
# Return values:
#   1  sysfs not mounted
#   2  Invalid status for <online>
#   3  No device found for read-channel
#   4  No device found for write-channel
#   5  Invalid device type
#   6  Device type mismatch
#   7  Could not load module
#   8  CCW devices grouped different devices
#   9  Could not group devices
#   10 Could not set device online
#   11 Could not set device offline
#

if [ "${DEBUG}" != "yes" ]; then
  DEBUG="no"
fi

DATUM=$(date)

add_channel_for_cio() {
	echo "$* # $DATUM" >> /boot/zipl/active_devices.txt
}

remove_channel_for_cio() {
	[ -w /boot/zipl/active_devices.txt ] && sed -i -e "/^$1/d" /boot/zipl/active_devices.txt
}

mesg () {
    echo "$@"
}

debug_mesg () {
    case "$DEBUG" in
        yes) mesg "$@" ;;
        *) ;;
    esac
}

# Get the mount point for sysfs
while read MNTPT MNTDIR MNTSYS MNTTYPE; do
    if test "$MNTSYS" = "sysfs"; then
	SYSFS="$MNTDIR"
	break;
    fi 
done </proc/mounts

if [ -z "$SYSFS" ]; then
    mesg "/sysfs not present"
    exit 1
fi

if [ $# -lt 3 ] ; then
    echo "Usage: $0 <read channel> <write channel> <online> [<protocol>]"
    echo "	read/write channel = x.y.ssss where"
    echo "			     x is always 0 until IBM creates something that uses that number"
    echo "			     y is the logical channel subsystem (lcss) number. Most often this is 0, but it could be non-zero"
    echo "			     ssss is the four digit subchannel address of the device, in hexidecimal, with leading zeros."
    echo "	online = 0 to take the device offline"
    echo "		 1 to bring the device online"
    echo "	protocol = 0 Compatibility with peers other than OS/390®, or z/OS, for example, a z/VM TCP service machine. This is the default."
    echo "		   1 Enhanced package checking for Linux peers."
    echo "		   3 For compatibility with OS/390 or z/OS peers."
    echo "		   4 For MPC connections to VTAM on traditional mainframe operating systems."
    exit 1
fi

CTC_READ_CHAN=$1
CTC_WRITE_CHAN=$2
ONLINE=$3
CTC_MODE=$4

[ -z "$CTC_MODE" ] && CTC_MODE=0

if [ -z "$ONLINE" ] || [ "$ONLINE" -ne "1" -a "$ONLINE" -ne "0" ]; then
    mesg "Invalid device status $ONLINE"
    exit 2
fi

_ccw_dir=${SYSFS}/bus/ccw/devices

debug_mesg "Configuring CTC/LCS device ${CTC_READ_CHAN}/${CTC_WRITE_CHAN}"


if test ! -d "$_ccw_dir/$CTC_READ_CHAN" ; then
    mesg "device $_ccw_dir/$CTC_READ_CHAN does not exist"
    exit 3
fi
if test ! -d "$_ccw_dir/$CTC_WRITE_CHAN" ; then
    mesg "device $_ccw_dir/$CTC_WRITE_CHAN does not exist"
    exit 4
fi

CCW_CHAN_GROUP=
for ccw in $_ccw_dir/$CTC_READ_CHAN $_ccw_dir/$CTC_WRITE_CHAN; do

    read _cu_type < $ccw/cutype
    read _dev_type < $ccw/devtype

    case "$_cu_type" in
        3088/01)
            # P/390 network adapter
            CCW_CHAN_NAME="cu3088"
            CCW_CHAN_GROUP="lcs"
            ;;
        3088/08)
            # Channel To Channel
            CCW_CHAN_NAME="cu3088"
            CCW_CHAN_GROUP="ctcm"
            ;;
        3088/1e)
            # FICON adapter
            CCW_CHAN_NAME="cu3088"
            CCW_CHAN_GROUP="ctcm"
            ;;
        3088/1f)
            # ESCON adapter (I.e. hardware CTC device)
            CCW_CHAN_NAME="cu3088"
            CCW_CHAN_GROUP="ctcm"
            ;;
        3088/60)
            # Lan Channel Station
            CCW_CHAN_NAME="cu3088"
            CCW_CHAN_GROUP="lcs"
            ;;
	*)
	    CCW_CHAN_NAME=
	;;
    esac

    if [ -z "$CCW_CHAN_NAME" ]; then
	mesg "Not a valid CTC device (cu $_cutype, dev $_devtype)"
	exit 5
    fi

    [ -z "$tmp_chan" ] && tmp_chan=$CCW_CHAN_GROUP
done

if [ "$tmp_chan" != "$CCW_CHAN_GROUP" ] ; then
    mesg "CTC type mismatch (read: $tmp_chan, write: $CCW_CHAN_GROUP)"
    exit 6
fi

_ccw_groupdir=${SYSFS}/bus/ccwgroup

# Check for modules
if test ! -d "${_ccw_groupdir}/drivers/${CCW_CHAN_GROUP}" ; then
    /sbin/modprobe $CCW_CHAN_GROUP

    # Re-check whether module loading has succeeded
    if test ! -d "${_ccw_groupdir}/drivers/${CCW_CHAN_GROUP}"; then
	mesg "Could not load module ${CCW_CHAN_GROUP}"
	exit 7
    fi
fi

# Check for grouping
_ccw_status_dir=
if [ -e ${_ccw_dir}/${CTC_READ_CHAN}/group_device ] ; then
    _ccw_status_dir=$(cd -P ${_ccw_dir}/${CTC_READ_CHAN}/group_device; echo $PWD)
fi
if [ -e ${_ccw_dir}/${CTC_WRITE_CHAN}/group_device ] ; then
    _tmp_status_dir=$(cd -P ${_ccw_dir}/${CTC_READ_CHAN}/group_device; echo $PWD)
    if [ "$_ccw_status_dir" ] && [ "$_ccw_status_dir" != "$_tmp_status_dir" ] ; then
	mesg "CCW devices grouped to different devices"
	exit 8
    fi
    _ccw_status_dir=$_tmp_status_dir
fi
#
#	Addresses are free (but may be bound to the wrong driver)
#
_ccw_drivers=${SYSFS}/bus/ccw/drivers
for i in ${CTC_READ_CHAN} ${CTC_WRITE_CHAN}
  do
        if [ "$CCW_CHAN_GROUP" = "lcs" ]
        then
                if [ -e "${_ccw_drivers}/ctcm/${i}" ] ; then
                        echo $i > ${_ccw_drivers}/ctcm/unbind
                fi
                if [ ! -e "${_ccw_drivers}/lcs/${i}" ] ; then
                    echo $i > ${_ccw_drivers}/ctcm/bind
                fi
        else
                if [ -e "${_ccw_drivers}/lcs/${i}" ] ; then
                        echo $i > ${_ccw_drivers}/lcs/unbind
                fi
                if [ ! -e "${_ccw_drivers}/ctcm/${i}" ] ; then
                        echo $i > ${_ccw_drivers}/ctcm/bind
                fi
        fi
  done

debug_mesg "Group is ${_ccw_groupdir}/drivers/${CCW_CHAN_GROUP}/group"
if [ -z "$_ccw_status_dir" ] ; then
    echo "$CTC_READ_CHAN,$CTC_WRITE_CHAN" > ${_ccw_groupdir}/drivers/${CCW_CHAN_GROUP}/group
    if [ -e ${_ccw_dir}/${CTC_READ_CHAN}/group_device ] ; then
	_ccw_status_dir=$(cd -P ${_ccw_dir}/${CTC_READ_CHAN}/group_device; echo $PWD)
    fi
fi

if [ -z "$_ccw_status_dir" -o ! -e  "$_ccw_status_dir" ] ; then
    mesg "Could not group $CCW_CHAN_GROUP devices $CTC_READ_CHAN/$CTC_WRITE_CHAN"
    exit 9
fi

CCW_CHAN_ID=${_ccw_status_dir##*/}

read _ccw_dev_status < $_ccw_status_dir/online

if [ "$ONLINE" -eq 1 ]; then
    # Check whether we need to do something
    if [ "$_ccw_dev_status" -eq 0 ]; then
	if [ "$CTC_MODE" -gt 0  -a "$CCW_CHAN_GROUP" != "lcs" ]; then
	    echo $CTC_MODE > $_ccw_status_dir/protocol
	fi
	# Set the device online
	debug_mesg "Setting device online"
	echo "1" > $_ccw_status_dir/online
        # Re-read device status
	read _ccw_dev_status < $_ccw_status_dir/online
	if [ "$_ccw_dev_status" -eq 0 ]; then
	    mesg "Could not set device ${CCW_CHAN_ID} online"
	    exit 10
	fi
    else
	debug_mesg "Device ${CCW_CHAN_ID} is already online"
    fi
else
    if [ "$_ccw_dev_status" -eq 1 ]; then
        # Set the device offline
	debug_mesg "Setting device offline"
	echo "$ONLINE" > $_ccw_status_dir/online

        # Re-read to check whether we have succeeded
	_ccw_dev_status=$(cat $_ccw_status_dir/online)
	if [ "$_ccw_dev_status" -ne "$ONLINE" ]; then
	    mesg "Could not set device ${CCW_CHAN_ID} offline"
	    exit 11
	fi
    else
	debug_mesg "Device ${CCW_CHAN_ID} is already offline"
    fi
    # Always reset CTC Protocol
    if [ "$CCW_CHAN_GROUP" != "lcs" ]; then
	echo 0 > $_ccw_status_dir/protocol
    fi
fi

RULES_DIR=/etc/udev/rules.d
RULES_FILE=51-${CCW_CHAN_GROUP}-${CCW_CHAN_ID}.rules

if [ -d "$RULES_DIR" ]; then
    if [ -f ${RULES_DIR}/${RULES_FILE} ]; then
	rm -f ${RULES_DIR}/${RULES_FILE}
    fi
    remove_channel_for_cio "$CTC_READ_CHAN"
    remove_channel_for_cio "$CTC_WRITE_CHAN"

    if [ "$ONLINE" -eq "1" ]; then
	add_channel_for_cio "$CTC_READ_CHAN,$CTC_WRITE_CHAN"
        # Write a new udev rules file
	cat > ${RULES_DIR}/${RULES_FILE} <<EOF
# Configure ${CCW_CHAN_GROUP} device at ${CTC_READ_CHAN}/${CTC_WRITE_CHAN} (Protocol ${CTC_MODE})
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_READ_CHAN", RUN+="/sbin/modprobe --quiet $CCW_CHAN_GROUP"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_READ_CHAN", DRIVER!="?*", GOTO="ctc-${CTC_READ_CHAN}-no-unbind"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_READ_CHAN", DRIVER=="ctcm", GOTO="ctc-${CTC_READ_CHAN}-no-unbind"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_READ_CHAN", ATTR{driver/unbind}="$CTC_READ_CHAN"
LABEL="ctc-${CTC_READ_CHAN}-no-unbind"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_READ_CHAN", DRIVER!="?*", ATTR{[drivers/ccw:ctcm]bind}="$CTC_READ_CHAN"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_WRITE_CHAN", DRIVER!="?*", GOTO="ctc-${CTC_WRITE_CHAN}-no-unbind"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_WRITE_CHAN", DRIVER=="ctcm", GOTO="ctc-${CTC_WRITE_CHAN}-no-unbind"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_WRITE_CHAN", ATTR{driver/unbind}="$CTC_WRITE_CHAN"
LABEL="ctc-${CTC_WRITE_CHAN}-no-unbind"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_WRITE_CHAN", DRIVER!="?*", ATTR{[drivers/ccw:ctcm]bind}="$CTC_WRITE_CHAN"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_READ_CHAN", IMPORT{program}="collect $CCW_CHAN_ID %k $CTC_READ_CHAN $CTC_WRITE_CHAN $CCW_CHAN_GROUP"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_WRITE_CHAN", RUN+="/sbin/modprobe --quiet $CCW_CHAN_GROUP"
ACTION=="add", SUBSYSTEM=="ccw", KERNEL=="$CTC_WRITE_CHAN", IMPORT{program}="collect $CCW_CHAN_ID %k $CTC_READ_CHAN $CTC_WRITE_CHAN $CCW_CHAN_GROUP"
ACTION=="add", SUBSYSTEM=="drivers", KERNEL=="$CCW_CHAN_GROUP", IMPORT{program}="collect $CCW_CHAN_ID %k $CTC_READ_CHAN $CTC_WRITE_CHAN $CCW_CHAN_GROUP"
ACTION=="remove", SUBSYSTEM=="ccw", KERNEL=="$CTC_READ_CHAN", IMPORT{program}="collect --remove $CCW_CHAN_ID %k $CTC_READ_CHAN $CTC_WRITE_CHAN $CCW_CHAN_GROUP"
ACTION=="remove", SUBSYSTEM=="ccw", KERNEL=="$CTC_WRITE_CHAN", IMPORT{program}="collect --remove $CCW_CHAN_ID %k $CTC_READ_CHAN $CTC_WRITE_CHAN $CCW_CHAN_GROUP"
ACTION=="remove", SUBSYSTEM=="drivers", KERNEL=="$CCW_CHAN_GROUP", IMPORT{program}="collect --remove $CCW_CHAN_ID %k $CTC_READ_CHAN $CTC_WRITE_CHAN $CCW_CHAN_GROUP"
TEST=="[ccwgroup/${CCW_CHAN_ID}]", GOTO="ctc-${CCW_CHAN_ID}-end"
ACTION=="add", SUBSYSTEM=="ccw", ENV{COLLECT_$CCW_CHAN_ID}=="0", ATTR{[drivers/ccwgroup:$CCW_CHAN_GROUP]group}="$CTC_READ_CHAN,$CTC_WRITE_CHAN"
ACTION=="add", SUBSYSTEM=="drivers", KERNEL=="$CCW_CHAN_GROUP", ENV{COLLECT_$CCW_CHAN_ID}=="0", ATTR{group}="$CTC_READ_CHAN,$CTC_WRITE_CHAN"
LABEL="ctc-${CCW_CHAN_ID}-end"
EOF
	if [ "$CTC_MODE" -gt 0 ]; then
	    cat >> ${RULES_DIR}/${RULES_FILE} <<EOF
ACTION=="add", SUBSYSTEM=="ccwgroup", KERNEL=="$CCW_CHAN_ID", ATTR{protocol}="$CTC_MODE"
EOF
	fi
	cat >> ${RULES_DIR}/${RULES_FILE} <<EOF
ACTION=="add", SUBSYSTEM=="ccwgroup", KERNEL=="$CCW_CHAN_ID", ATTR{online}="1"
EOF
    fi
fi
