# -*- 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.38 2004/06/10 04:36:22 martins Exp $

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

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

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

# NOTE: Don't need db_initialise_bus here.  Individual nodes are
# created by do_adapter (via a call to adapter_init_node).  Also
# list_adapters accesses sysfs directly.
adapter_init_node ()
{
    local type="$1"
    local bus_info="$2"

    local bus_type="${bus_info%%/*}"
    local bus_addr="${bus_info#*/}"

    local s="${sysfs_dir}/bus/${bus_type}/devices/${bus_addr}"
    case "$type" in
	(ide) s="${s%/*}" ;; # Strip off channel.
    esac
    local d=$(get_adapter_bus_dir "$type" "$bus_info")

    if [ -n "$d" -a ! -d "$d" ] ; then
	if [ -d "$s" ] ; then
	    ensure_directory "$d"

	    case "$bus_type" in
		pci)
		    local a
		    for a in "class" \
			"vendor" "device" \
			"subsystem_vendor" "subsystem_device" \
			"irq" ; do
		      cp -a "${s}/${a}" "${d}/${a}"
		    done
		    ;;
		pseudo)
		    echo "$type" >"${d}/type"
		    ;;
	    esac
	else
	    debug "adapter_init_node: missing sysfs node \"$s\""
	fi
    fi

    case "$bus_type" in
	(pci) pci_do_pci22_vpd "${bus_addr%/*}" "$d" ;;
    esac
}

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

pci_get_config_filename ()
{
    local pci_addr="$1"

    local conf="${sysfs_dir}/bus/pci/devices/${pci_addr}/config"
    [ -f "$conf" ] && echo "$conf"
}

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

unset -f adapter_setup_scsi
unset -f adapter_setup_ethernet
unset -f adapter_setup_ide

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

list_adapters_DEFAULT ()
{
    local type="$1"

    local n
    for n in "${sysfs_dir}/bus/pci/devices/"* ; do
	local c=$(sysfs_get_pci_class "$n")
	if [ -n "$c" ] ; then
	    if pci_check_class "$type" "$c" ; then
		local ns
		case "$type" in
		    ide)
			ns=$(echo "${n}/ide"[0-9]*)
			ns="${ns//ide/}"
			;;
		    *)
			ns="$n"
			;;
		esac
		local i
		for i in $ns ; do
		    echo "pci/${i#${sysfs_dir}/bus/pci/devices/}"
		done
	    fi
	fi
    done

    case "$type" in
	scsi)
	    for n in "${sysfs_dir}/bus/pseudo/devices/"adapter*/host* ; do
		n="${n#${sysfs_dir}/bus/pseudo/devices/}"
		echo "pseudo/${n%/host*}"
	    done
	    ;;
    esac
}

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

adapter_set_ax_osname_hook_scsi ()
{
    local bus_info="$1"
    
    local sysfs_node=$(sysfs_get_node "scsi" "$bus_info")
    if [ -n "$sysfs_node" ] ; then
	osname="${sysfs_node##*/}"
	case "$bus_info" in
	    (*/*/*) osname="${osname}:${bus_info##*/}" ;; # Add channel
	esac
	adapter_set_ax_from_osname_hook
    else
	adapter_set_ax_osname_hook_DEFAULT "scsi" "$bus_info"
    fi
}

adapter_set_ax_osname_hook_ethernet ()
{
    local bus_info="$1"
    
    # Populate cache with class node too.
    local sysfs_node=$(sysfs_get_node "ethernet" "$bus_info")
    if [ -n "$sysfs_node" ] ; then
	local adapter_bus_dir=$(get_adapter_bus_dir "ethernet" "$bus_info")
	local l="${adapter_bus_dir}/cache/sysfs-class-node"
	
	if [ -L "$l" ] ; then
	    local s=$(get_adapter_subdir "ethernet")
	    local t=$(readlink "$l")
	    t="${t#${sysfs_dir}/class/${s}/}"
	    osname="${t%/device}"
	    adapter_set_ax_from_osname_hook
	fi
    else
	adapter_set_ax_osname_hook_DEFAULT "ethernet" "$bus_info"
    fi
}

