# -*- shell-script -*-

# 20sysfs - Hardware database scanning routines and variables for sysfs.

# This file is part of the Linux lsvpd package.

# (C) Copyright IBM Corp. 2002, 2003, 2004

# Maintained by Martin Schwenke <martins@au.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; either version 2, or (at your option)
# any later version.
 
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
    
# $Id: 20sysfs,v 1.22 2004/02/19 03:29:18 martins Exp $

sysfs_dir=$(sed -n -e 's@^[^ ]* \([^ ]*\) sysfs .*@\1@p' /proc/mounts)

[ -n "$sysfs_dir" ] || return 0

######################################################################

function db_initialise_bus ()
{
    db_initialise_sysfs
}

function db_initialise_sysfs ()
{
    local d="${db_bus_dir}/pci"
    mkdir -p "$d"
    local i
    for i in "${sysfs_dir}/bus/pci/devices/"* ; do
	local b=$(basename "$i")
	local s="${d}/${b}"
	mkdir -p "$s"
	for a in "class" \
	    "vendor" "device" "subsystem_vendor" "subsystem_device" \
	    "irq" ; do
	  cp -a "${i}/${a}" "${s}/${a}"
	done

	do_pci22_vpd $(pcistring2info "$b") "${d}/${b}"
    done

    d="${db_bus_dir}/pseudo"
    mkdir -p "$d"
    for i in "${sysfs_dir}/bus/pseudo/devices/"* ; do
	local b=$(basename "$i")
	mkdir -p "${d}/${b#adapter}"
    done
}

######################################################################

# This overrides the multiplexed function.

function get_pci_info ()
{
    local s=$(sysfs_get_bus_string "$@")
    case "$s" in
	(pci:*) pcistring2info ${s#pci\:} ;;
    esac
}    

function get_pci_config_filename ()
{
    local dom="$1"
    local bus="$2"
    local dev="$3"
    local fun="$4"

    local t="${sysfs_dir}/bus/pci/devices"
    local f="${t}/%04x:%02x:%02x.%x/config"
    local conf=$(printf "$f" "$dom" "$bus" "$dev" "$fun")
    [ -f "$conf" ] && echo "$conf"
}

######################################################################

unset -f list_os_adapters_scsi
unset -f list_os_adapters_ethernet

function list_os_adapters_DEFAULT ()
{
    local type="$1"

    local class=$(sysfs_get_adapter_class "$type")
    local prefix=$(get_adapter_os_prefix "$type")
    if [ -n "$class" -a -n "$prefix" ]  ; then
	local i
	for i in "${sysfs_dir}/class/${class}/${prefix}"* ; do
	    basename "$i"
	done
    fi
}

######################################################################

# Overrides multiplexed function.

function match_adapter ()
{
    local type="$1"
    local name="$2"

    local subdir=$(get_adapter_subdir "$type")
    local os_dir="${db_os_dir}/${subdir}/${name}"
    
    local bus_string=$(sysfs_get_bus_string "$type" "$name")
    local bus_type="${bus_string%%\:*}"
    local bus_addr="${bus_string#*\:}"
    case "$bus_type" in
	(pci|pseudo) :           ;;
	(*)          bus_type="" ;;
    esac

    if [ -n "$bus_type" ] ; then
	local bus_dir="${db_bus_dir}/${bus_type}/${bus_addr}"

	case "$type" in
	    ide)
		local num=$(basename $(sysfs_get_node "$type" "$name"))
		local channel="${num%.*}"
		[ -n "$channel" ] && bus_dir="${bus_dir}/${channel}"
		;;
	esac

	mkdir -p "$bus_dir"
	mkdir -p "$os_dir"

	cross_link "$bus_dir" "$os_dir"

	case "${bus_type}" in
	    (pseudo) echo "$type" >"${bus_dir}/type" ;;
	esac

    fi
}

######################################################################

# Overrides multiplexed function.

