#! /bin/bash

# lsznet.raw: list sensible network device hardware setups for Linux on s390(x)
# Copyright (C) IBM Corp. 2008,2009
# Author: Steffen Maier <maier@de.ibm.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

readonly SYSFS=/sys
# DEBUG=0 turns off debugging. >=1 means increasing debugging.
readonly DEBUG=0

# nothing to be changed below here

export TEXTDOMAIN=lsznet.raw

readonly CMD=${0##*/}

function error() {
    echo $"$CMD: ERROR: $*" 1>&2
    exit 1;
}

# currently requires bash version 3.0 or later
# (this seems reasonable since bash-3.0 has been shipped with
#  RHEL 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7;
#  bash-3.1 with RHEL 5.0, 5.1; and bash-3.2 with RHEL 5.2)
if [ ${BASH_VERSINFO[0]} -lt 3 -o \
    ${BASH_VERSINFO[1]} -lt 0 ]; then
    error $"only works with BASH version 3.0 or later (current version used is ${BASH_VERSION})"
fi

# the script was designed for Linux kernel 2.6 and might work with newer ones
kernel_version=$(uname -r)
IFS=.
read krn_ver krn_patch krn_foo <<< "$kernel_version"
unset IFS
if [ $krn_ver -lt 2 -o $krn_patch -lt 6 ]; then
    error $"only works for kernel versions 2.6 or probably later"
fi

. /sbin/controlunits

readonly PREFIXFORMAT=[[:xdigit:]]*
readonly SSIDFORMAT=[0-3]
readonly BUSIDFORMAT=[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]
readonly IDFORMAT=$PREFIXFORMAT.$SSIDFORMAT.$BUSIDFORMAT
readonly SUBCHANNEL_TYPE_IO=0

function debug() {
    level=$1
    shift
    [ $DEBUG -ge $level ] && echo "$*" 1>&2
}

# Returns symbolic name of CHPID type in $chpidtype_symbolic,
# if an entry in the array $CHPIDTYPES has been found at index of argument 1.
# Returns "?" otherwise.
# Always succeeds and returns 0.
function search_chpt() {
    local chpidtype_number=$1
    chpidtype_symbolic=${CHPIDTYPES[$((0x$chpidtype_number))]}
    if [ "$chpidtype_symbolic" == "" ]; then
        chpidtype_symbolic="?"
    fi
    return 0
}

# build_list:
#
# Prints list on standard output consisting of all subchannels and
# ccwdevices whose control unit type/model match supported network
# device types on s390.  Each matching entry is accompanied with
# (almost all) corresponding attributes.
#
function build_list() {
    # use /sys/devices/css*/ for startpath
    readonly STARTPATH=$SYSFS/devices
    # change to base directory so path globbing length with find is minimal
    cd $STARTPATH
    # fail out gracefully, if there is not expected sysfs environment
    # (could even fail out near the top, if $(uname -m) != s390x)
    csses=css$PREFIXFORMAT
    for d in $csses; do
        [ -d $d ] || exit
    done
    find $csses -name "$IDFORMAT" |
    while read dir; do
        debug 6 " examining sysfs directory $dir"
        # must not use $...FORMAT (file globs) here since this is a regex:
        [[ "$dir" =~ ^css([[:xdigit:]]+)/([[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4})/([[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4})$ ]]
        case $? in
            0)
                # string matched the pattern
                debug 6 " ${BASH_REMATCH[@]}"
                prefix=${BASH_REMATCH[1]}
                subch=${BASH_REMATCH[2]}
                devbusid=${BASH_REMATCH[3]}
                subch_p=css$prefix/$subch
                dev_p=$subch_p/$devbusid
                debug 6 " $subch_p $dev_p"
                ;;
            1)
                # string did not match the pattern
                continue
                ;;
            2)
                error $"syntax error in regex of match operator =~, code needs to be fixed"
                ;;
            *)
                error $"unexpected return code of regex match operator =~, code needs to be fixed"
                ;;
        esac
        debug 5 " sysfs directory matched regex $dir"
        # skip non-I/O-subchannels, i.e. chsc and message subchannels
	if [ -f $subch_p/type ]; then
	    read type < $subch_p/type
            if [ $type != $SUBCHANNEL_TYPE_IO ]; then
                debug 3 " skip non-I/O subchannel"
                continue
            fi
	fi
        # get subchannel information...
        # ATTENTION: hex values from sysfs are WITHOUT leading 0x prefix!
        read chpid_list < $subch_p/chpids
        read -a chpids <<< "$chpid_list"
        if [ ${#chpids[@]} -ne 8 ]; then
            error $"sysfs reported ${#chpids[@]} CHPIDs instead of expected 8"
        fi
        read pim pam pom foo < $subch_p/pimpampom
        pimchpidZ=""
        local chp
        for ((chp=0; chp < 8; chp++)); do
            mask=$((0x80 >> chp))
            if (( 0x$pim & $mask )); then
                pimchpidZ=${pimchpidZ}${chpids[chp]}
            else
                pimchpidZ=${pimchpidZ}"ZZ"
            fi
        done
        # get device information...
        read cutype < $dev_p/cutype
        read active < $dev_p/online
        # skip already active subchannels and those that are already in a
        # ccwgroup and thus not available any more:
        [ $active == "1" ] && continue
        [ -h $dev_p/group_device ] && continue
        # get chpid information...
        pimchpids=${pimchpidZ//ZZ/}
        [ $pimchpids == "" ] && continue
        # Taking the first 2 hex digits as CHPID relies somewhat on the fact
        # that network adaptors don't use multipathing and only have one CHP.
        # Anyway it's OK since we're only interested in CHPID type and I guess
        # this should be equal for all possible multipaths to the same device.
        chpid=${pimchpids:0:2}
        chpid_p=css$prefix/chp$prefix.$chpid
        read chptype < $chpid_p/type
        # filter and output...
        if search_cu $cutype; then
            if [ "${CU_DEVDRV[$cu_idx]}" == "ctc" ]; then
                # assume CTC are mostly virtual and ignore chpid from sysfs
                chpidtype_symbolic="-"
            else
                search_chpt $chptype
            fi
            echo $pimchpids $devbusid $STARTPATH/$dev_p $cutype $chpidtype_symbolic $chptype ${CU_DEVDRV[$cu_idx]} ${CU_DEVNAME[$cu_idx]} ${CU_GROUPCHANNELS[$cu_idx]} $cu_idx ${CU_CARDTYPE[$cu_idx]}
        else
            debug 5 " skip non-network device $devbusid CU $cutype"
        fi
    done
}

# search_groups:
#
# Prints enumeration list on standard output consisting of possible
# hardware configurations (ccwgroups) for network devices on s390.
# Each configuration suggestion includes corresponding attributes
# that are of potential interest for the user and fit in a fixed column
# table on an 80 column screen.
#
# PRECONDITION: Standard input has to be stably sorted by device bus IDs and
# then by CHPIDs, i.e. grouped by CHPIDs.
#
function search_groups() {
    local w_prefix w_ssid w_devno
    local d_prefix d_ssid d_devno
    local prefix ssid devno x
    local chp devbusid dev_p cutype chpidtypename chptype devdrv devname groupchs cu_idx cardtype
    # remembered last state variables for possible ccwgroup:
    local r_prefix="Z"
    local r_ssid="Z"
    local r_devno="ZZZZ"
    local r_chp="ZZ"
    local r_cutype="ZZZZ/ZZ"
    local count=0
    local item=1
    local skipped=0
    # currently unused are: dev_p,chptype, cu_idx.
    while read chp devbusid dev_p cutype chpidtypename chptype devdrv devname groupchs cu_idx cardtype; do
        debug 1 " # $chp $devbusid $dev_p $cutype $chpidtypename $chptype $devdrv $devname $groupchs $cu_idx $cardtype"
        IFS=.
        read prefix ssid devno x <<< "$devbusid"
        unset IFS
        if [ $r_chp != $chp \
            -o $r_prefix != $prefix \
            -o $r_ssid != $ssid \
            -o $r_cutype != $cutype ]; then
            # restart with new read channel info and remember it
            r_prefix=$prefix
            r_ssid=$ssid
            r_devno=$devno
            r_chp=$chp
            r_cutype=$cutype
            count=1
            debug 2 " INFO: restart on different CHPID or prefix or CUtype/model"
            continue
        fi
        count=$((count + 1))
        if [ $count -eq 2 ]; then
            # about to check if write channel is one above read channel
            if [ $((0x$devno)) -ne $((0x$r_devno + 1)) ]; then
                # start with new read channel info
                r_prefix=$prefix
                r_ssid=$ssid
                r_devno=$devno
                r_chp=$chp
                r_cutype=$cutype
                count=1
                skipped=$((skipped + 1))
                # unimplemented possible packed channel usage option:
                # remember unused channels for later use as data channel
                debug 2 " INFO: restart on unmatching read channel"
                continue
            fi
            w_prefix=$prefix
            w_ssid=$ssid
            w_devno=$devno
        elif [ $count -eq 3 ]; then
            # remember data channel info
            d_prefix=$prefix
            d_ssid=$ssid
            d_devno=$devno
        fi
        debug 2 " INFO: groupchs=$groupchs count=$count"
        if [ $count -ne $groupchs ]; then
            debug 2 " INFO: skip"
            continue
        fi
        # found possible ccwgroup
        case $count in
            2)
                chlist=$r_prefix.$r_ssid.$r_devno,$w_prefix.$w_ssid.$w_devno
                ;;
            3)
                chlist=$r_prefix.$r_ssid.$r_devno,$w_prefix.$w_ssid.$w_devno,$d_prefix.$d_ssid.$d_devno
                ;;
            *)
                error $"unknown number of channels for group, code needs to be fixed"
                ;;
        esac
        echo $item $cutype $chp $chpidtypename $devdrv $devname $chlist "$cardtype"
        item=$((item + 1))
        # restart after successful detection
        r_prefix="Z"
        count=0
    done
    debug 1 " STATISTIC: skipped $skipped devnos because of unmatching read channel"
}

build_list |
# stable sort by device bus IDs and then by CHPIDs => grouped by CHPIDs
# (sorting only works since keys are fixed no. of digits with leading zeros!)
sort -s -k 1,1 -k 2,2 |
#cat ; exit # move at desired line and uncomment to see intermediate output
search_groups