adapter_set_ax_osname_hook_ide ()
{
    local bus_info="$1"

    local sysfs_node=$(sysfs_get_node "ide" "$bus_info")
    if [ -n "$sysfs_node" ] ; then
	osname="ide${sysfs_node##*/ide}"
	adapter_set_ax_from_osname_hook
    else
	adapter_set_ax_osname_hook_DEFAULT "ide" "$bus_info"
    fi
}

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

do_adapter_DEFAULT ()
{
    local type="$1"
    local bus_info="$2" # bus_type/dom:bus:dev.fun

    local node=$(get_adapter_bus_dir "$type" "$bus_info")

    if [ ! -d "$node" ] ; then
	adapter_init_node "$type" "$bus_info"
    fi

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

    local vpd_dir
    vpd_dir_set_hook "$node"
    if [ -e "$vpd_dir" ] ; then
        # Already VPD: ensure YL exists, runs extra hooks - we're done.
	vpd_field_ensure   "$vpd_dir" "YL" "$yl"
	adapter_extra_vpd_hook "$type" "$bus_info" "$node"
	return 0
    fi

    local mf tm cd
    case "$node" in
	(*/pci*)    sysfs_do_pci_adapter_hook ;;
	(*/pseudo*) yl="~${yl}"         ;;
    esac
    
    local sn rl rm

    vpd_create_hook "$node"

    adapter_extra_vpd_hook "$type" "$bus_info" "$node"
}

adapter_extra_vpd_hooks="${adapter_extra_vpd_hooks} fw_version_vpd_hook"

fw_version_vpd_hook ()
{
    local type="$1"
    local bus_info="$2"
    local vpd_dir="$3"

    local rm=$(sysfs_get_class_property "$type" "$bus_info" "fw_version")

    if [ -n "$rm" ] ; then
	vpd_field_override_link "$vpd_dir" "RM" "$rm"
    fi
}

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

# do_adapter_hook:
#
# Uses: type, node
# Sets: ds, yl

unset -f do_adapter_hook_ethernet

do_adapter_hook_DEFAULT ()
{
    ds=$(get_adapter_ds "$type")
    yl="${node##*/}" # basename
}

do_adapter_hook_scsi ()
{
    do_adapter_hook_DEFAULT
    local class=$(sysfs_get_pci_class "$node")
    case "$class" in
	(0[Cc]04) ds="Fibre Channel Adapter" ;;
    esac
}

# NOTE: also uses $channel.
do_adapter_hook_ide ()
{
    ds=$(get_adapter_ds "$type")
    yl="${node%/*}" # dirname
    yl="${yl##*/}"  # basename
    if [ -n "$channel" ] ; then
	yl="${yl}/${channel}"
    fi
}

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

do_device_hook_scsi ()
{
    # Uses: bus_info, adapter
    # Sets: adapter_os_dir, device_bus_dir, yl

    local adapter_bus_dir yl_prefix

    sysfs_do_device_hook_common || return 1

    yl="${adapter_bus_dir##*/}" # basename
    yl="${yl_prefix}${yl}/${bus_info#*/}"
}

do_device_hook_ide ()
{
    # Uses: bus_info, adapter
    # Sets: adapter_os_dir, device_bus_dir, yl

    local adapter_bus_dir yl_prefix

    sysfs_do_device_hook_common || return 1

    yl="${adapter_bus_dir%/*}" # dirname
    yl="${yl##*/}"             # basename
    yl="${yl_prefix}${yl}/${bus_info#*/}"
}