function list_db_adapters ()
{
    local type="$1"

    local class=$(get_pci_class "$type")

    # The pci and pseudo cases could be cleanly separated, but then
    # the IDE hack would have to be duplicated or put into a separate
    # function.
    local b n
    for b in "pci" "pseudo" ; do
	for n in "${db_bus_dir}/${b}/"* ; do
	    local c=$(sysfs_get_pci_class "$n")
	    if [ -z "$c" -a -f "${n}/type" ] ; then
		read c <"${n}/type"
		c=$(get_pci_class "$c")
	    fi
	    if [ -n "$c" ] ; then
		if [ "$class" = "$c" ] ; then
		    case "$type" in
			(ide) n=$(echo "${n}/"[0-9]*) ;;
		    esac
		    echo "$n"
		fi
	    fi
	done
    done
}

######################################################################

function do_adapter_DEFAULT ()
{
    local type="$1"
    local node="$2"

    local ax osname
    adapter_set_ax_osname_hook

    vpdprop="${node}/linux,vpd"

    # Hook sets these variables.
    local ds yl rl rm
    do_adapter_hook "$type"

    if [ -f "$vpdprop" ] ; then
	ensure_field "$vpdprop" "AX" "$ax"
	[ -n "$rm" ] && override_field "$vpdprop" "RM" "$rm"
	ensure_field "$vpdprop" "YL" "$yl"
	return 0
    else
	case "$node" in
	    */pci*)
		sysfs_do_pci_adapter_hook
		;;
	    */pseudo*)
		yl="pseudo:${yl}"
		print_adapter_vpd_hook >"${vpdprop}"
		;;
	esac
    fi
}

function get_adapter_firmware_version ()
{
    local node="$1"

    sysfs_get_class_attribute "$node" "fw_version"
}

######################################################################

unset -f do_adapter_hook_scsi
unset -f do_adapter_hook_ethernet

function do_adapter_hook_DEFAULT ()
{
    ds=$(get_adapter_ds "$type")
    rm=$(get_adapter_firmware_version "$node")
    yl=$(basename "$node")
}

function do_adapter_hook_ide ()
{
    ds=$(get_adapter_ds "$type")
    yl=$(basename $(dirname "$node"))
    if [ -n "$channel" ] ; then
	yl="${yl}/${channel}"
    fi
}

######################################################################

function do_device_hook_scsi ()
{
    local bus_suffix="${bus}/${target}/${lun}"

    local adapter_bus_link yl_prefix
    sysfs_do_device_hook_common

    yl=$(basename "$adapter_bus_link")
    yl="${yl_prefix}${yl}/${host}:${bus}:${target}:${lun}"
}

function do_device_hook_ide ()
{
    local bus_suffix="$unit"

    local adapter_bus_link yl_prefix
    sysfs_do_device_hook_common

    yl=$(basename $(dirname "$adapter_bus_link"))
    yl="${yl_prefix}${yl}/${channel}.${unit}"
}

function sysfs_do_device_hook_common ()
{

    local subdir=$(get_adapter_subdir "$type")
    local adapter_os_dir="${db_os_dir}/${subdir}/${adapter}"
    local os_dir="${adapter_os_dir}/${name}"

    adapter_bus_link=$(readlink "${adapter_os_dir}/bus-node")
    local adapter_bus_dir="${adapter_os_dir}/${adapter_bus_link}"
    local bus_dir="${adapter_bus_dir}/${bus_suffix}"

    mkdir -p "$os_dir"
    mkdir -p "$bus_dir"
    
    cross_link "$bus_dir" "$os_dir"
    
    case "$adapter_bus_link" in
	(*/pseudo*) yl_prefix="pseudo:" ;;
    esac
    vpdprop="${bus_dir}/linux,vpd"
}

######################################################################

function sysfs_get_adapter_class ()
{
    local type="$1"

    case "$type" in
	(scsi)     echo "scsi_host" ;;
	(ethernet) echo "net"	    ;;
	(ide)      echo "ide"	    ;;
    esac
}

function pcistring2info ()
{
    echo "0x${1//[:.]/ 0x}"
}    

