# -*- 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.50 2004/10/25 02:15:43 martins Exp $

[ -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"
}

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

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
	    ;;
	ide)
	    for n in "${sysfs_dir}/devices/"ide* ; do
		echo "pseudo/${n#${sysfs_dir}/devices/}"
	    done
	    ;;
    esac
}

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

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

    # Populate cache with class node too.
    local sysfs_node=""
    set_sysfs_node_hook "$type" "$bus_info"
    if [ -n "$sysfs_node" ] ; then
	local t=$(bus_alias_get "$bus_info" "sysfs-class")
	
	if [ -n "$t" ] ; then
	    local s=$(sysfs_get_adapter_class "$type")
	    t="${t#${sysfs_dir}/class/${s}/}"
	    osname="${t%/device}"
	    adapter_set_ax_from_osname_hook
	fi
    else
	adapter_set_ax_osname_hook_DEFAULT "$type" "$bus_info"
    fi
}

adapter_set_ax_osname_hook_scsi ()
{
    local bus_info="$1"
    
    local sysfs_node=""
    set_sysfs_node_hook "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_ide ()
{
    local bus_info="$1"

    local sysfs_node=""
    set_sysfs_node_hook "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
}

adapter_set_ax_osname_hook_ethernet ()
{
    local bus_info="$1"

    sysfs_default_set_ax_osname_hook "ethernet" "$bus_info"
}

adapter_set_ax_osname_hook_usb ()
{
    local bus_info="$1"

    sysfs_default_set_ax_osname_hook "usb" "$bus_info"
}

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

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

    local node=$(bus_alias_get "$bus_info")

    if [ -z "$node" ] ; then
	local sysfs_node=""
	set_sysfs_node_hook "$type" "$bus_info"

	if [ -n "$sysfs_node" ] ; then
	    case "$sysfs_node" in
		"${sysfs_dir}/devices/"*)
		    node="${db_bus_dir}/${sysfs_node#${sysfs_dir}/}"
		    ;;
		(*) : "Unknown device path format." ;;
	    esac
	fi
    fi

    if [ -n "$node" ] ; then
	local relnode="${node#${db_bus_dir}/}"
	bus_alias_set "$bus_info" "$relnode"
        # Has channel?  Also alias without channel.
	case "$bus_info" in
	    */*/*)
		bus_alias_set "${bus_info%/*}" "${relnode%/*}" # dirname
		;;
	esac
	node="${db_bus_dt_dir}/${node}"

	echo "$node"
    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 ()
{
    local class=$(sysfs_get_pci_class "$node")
    case "$class" in
	(0[Cc]04) ds="Fibre Channel Adapter"   ;;
	(*)       ds=$(get_adapter_ds "$type") ;;
    esac
    yl="${node%/*}" # dirname
    yl="${yl##*/}"  # basename
}

# 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
}

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

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

    device_bus_dir=""

    add_device_hook_common || return 1

    # $adapter_bus_dir points to IDE channel or SCSI host adapter, so
    # strip off the first part (of the bus address... including the
    # bus_type) and use subdirectories instead of punctuation.
    local t="${bus_info#*[:.]}"
    device_bus_dir=$(pseudo_realpath "${adapter_bus_dir}/${t//[:.]//}")
    bus_alias_set "$bus_info" "${device_bus_dir#${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"	    ;;
	(usb)      echo "usb_host"  ;;
    esac
}

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

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

    sysfs_node=$(bus_alias_get "$bus_info" "sysfs-device")

    if [ -z "$sysfs_node" ] ; then
	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_node="${sysfs_dir}/devices/${t}"
		fi
		;;
	    display)
		local t="${sysfs_dir}/bus/${bus_type}/devices/${bus_addr}"
		if [ -L "$t" ] ; then
		    local l=$(readlink "$t")
		    [ -n "$l" ] && \
			sysfs_node="${sysfs_dir}/devices/${l#*/devices/}"
		fi
		;;
	    scsi|ethernet|usb)
		local class=$(sysfs_get_adapter_class "$type")
		local d="${sysfs_dir}/class/${class}"
		local sysfs_bus_type="$bus_type"
		case "$bus_type" in
		    (vdevice) sysfs_bus_type="vio" ;;
		esac
		local name=$(ls -l "${d}/"*"/device" | \
		    sed -n -e "s@.*${d}/\([^/]*\)/device -> .*/devices/${sysfs_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
			bus_alias_set \
			    "$bus_info" "$sysfs_class_node" "sysfs-class"
		    fi
		    sysfs_node=$(pseudo_realpath "${sysfs_class_node}/${t}")
		else
		    : "set_sysfs_node_hook: unknown name for \"${bus_info}\""
		fi
		;;
	    *)
		: "set_sysfs_node_hook: unsupported type \"${type}\""
	esac
    
	[ -d "$sysfs_node" ] && \
	    bus_alias_set "$bus_info" "$sysfs_node" "sysfs-device"
    fi
}

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}"

	    if [ "$subvendor" != "0000" -a "$subdevice" != "0000" ] ; then
		cd="${subvendor}${subdevice}"
		local dscd="${cd:2:2}${cd:0:2}${cd:6:2}${cd:4:2}"
		ds="${ds} (${dscd})"
	    fi
	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=""
    set_sysfs_node_hook "$type" "$bus_info"
    if [ -n "$sysfs_node" ] ; then
	local n=$(bus_alias_get "$bus_info" "sysfs-class")
	if [ -n "$n" ] ; then
	    local a="${n}/${attribute}"
	    [ -r "$a" ] && echo "$a"
	fi
    fi
}