sysfs_do_device_hook_common ()
{
    # Uses: bus_info, adapter
    # Sets: adapter_bus_dir, adapter_os_dir, device_bus_dir, yl_prefix

    device_bus_dir=""

    do_device_hook_common || return 1

    local t="${bus_info#*[:.]}"
    device_bus_dir="${adapter_bus_dir}/${t//[:.]//}"
    
    t=$(pseudo_realpath "$device_bus_dir")
    bus_alias_set "$bus_info" "${t#${db_bus_dir}/}"
    
    case "$adapter_bus_dir" in
	(*/pseudo*) yl_prefix="~" ;;
    esac
}

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

sysfs_get_adapter_class ()
{
    local type="$1"

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

sysfs_get_node ()
{
    local type="$1"
    local bus_info="$2"

    local bus_type="${bus_info%%/*}"
    local bus_addr="${bus_info#*/}"

    local adapter_bus_dir=$(get_adapter_bus_dir "$type" "$bus_info")
    local cache_dir="${adapter_bus_dir}/cache"

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

    if [ ! -L "$sysfs_device_node_cache" ] ; then

	local sysfs_device_node

	case "$type" in
	    ide)
		if [ -d "${sysfs_dir}/bus/ide/devices" ] ; then
		    # Find the last part of the corresponding symlink
		    # for $bus_info, respecting channels.
		    local ch="."
		    case "$bus_addr" in
			(*/*) ch="${bus_addr#*/}"
		    esac
		    set -f
		    set -- $(ls -l "${sysfs_dir}/bus/ide/devices" | \
			sed -n -e "s@^.* -> .*/devices/\(${bus_type}.*/${bus_addr%/*}/ide[0-9][0-9]*\)/${ch}\..*\$@\1@p")
		    set +f
		    local t="$1"
		    [ -n "$t" ] && \
			sysfs_device_node="${sysfs_dir}/devices/${t}"
		fi
		;;
	    scsi|ethernet)
		local class=$(sysfs_get_adapter_class "$type")
		local d="${sysfs_dir}/class/${class}"
		local name=$(ls -l "${d}/"*"/device" | \
		    sed -n -e "s@.*${d}/\([^/]*\)/device -> .*/devices/${bus_type}.*/${bus_addr%/*}\(/host[0-9][0-9]*\)*@\1@p")
		if [ -n "$name" ] ; then
		    local sysfs_class_node="${d}/${name}"
		    local t=$(readlink "${sysfs_class_node}/device")
		    if [ -d "$sysfs_class_node" ] ; then
			ensure_directory "$cache_dir"
			ln -s "$sysfs_class_node" "${cache_dir}/sysfs-class-node"
		    fi
		    sysfs_device_node=$(pseudo_realpath "${sysfs_class_node}/${t}")
		else
		    debug "sysfs_get_node: unknown name for \"${bus_info}\""
		fi
		;;
	    *)
		debug "sysfs_get_node: unsupported type \"${type}\""
	esac
    
	if [ -d "$sysfs_device_node" ] ; then
	    ensure_directory "$cache_dir"
	    ln -s "$sysfs_device_node" "$sysfs_device_node_cache"
	fi
    fi

    [ -L "$sysfs_device_node_cache" ] && readlink "$sysfs_device_node_cache"
}

sysfs_do_pci_adapter_hook ()
{
    # Uses: type, node
    # Sets: ds, mf, tm, cd

    case "$type" in
	scsi)
	    # Multi-channel card or something?
	    local dn="${node%/*}" # dirname
	    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
	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
	
	pci_set_mf_tm_hook
    fi
}

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
}

sysfs_get_class_property ()
{
    local type="$1"
    local bus_info="$2"
    local attribute="$3"

    local sysfs_node=$(sysfs_get_node "$type" "$bus_info")
    if [ -n "$sysfs_node" ] ; then
	local adapter_bus_dir=$(get_adapter_bus_dir "$type" "$bus_info")
	local s="${adapter_bus_dir}/cache/sysfs-class-node"
	if [ -L "$s" ] ; then
	    local n=$(readlink "$s")
	    if [ -d "$n" ] ; then
		local a="${n}/${attribute}"
		[ -r "$a" ] && echo "$a"
	    fi
	fi
    fi
}