function sysfs_get_node ()
{
    local type="$1"
    local name="$2"

    local subdir=$(get_adapter_subdir "$type")
    local os_dir="${db_os_dir}/${subdir}/${name}"
    local cache_dir="${os_dir}/cache"

    local sysfs_device_node_cache="${cache_dir}/sysfs-device-node"

    if [ -h "$sysfs_device_node_cache" ] ; then
	readlink "$sysfs_device_node_cache"
	return 0
    fi

    local class=$(sysfs_get_adapter_class "$type")
    local sysfs_class_node="${sysfs_dir}/class/${class}/${name}"
    local sysfs_device_link="${sysfs_class_node}/device"
    
    case "$type" in
	ide)
	    sysfs_device_link=""
	    if [ -d "${sysfs_dir}/bus/ide/devices" ] ; then
		# There may be more than one device for this
		# adapter, so grab the details from the 1st one.
		set -f
		set -- $(ls -l "${sysfs_dir}/bus/ide/devices" | \
		    sed -n -e "s@^.* \([0-9.]*\) -> .*/${name}/.*\$@\1@p")
		set +f
		local num="$1"
		if [ -n "$num" ] ; then
		    sysfs_device_link="${sysfs_dir}/bus/ide/devices/${num}"
		fi
	    fi
	    ;;
	*)
	    if [ -d "$sysfs_class_node" ] ; then
		mkdir -p "$cache_dir"
		ln -s "$sysfs_class_node" "${cache_dir}/sysfs-class-node"
	    fi
    esac
    
    if [ -h "$sysfs_device_link" ] ; then
	local l=$(readlink "$sysfs_device_link")
	local dn=$(dirname "$sysfs_device_link")
	local sysfs_device_node=$(pseudo_realpath "${dn}/${l}")
	mkdir -p "$cache_dir"
	ln -s "$sysfs_device_node" "$sysfs_device_node_cache"
	echo "$sysfs_device_node"
    fi
}    

function sysfs_get_bus_string ()
{
    local type="$1"
    local name="$2"

    local sysfs_node=$(sysfs_get_node "$type" "$name")
    local context=""

    if [ -n "$sysfs_node" ] ; then
	
	case "$type" in
	    scsi)
		context="/host.*"
		;;
	    ide)
		num=$(basename "$sysfs_node")
		context="/${name}/${num}"
		;;	    
	esac

	case "$sysfs_node" in
	    */pci*)
		echo "$sysfs_node" | \
		    sed -n -e "s@^.*/\([0-9a-fA-F:.]*\)${context}\$@pci:\1@p"
		;;
	    */pseudo*)
		local n="${sysfs_node#*/adapter}"
		n="${n%%/*}"
		echo "pseudo:${n}"
		;;
	esac
    fi
}    

function sysfs_do_pci_adapter_hook ()
{
    case "$type" in
	scsi)
	    # Multi-channel card or something?
	    local dn=$(dirname "$node")
	    if [ ! -f "${node}/device" -a -f "${dn}/device" ] ; then
		    node="$dn"
	    fi
	    ;;
    esac
	
    if [ -f "${node}/vendor" -a -f "${node}/device" ] ; then
	local vendor device
	read vendor <"${node}/vendor" ; vendor=${vendor#0x}
	read device <"${node}/device" ; device=${device#0x}
	
	local subvendor subdevice cd
	if [ -f "${node}/subsystem_vendor" -a \
	    -f "${node}/subsystem_device" ] ; then
	    read subvendor <"${node}/subsystem_vendor"
	    subvendor=${subvendor#0x}
	    read subdevice <"${node}/subsystem_device"
	    subdevice=${subdevice#0x}
	    
	    cd="${subvendor}${subdevice}"
	    local dscd="${cd:2:2}${cd:0:2}${cd:6:2}${cd:4:2}"
	    ds="${ds} (${dscd})"
	fi
	
	local mf tm
	pci_set_mf_tm_hook
	
	print_adapter_vpd_hook >"$vpdprop"
    fi
}

function sysfs_get_pci_class ()
{
    local node="$1"

    local c="${node}/class"
    if [ -r "$c" ] ; then
	local x
	read x <"$c"
	case "$x" in
	    (0x[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])
	    echo "${x:2:4}"
	    ;;
	esac
    fi
}

# For this to work, it must be called after sysfs_get_node.
function sysfs_get_class_attribute ()
{
    local node="$1"
    local attribute="$2"

    local a="${node}/linux,os-node/cache/sysfs-class-node/${attribute}"
    if [ -r "$a" ] ; then
	cat "$a"
    fi
}
